-
LinkedIn - Zip Solver (userScript)
Bu script LinkedIn'de bulunan Zip oyununa "Bulmacayı çöz" adında bir buton ekler.
Kullanım
Bu bir userScript betiğidir, çalıştırabilmek için kullandığınız tarayıcıda Tampermonkey, Violentmonkey vb. gibi bir komut dosyası yöneticisi yüklü olmalıdır.

Bilgilendirme
userScript bir tarayıcı komut dosyası olarak çalışır, yani sadece yüklediğiniz tarayıcı ile bu betiği kullanabilirsiniz. userScript hakkında detaylı bilgi edinmek için buraya tıklayabilirsiniz.
Yükleme adımlarını görmek için tıklayın
- Kullandığınız tarayıcıya Tampermonkey, Violentmonkey vb. gibi bir komut dosyası yöneticisi yükleyin.
- Chromium tabanlı bir tarayıcı (Chrome, Edge, Opera vb.) kullanıyorsanız "Uzantılar" sayfasından geliştirici modunu etkinleştirin.
- Ardından uzantı seçeneklerinden "Kullanıcı Betiklerine İzin Ver" seçeneğini etkinleştirin.
- Tarayıcınızı yeniden başlatın.
- Aşağıdaki snippetin sağ üst köşesinde bulunan "Aç" butonuna tıklayın.
- Karşınıza yükleme ekranı çıkacaktır, Yükle butonuna tıklayarak yükleme işlemini tamamlayabilirsiniz. Olası bir güncelleme yayınlanması durumunda bu ekran otomatik olarak tekrar karşınıza çıkacaktır.
// ==UserScript==
// @name LinkedIn Zip Auto Solver
// @version 1.0
// @description LinkedIn Zip için otomatik çözücü butonu ekler.
// @author Burak Şipşak
// @match https://www.linkedin.com/*
// @icon data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAzMiAzMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwXzMzNTYxXzEzNTgwOCkiPgo8ZyBjbGlwLXBhdGg9InVybCgjY2xpcDFfMzM1NjFfMTM1ODA4KSI+CjxwYXRoIGQ9Ik0xIDVDMSAzLjExNDM4IDEgMi4xNzE1NyAxLjU4NTc5IDEuNTg1NzlDMi4xNzE1NyAxIDMuMTE0MzggMSA1IDFIMjdDMjguODg1NiAxIDI5LjgyODQgMSAzMC40MTQyIDEuNTg1NzlDMzEgMi4xNzE1NyAzMSAzLjExNDM4IDMxIDVWMjdDMzEgMjguODg1NiAzMSAyOS44Mjg0IDMwLjQxNDIgMzAuNDE0MkMyOS44Mjg0IDMxIDI4Ljg4NTYgMzEgMjcgMzFINUMzLjExNDM4IDMxIDIuMTcxNTcgMzEgMS41ODU3OSAzMC40MTQyQzEgMjkuODI4NCAxIDI4Ljg4NTYgMSAyN1Y1WiIgZmlsbD0iIzVCQjRDMiIvPgo8cmVjdCB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHRyYW5zZm9ybT0ibWF0cml4KDQuMzcxMTRlLTA4IC0xIC0xIC00LjM3MTE0ZS0wOCAzMiAzMikiIGZpbGw9IiNGRkQwQkEiLz4KPGcgZmlsdGVyPSJ1cmwoI2ZpbHRlcjBfaV8zMzU2MV8xMzU4MDgpIj4KPHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0yOS41IDI1LjUzNjdDMjkuNSAyNy43MjU2IDI3LjcyNTYgMjkuNSAyNS41MzY3IDI5LjVMMTAuNzM4OCAyOS41QzUuOTEyNTEgMjkuNSAyIDI1LjU4NzUgMiAyMC43NjExVjIwLjc2MTFDMiAxNS45MzQ4IDUuOTEyNTEgMTIuMDIyMyAxMC43Mzg4IDEyLjAyMjNMMjEuMjAyMiAxMi4wMjIzQzIxLjY0MjggMTIuMDIyMyAyMiAxMS42NjUxIDIyIDExLjIyNDRWMTEuMjI0NEMyMiAxMC43ODM4IDIxLjY0MjggMTAuNDI2NiAyMS4yMDIyIDEwLjQyNjZMNi40NjMzIDEwLjQyNjZDNC4yNzQ0MyAxMC40MjY2IDIuNSA4LjY1MjE3IDIuNSA2LjQ2MzNWNi40NjMzQzIuNSA0LjI3NDQzIDQuMjc0NDMgMi41IDYuNDYzMyAyLjVMMjEuMjc1NSAyLjVDMjYuMDkzOSAyLjUgMzAgNi40MDYwNyAzMCAxMS4yMjQ0VjExLjIyNDRDMzAgMTYuMDQyOCAyNi4wOTM5IDE5Ljk0ODkgMjEuMjc1NSAxOS45NDg5TDExLjE4MTQgMTkuOTQ4OUMxMC41MjkgMTkuOTQ4OSAxMCAyMC40Nzc5IDEwIDIxLjEzMDNWMjEuMTMwM0MxMCAyMS4zNzUgMTAuMTk4NCAyMS41NzM0IDEwLjQ0MyAyMS41NzM0TDI1LjUzNjcgMjEuNTczNEMyNy43MjU2IDIxLjU3MzQgMjkuNSAyMy4zNDc4IDI5LjUgMjUuNTM2N1YyNS41MzY3WiIgZmlsbD0iI0ZGNkQyOSIvPgo8L2c+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIxX2lfMzM1NjFfMTM1ODA4KSI+CjxlbGxpcHNlIGN4PSIyLjUiIGN5PSIyLjUiIHJ4PSIyLjUiIHJ5PSIyLjUiIHRyYW5zZm9ybT0ibWF0cml4KC0xIDAgMCAxIDI4IDIyLjc1KSIgZmlsbD0iI0Y0RjJFRSIvPgo8L2c+CjxnIGZpbHRlcj0idXJsKCNmaWx0ZXIyX2lfMzM1NjFfMTM1ODA4KSI+CjxjaXJjbGUgY3g9IjIuNSIgY3k9IjIuNSIgcj0iMi41IiB0cmFuc2Zvcm09Im1hdHJpeCgtMSAwIDAgMSA5LjI1IDMuNSkiIGZpbGw9IiNGNEYyRUUiLz4KPC9nPgo8L2c+CjxwYXRoIGQ9Ik0yNyAwLjVINUw0Ljk2MzQyIDAuNUM0LjA1MTY5IDAuNDk5OTg2IDMuMzE2ODEgMC40OTk5NzUgMi43Mzg4MyAwLjU3NzY4MkMyLjEzODc2IDAuNjU4MzU5IDEuNjMzNTEgMC44MzA5NTUgMS4yMzIyMyAxLjIzMjIzQzAuODMwOTU1IDEuNjMzNTEgMC42NTgzNTkgMi4xMzg3NiAwLjU3NzY4MiAyLjczODgzQzAuNDk5OTc1IDMuMzE2ODEgMC40OTk5ODYgNC4wNTE2OSAwLjUgNC45NjM0MkwwLjUgNVYyN0wwLjUgMjcuMDM2NkMwLjQ5OTk4NiAyNy45NDgzIDAuNDk5OTc1IDI4LjY4MzIgMC41Nzc2ODIgMjkuMjYxMkMwLjY1ODM1OSAyOS44NjEyIDAuODMwOTU1IDMwLjM2NjUgMS4yMzIyMyAzMC43Njc4QzEuNjMzNTEgMzEuMTY5IDIuMTM4NzYgMzEuMzQxNiAyLjczODgzIDMxLjQyMjNDMy4zMTY4MSAzMS41IDQuMDUxNjggMzEuNSA0Ljk2MzQgMzEuNUg1SDI3SDI3LjAzNjZDMjcuOTQ4MyAzMS41IDI4LjY4MzIgMzEuNSAyOS4yNjEyIDMxLjQyMjNDMjkuODYxMiAzMS4zNDE2IDMwLjM2NjUgMzEuMTY5IDMwLjc2NzggMzAuNzY3OEMzMS4xNjkgMzAuMzY2NSAzMS4zNDE2IDI5Ljg2MTIgMzEuNDIyMyAyOS4yNjEyQzMxLjUgMjguNjgzMiAzMS41IDI3Ljk0ODMgMzEuNSAyNy4wMzY2VjI3VjVWNC45NjM0QzMxLjUgNC4wNTE2OCAzMS41IDMuMzE2ODEgMzEuNDIyMyAyLjczODgzQzMxLjM0MTYgMi4xMzg3NiAzMS4xNjkgMS42MzM1MSAzMC43Njc4IDEuMjMyMjNDMzAuMzY2NSAwLjgzMDk1NSAyOS44NjEyIDAuNjU4MzU5IDI5LjI2MTIgMC41Nzc2ODJDMjguNjgzMiAwLjQ5OTk3NSAyNy45NDgzIDAuNDk5OTg2IDI3LjAzNjYgMC41TDI3IDAuNVoiIHN0cm9rZT0iYmxhY2siIHN0cm9rZS1vcGFjaXR5PSIwLjkiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9nPgo8ZGVmcz4KPGZpbHRlciBpZD0iZmlsdGVyMF9pXzMzNTYxXzEzNTgwOCIgeD0iMiIgeT0iMi41IiB3aWR0aD0iMjgiIGhlaWdodD0iMjciIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iLTEuNDA2MjUiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0iYXJpdGhtZXRpYyIgazI9Ii0xIiBrMz0iMSIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwLjIyNDc5IDAgMCAwIDAgMC4yNzM3NzkgMCAwIDAgMCAwLjI0MzU2OSAwIDAgMCAwLjI1IDAiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbjI9InNoYXBlIiByZXN1bHQ9ImVmZmVjdDFfaW5uZXJTaGFkb3dfMzM1NjFfMTM1ODA4Ii8+CjwvZmlsdGVyPgo8ZmlsdGVyIGlkPSJmaWx0ZXIxX2lfMzM1NjFfMTM1ODA4IiB4PSIyMyIgeT0iMjIuNzUiIHdpZHRoPSI1IiBoZWlnaHQ9IjUiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgY29sb3ItaW50ZXJwb2xhdGlvbi1maWx0ZXJzPSJzUkdCIj4KPGZlRmxvb2QgZmxvb2Qtb3BhY2l0eT0iMCIgcmVzdWx0PSJCYWNrZ3JvdW5kSW1hZ2VGaXgiLz4KPGZlQmxlbmQgbW9kZT0ibm9ybWFsIiBpbj0iU291cmNlR3JhcGhpYyIgaW4yPSJCYWNrZ3JvdW5kSW1hZ2VGaXgiIHJlc3VsdD0ic2hhcGUiLz4KPGZlQ29sb3JNYXRyaXggaW49IlNvdXJjZUFscGhhIiB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMTI3IDAiIHJlc3VsdD0iaGFyZEFscGhhIi8+CjxmZU9mZnNldCBkeT0iLTEuNDA2MjUiLz4KPGZlQ29tcG9zaXRlIGluMj0iaGFyZEFscGhhIiBvcGVyYXRvcj0iYXJpdGhtZXRpYyIgazI9Ii0xIiBrMz0iMSIvPgo8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMCAwIDAgMCAwLjM4MTkwMSAwIDAgMCAwIDAuMzgxOTAxIDAgMCAwIDAgMC4zODE5MDEgMCAwIDAgMC4yNSAwIi8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW4yPSJzaGFwZSIgcmVzdWx0PSJlZmZlY3QxX2lubmVyU2hhZG93XzMzNTYxXzEzNTgwOCIvPgo8L2ZpbHRlcj4KPGZpbHRlciBpZD0iZmlsdGVyMl9pXzMzNTYxXzEzNTgwOCIgeD0iNC4yNSIgeT0iMy41IiB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWx0ZXJVbml0cz0idXNlclNwYWNlT25Vc2UiIGNvbG9yLWludGVycG9sYXRpb24tZmlsdGVycz0ic1JHQiI+CjxmZUZsb29kIGZsb29kLW9wYWNpdHk9IjAiIHJlc3VsdD0iQmFja2dyb3VuZEltYWdlRml4Ii8+CjxmZUJsZW5kIG1vZGU9Im5vcm1hbCIgaW49IlNvdXJjZUdyYXBoaWMiIGluMj0iQmFja2dyb3VuZEltYWdlRml4IiByZXN1bHQ9InNoYXBlIi8+CjxmZUNvbG9yTWF0cml4IGluPSJTb3VyY2VBbHBoYSIgdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDAgMCAwIDEyNyAwIiByZXN1bHQ9ImhhcmRBbHBoYSIvPgo8ZmVPZmZzZXQgZHk9Ii0xLjQwNjI1Ii8+CjxmZUNvbXBvc2l0ZSBpbjI9ImhhcmRBbHBoYSIgb3BlcmF0b3I9ImFyaXRobWV0aWMiIGsyPSItMSIgazM9IjEiLz4KPGZlQ29sb3JNYXRyaXggdHlwZT0ibWF0cml4IiB2YWx1ZXM9IjAgMCAwIDAgMC4zODE5MDEgMCAwIDAgMCAwLjM4MTkwMSAwIDAgMCAwIDAuMzgxOTAxIDAgMCAwIDAuMjUgMCIvPgo8ZmVCbGVuZCBtb2RlPSJub3JtYWwiIGluMj0ic2hhcGUiIHJlc3VsdD0iZWZmZWN0MV9pbm5lclNoYWRvd18zMzU2MV8xMzU4MDgiLz4KPC9maWx0ZXI+CjxjbGlwUGF0aCBpZD0iY2xpcDBfMzM1NjFfMTM1ODA4Ij4KPHBhdGggZD0iTTAgNEMwIDIuMTE0MzggMCAxLjE3MTU3IDAuNTg1Nzg2IDAuNTg1Nzg2QzEuMTcxNTcgMCAyLjExNDM4IDAgNCAwSDI4QzI5Ljg4NTYgMCAzMC44Mjg0IDAgMzEuNDE0MiAwLjU4NTc4NkMzMiAxLjE3MTU3IDMyIDIuMTE0MzggMzIgNFYyOEMzMiAyOS44ODU2IDMyIDMwLjgyODQgMzEuNDE0MiAzMS40MTQyQzMwLjgyODQgMzIgMjkuODg1NiAzMiAyOCAzMkg0QzIuMTE0MzggMzIgMS4xNzE1NyAzMiAwLjU4NTc4NiAzMS40MTQyQzAgMzAuODI4NCAwIDI5Ljg4NTYgMCAyOFY0WiIgZmlsbD0id2hpdGUiLz4KPC9jbGlwUGF0aD4KPGNsaXBQYXRoIGlkPSJjbGlwMV8zMzU2MV8xMzU4MDgiPgo8cGF0aCBkPSJNMSA1QzEgMy4xMTQzOCAxIDIuMTcxNTcgMS41ODU3OSAxLjU4NTc5QzIuMTcxNTcgMSAzLjExNDM4IDEgNSAxSDI3QzI4Ljg4NTYgMSAyOS44Mjg0IDEgMzAuNDE0MiAxLjU4NTc5QzMxIDIuMTcxNTcgMzEgMy4xMTQzOCAzMSA1VjI3QzMxIDI4Ljg4NTYgMzEgMjkuODI4NCAzMC40MTQyIDMwLjQxNDJDMjkuODI4NCAzMSAyOC44ODU2IDMxIDI3IDMxSDVDMy4xMTQzOCAzMSAyLjE3MTU3IDMxIDEuNTg1NzkgMzAuNDE0MkMxIDI5LjgyODQgMSAyOC44ODU2IDEgMjdWNVoiIGZpbGw9IndoaXRlIi8+CjwvY2xpcFBhdGg+CjwvZGVmcz4KPC9zdmc+Cg==
// @grant none
// @run-at document-idle
// @updateURL https://sipsak.app/docs/scripts/linkedin-zip-solver.user.js
// @downloadURL https://sipsak.app/docs/scripts/linkedin-zip-solver.user.js
// ==/UserScript==
(function () {
'use strict';
const BUTTON_ID = 'zip-auto-solve-btn';
const BUTTON_ROW_ID = 'zip-auto-solve-row';
const MAX_SEARCH_NODES = 5000000;
const KEY_DELAY = 35;
let isSolving = false;
let stopRequested = false;
const sleep = ms =>
new Promise(resolve => setTimeout(resolve, ms));
function getGrid() {
return document.querySelector(
'[data-testid="interactive-grid"][data-trail-grid], [data-trail-grid]'
);
}
function getControls() {
return document.querySelector(
'[data-testid="under-board-controls"]'
);
}
function getCells(grid) {
if (!grid) {
return [];
}
return [
...grid.querySelectorAll(
':scope > [data-cell-idx]'
)
].sort(
(a, b) =>
Number(a.dataset.cellIdx) -
Number(b.dataset.cellIdx)
);
}
function getCell(index) {
return getGrid()?.querySelector(
`[data-cell-idx="${index}"]`
) || null;
}
function clusterPositions(values, tolerance = 3) {
const sorted =
[...values].sort(
(a, b) => a - b
);
const groups = [];
for (const value of sorted) {
const last =
groups[
groups.length - 1
];
if (
!last ||
Math.abs(
value - last.mean
) > tolerance
) {
groups.push({
values: [value],
mean: value
});
} else {
last.values.push(value);
last.mean =
last.values.reduce(
(a, b) => a + b,
0
) /
last.values.length;
}
}
return groups.map(
group => group.mean
);
}
function getDimensions(grid, cells) {
const style =
getComputedStyle(grid);
const cssRows =
parseInt(
style.getPropertyValue(
'--_966c252c'
),
10
);
const cssCols =
parseInt(
style.getPropertyValue(
'--a136d84c'
),
10
);
if (
cssRows > 0 &&
cssCols > 0 &&
cssRows * cssCols ===
cells.length
) {
return {
rows: cssRows,
cols: cssCols
};
}
const rects =
cells.map(
cell =>
cell.getBoundingClientRect()
);
const rows =
clusterPositions(
rects.map(
rect => rect.top
)
).length;
const cols =
clusterPositions(
rects.map(
rect => rect.left
)
).length;
if (
rows * cols ===
cells.length
) {
return {
rows,
cols
};
}
const side =
Math.round(
Math.sqrt(
cells.length
)
);
if (
side * side ===
cells.length
) {
return {
rows: side,
cols: side
};
}
throw new Error(
'Tahta boyutu belirlenemedi.'
);
}
function readNumber(cell) {
const content =
cell.querySelector(
'[data-cell-content="true"]'
);
if (!content) {
return null;
}
const value =
parseInt(
content.textContent.trim(),
10
);
return (
Number.isInteger(value) &&
value > 0
)
? value
: null;
}
function sideBorderWidth(element, side) {
return (
parseFloat(
getComputedStyle(
element
).getPropertyValue(
`border-${side}-width`
)
) || 0
);
}
function hasLegacyWall(cell, side) {
return !!cell.querySelector(
`[data-wall-${side}], [class*="wall--${side}"]`
);
}
function hasWallBetween(
a,
b,
direction
) {
const opposite = {
up: 'down',
down: 'up',
left: 'right',
right: 'left'
}[direction];
if (
hasLegacyWall(
a,
direction
) ||
hasLegacyWall(
b,
opposite
)
) {
return true;
}
const side = {
up: 'top',
down: 'bottom',
left: 'left',
right: 'right'
}[direction];
const oppositeSide = {
up: 'bottom',
down: 'top',
left: 'right',
right: 'left'
}[direction];
return (
sideBorderWidth(
a,
side
) >= 3 ||
sideBorderWidth(
b,
oppositeSide
) >= 3
);
}
function buildBoard() {
const grid =
getGrid();
if (!grid) {
throw new Error(
'Zip oyun tahtası bulunamadı.'
);
}
const cells =
getCells(grid);
if (!cells.length) {
throw new Error(
'Zip hücreleri bulunamadı.'
);
}
const {
rows,
cols
} = getDimensions(
grid,
cells
);
const total =
rows * cols;
if (
total !==
cells.length
) {
throw new Error(
'Tahta hücre sayısı uyuşmuyor.'
);
}
const numberAt =
new Array(total)
.fill(null);
const cellByNumber =
new Map();
cells.forEach(
(cell, index) => {
const number =
readNumber(cell);
if (
number !== null
) {
numberAt[
index
] = number;
cellByNumber.set(
number,
index
);
}
}
);
if (
!cellByNumber.has(1)
) {
throw new Error(
'1 numaralı hücre bulunamadı.'
);
}
const numbers =
[
...cellByNumber.keys()
].sort(
(a, b) => a - b
);
const maxNumber =
numbers[
numbers.length - 1
];
for (
let number = 1;
number <= maxNumber;
number++
) {
if (
!cellByNumber.has(
number
)
) {
throw new Error(
`${number} numaralı hücre bulunamadı.`
);
}
}
const adjacency =
Array.from(
{
length: total
},
() => []
);
const addEdge = (
a,
b,
directionA,
directionB
) => {
if (
!hasWallBetween(
cells[a],
cells[b],
directionA
) &&
!hasWallBetween(
cells[b],
cells[a],
directionB
)
) {
adjacency[
a
].push(b);
adjacency[
b
].push(a);
}
};
for (
let row = 0;
row < rows;
row++
) {
for (
let col = 0;
col < cols;
col++
) {
const index =
row * cols +
col;
if (
col + 1 <
cols
) {
addEdge(
index,
index + 1,
'right',
'left'
);
}
if (
row + 1 <
rows
) {
addEdge(
index,
index + cols,
'down',
'up'
);
}
}
}
return {
grid,
cells,
rows,
cols,
total,
numberAt,
cellByNumber,
maxNumber,
adjacency,
start:
cellByNumber.get(1),
end:
cellByNumber.get(
maxNumber
)
};
}
function solveBoard(board) {
const {
cols,
total,
numberAt,
cellByNumber,
maxNumber,
adjacency,
start,
end
} = board;
const startColor =
(
Math.floor(
start / cols
) +
start % cols
) & 1;
const endColor =
(
Math.floor(
end / cols
) +
end % cols
) & 1;
if (
(
total % 2 === 0 &&
startColor === endColor
) ||
(
total % 2 === 1 &&
startColor !== endColor
)
) {
return null;
}
const visited =
new Uint8Array(total);
const path = [
start
];
visited[start] = 1;
let visitedCount = 1;
let nodes = 0;
function effectiveDegree(
vertex,
head
) {
let degree = 0;
for (
const neighbor
of adjacency[
vertex
]
) {
if (
!visited[
neighbor
] ||
neighbor === head
) {
degree++;
}
}
return degree;
}
function connectedRemaining(
head
) {
const stack = [
head
];
const seen =
new Uint8Array(
total
);
seen[head] = 1;
let count = 1;
while (
stack.length
) {
const vertex =
stack.pop();
for (
const neighbor
of adjacency[
vertex
]
) {
if (
seen[
neighbor
]
) {
continue;
}
if (
visited[
neighbor
] &&
neighbor !==
head
) {
continue;
}
seen[
neighbor
] = 1;
count++;
stack.push(
neighbor
);
}
}
return (
count ===
total -
visitedCount +
1
);
}
function deadEndOkay(
head
) {
let forced =
-1;
for (
let vertex = 0;
vertex < total;
vertex++
) {
if (
visited[
vertex
]
) {
continue;
}
const degree =
effectiveDegree(
vertex,
head
);
if (
degree === 0
) {
return false;
}
if (
degree === 1 &&
vertex !== end
) {
if (
forced !== -1
) {
return false;
}
forced =
vertex;
}
}
return (
forced === -1 ||
adjacency[
head
].includes(
forced
)
);
}
function manhattan(
a,
b
) {
const ar =
Math.floor(
a / cols
);
const ac =
a % cols;
const br =
Math.floor(
b / cols
);
const bc =
b % cols;
return (
Math.abs(
ar - br
) +
Math.abs(
ac - bc
)
);
}
function dfs(
head,
expectedNumber
) {
nodes++;
if (
nodes >
MAX_SEARCH_NODES ||
stopRequested
) {
return false;
}
if (
visitedCount ===
total
) {
return (
head === end &&
expectedNumber ===
maxNumber + 1
);
}
const checkpoint =
cellByNumber.get(
expectedNumber
);
if (
checkpoint !==
undefined &&
manhattan(
head,
checkpoint
) >
total -
visitedCount
) {
return false;
}
const candidates =
[];
for (
const next
of adjacency[
head
]
) {
if (
visited[
next
]
) {
continue;
}
const number =
numberAt[
next
];
if (
number !== null &&
number !==
expectedNumber
) {
continue;
}
if (
next === end &&
visitedCount !==
total - 1
) {
continue;
}
const nextExpected =
number ===
expectedNumber
? expectedNumber +
1
: expectedNumber;
let onward = 0;
for (
const neighbor
of adjacency[
next
]
) {
if (
!visited[
neighbor
]
) {
onward++;
}
}
candidates.push({
next,
nextExpected,
checkpoint:
number ===
expectedNumber
? 0
: 1,
onward
});
}
candidates.sort(
(a, b) =>
a.checkpoint -
b.checkpoint ||
a.onward -
b.onward
);
for (
const candidate
of candidates
) {
visited[
candidate.next
] = 1;
visitedCount++;
path.push(
candidate.next
);
let valid =
deadEndOkay(
candidate.next
);
if (valid) {
valid =
connectedRemaining(
candidate.next
);
}
if (
valid &&
dfs(
candidate.next,
candidate.nextExpected
)
) {
return true;
}
path.pop();
visitedCount--;
visited[
candidate.next
] = 0;
}
return false;
}
return dfs(
start,
2
)
? [...path]
: null;
}
function findButtonByText(text) {
const controls =
getControls();
if (!controls) {
return null;
}
return [
...controls.querySelectorAll(
'button'
)
].find(
button =>
button.textContent
.trim()
.toLocaleLowerCase(
'tr-TR'
) ===
text.toLocaleLowerCase(
'tr-TR'
)
) || null;
}
async function resetBoard() {
for (
let i = 0;
i < 100;
i++
) {
const undo =
findButtonByText(
'Geri Al'
);
if (
!undo ||
undo.disabled
) {
break;
}
undo.click();
await sleep(20);
}
await sleep(80);
}
function getFilledCount() {
return (
getGrid()
?.querySelectorAll(
'[data-testid="filled-cell"]'
)
.length ||
0
);
}
function getReactProps(element) {
if (!element) {
return null;
}
const key =
Object.keys(
element
).find(
name =>
name.startsWith(
'__reactProps$'
)
);
return key
? element[key]
: null;
}
function getKeyInfo(from, to, cols) {
if (
to ===
from - cols
) {
return {
key: 'ArrowUp',
code: 'ArrowUp',
keyCode: 38
};
}
if (
to ===
from + cols
) {
return {
key: 'ArrowDown',
code: 'ArrowDown',
keyCode: 40
};
}
if (
to ===
from - 1
) {
return {
key: 'ArrowLeft',
code: 'ArrowLeft',
keyCode: 37
};
}
if (
to ===
from + 1
) {
return {
key: 'ArrowRight',
code: 'ArrowRight',
keyCode: 39
};
}
throw new Error(
'Geçersiz yön tespit edildi.'
);
}
function createKeyboardEvent(
type,
info
) {
return new KeyboardEvent(
type,
{
key: info.key,
code: info.code,
keyCode:
info.keyCode,
which:
info.keyCode,
charCode: 0,
location: 0,
repeat: false,
isComposing: false,
bubbles: true,
cancelable: true,
composed: true,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false
}
);
}
function dispatchKeyboard(
target,
info
) {
target.dispatchEvent(
createKeyboardEvent(
'keydown',
info
)
);
target.dispatchEvent(
createKeyboardEvent(
'keyup',
info
)
);
}
function createReactKeyEvent(
target,
currentTarget,
info
) {
const nativeEvent = {
type: 'keydown',
key: info.key,
code: info.code,
keyCode:
info.keyCode,
which:
info.keyCode,
charCode: 0,
location: 0,
repeat: false,
isComposing: false,
altKey: false,
ctrlKey: false,
metaKey: false,
shiftKey: false,
target,
currentTarget,
bubbles: true,
cancelable: true,
composed: true,
isTrusted: true,
preventDefault() {},
stopPropagation() {},
stopImmediatePropagation() {}
};
return {
...nativeEvent,
nativeEvent,
defaultPrevented: false,
eventPhase: 3,
persist() {},
preventDefault() {
this.defaultPrevented =
true;
},
stopPropagation() {},
stopImmediatePropagation() {},
isDefaultPrevented() {
return this
.defaultPrevented;
},
isPropagationStopped() {
return false;
}
};
}
function invokeReactKeyboard(
target,
info
) {
const grid =
getGrid();
const used =
new Set();
let count = 0;
let element =
target;
for (
let depth = 0;
element &&
depth < 12;
depth++,
element =
element.parentElement
) {
const props =
getReactProps(
element
);
if (props) {
for (
const name
of [
'onKeyDownCapture',
'onKeyDown'
]
) {
const handler =
props[
name
];
if (
typeof handler ===
'function' &&
!used.has(
handler
)
) {
used.add(
handler
);
try {
handler(
createReactKeyEvent(
target,
element,
info
)
);
count++;
} catch (
error
) {
console.debug(
'[Zip Solver]',
error
);
}
}
}
}
if (
element ===
document.body
) {
break;
}
}
if (
grid &&
!grid.contains(
target
)
) {
const props =
getReactProps(
grid
);
if (props) {
for (
const name
of [
'onKeyDownCapture',
'onKeyDown'
]
) {
const handler =
props[
name
];
if (
typeof handler ===
'function' &&
!used.has(
handler
)
) {
used.add(
handler
);
try {
handler(
createReactKeyEvent(
target,
grid,
info
)
);
count++;
} catch (
error
) {
console.debug(
'[Zip Solver]',
error
);
}
}
}
}
}
return count;
}
async function sendArrow(
info,
expectedFilled
) {
const grid =
getGrid();
if (!grid) {
return true;
}
const active =
document.activeElement;
const target =
active &&
active !==
document.body &&
active !==
document.documentElement
? active
: grid;
dispatchKeyboard(
target,
info
);
await sleep(
KEY_DELAY
);
if (
getFilledCount() >=
expectedFilled
) {
return true;
}
invokeReactKeyboard(
target,
info
);
await sleep(
KEY_DELAY
);
if (
getFilledCount() >=
expectedFilled
) {
return true;
}
if (
target !== grid
) {
dispatchKeyboard(
grid,
info
);
await sleep(
KEY_DELAY
);
if (
getFilledCount() >=
expectedFilled
) {
return true;
}
}
dispatchKeyboard(
document.body,
info
);
await sleep(
KEY_DELAY
);
return (
getFilledCount() >=
expectedFilled
);
}
async function activateKeyboardMode(
board
) {
const startCell =
getCell(
board.start
);
if (!startCell) {
throw new Error(
'Başlangıç hücresi bulunamadı.'
);
}
const oldTabindex =
startCell.getAttribute(
'tabindex'
);
startCell.setAttribute(
'tabindex',
'-1'
);
try {
startCell.focus({
preventScroll: true
});
} catch (_) {
startCell.focus();
}
startCell.click();
await sleep(80);
if (
getFilledCount() < 1
) {
startCell.dispatchEvent(
new MouseEvent(
'mousedown',
{
bubbles: true,
cancelable: true,
composed: true,
button: 0,
buttons: 1,
view: window
}
)
);
startCell.dispatchEvent(
new MouseEvent(
'mouseup',
{
bubbles: true,
cancelable: true,
composed: true,
button: 0,
buttons: 0,
view: window
}
)
);
startCell.click();
await sleep(80);
}
if (
oldTabindex ===
null
) {
startCell.removeAttribute(
'tabindex'
);
} else {
startCell.setAttribute(
'tabindex',
oldTabindex
);
}
if (
getFilledCount() < 1
) {
throw new Error(
'Klavye modu başlatılamadı.'
);
}
}
async function applyKeyboardSolution(
board,
path
) {
await resetBoard();
await activateKeyboardMode(
board
);
for (
let i = 1;
i < path.length;
i++
) {
if (
stopRequested
) {
return false;
}
const info =
getKeyInfo(
path[i - 1],
path[i],
board.cols
);
const moved =
await sendArrow(
info,
i + 1
);
if (!moved) {
throw new Error(
`${i}. adımda ${info.key} tuşu LinkedIn tarafından kabul edilmedi.`
);
}
}
await sleep(500);
return true;
}
function getSolverButton() {
return document
.getElementById(
BUTTON_ID
);
}
function getSolverLabel() {
const button =
getSolverButton();
if (!button) {
return null;
}
const leaves = [
...button.querySelectorAll(
'span'
)
].filter(
span =>
!span.children
.length &&
span.textContent
.trim()
);
return (
leaves[
leaves.length - 1
] ||
null
);
}
function setSolverText(text) {
const label =
getSolverLabel();
if (label) {
label.textContent =
text;
}
}
async function solveAndApply() {
if (isSolving) {
stopRequested =
true;
setSolverText(
'Durduruluyor…'
);
return;
}
isSolving = true;
stopRequested = false;
setSolverText(
'Çözülüyor…'
);
try {
await resetBoard();
const board =
buildBoard();
const path =
solveBoard(
board
);
if (
stopRequested
) {
return;
}
if (!path) {
throw new Error(
'Geçerli çözüm bulunamadı.'
);
}
console.log(
'[Zip Solver] Çözüm:',
path
);
console.log(
'[Zip Solver] Tuşlar:',
path
.slice(1)
.map(
(
cell,
index
) =>
getKeyInfo(
path[
index
],
cell,
board.cols
).key
)
);
setSolverText(
'Uygulanıyor…'
);
await applyKeyboardSolution(
board,
path
);
if (
stopRequested
) {
return;
}
setSolverText(
'Tamamlandı'
);
await sleep(900);
} catch (error) {
console.error(
'[Zip Solver]',
error
);
alert(
`Zip Solver: ${error.message}`
);
} finally {
isSolving = false;
stopRequested = false;
setTimeout(
() => {
if (
getSolverButton()
) {
setSolverText(
'Bulmacayı Çöz'
);
}
},
1200
);
}
}
function removeSolverButton() {
document
.getElementById(
BUTTON_ROW_ID
)
?.remove();
}
function injectButton() {
const grid =
getGrid();
const controls =
getControls();
if (
!grid ||
!controls
) {
removeSolverButton();
return;
}
if (
document.getElementById(
BUTTON_ROW_ID
)
) {
return;
}
const sourceButton =
findButtonByText(
'İpucu'
) ||
controls.querySelector(
'button'
);
if (!sourceButton) {
return;
}
const row =
document.createElement(
'div'
);
row.id =
BUTTON_ROW_ID;
row.style.cssText =
'display:flex;justify-content:center;align-items:center;position:relative;top:-8px;margin:0;padding:0;';
const wrapper =
sourceButton
.parentElement
.cloneNode(
false
);
wrapper.removeAttribute(
'style'
);
const button =
sourceButton.cloneNode(
true
);
button.id =
BUTTON_ID;
button.type =
'button';
button.removeAttribute(
'componentkey'
);
button.removeAttribute(
'disabled'
);
button.removeAttribute(
'aria-disabled'
);
button.removeAttribute(
'aria-expanded'
);
const leaves = [
...button.querySelectorAll(
'span'
)
].filter(
span =>
!span.children
.length &&
span.textContent
.trim()
);
if (
leaves.length
) {
leaves[
leaves.length - 1
].textContent =
'Bulmacayı Çöz';
} else {
button.textContent =
'Bulmacayı Çöz';
}
button.addEventListener(
'click',
event => {
event.preventDefault();
event.stopPropagation();
solveAndApply();
}
);
wrapper.appendChild(
button
);
row.appendChild(
wrapper
);
controls.insertAdjacentElement(
'afterend',
row
);
}
let observerQueued =
false;
const observer =
new MutationObserver(
() => {
if (
observerQueued
) {
return;
}
observerQueued =
true;
requestAnimationFrame(
() => {
observerQueued =
false;
injectButton();
}
);
}
);
observer.observe(
document.documentElement,
{
childList: true,
subtree: true
}
);
injectButton();
})();