Kick mesaj gönderme 403 hatası için fetch retry fix
// ==UserScript==
// @name Kick Mesaj Fix
// @namespace wichlost-kick-message-fix
// @version 6.0.0
// @description Kick mesaj gönderme 403 hatası için fetch retry fix
// @match https://kick.com/*
// @match https://www.kick.com/*
// @run-at document-start
// @grant unsafeWindow
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// ==/UserScript==
(function () {
'use strict';
const W = typeof unsafeWindow !== 'undefined' ? unsafeWindow : window;
const APP = 'Kick Mesaj Fix';
const MAKER = 'wichlost';
const SEND_REGEX = /\/api\/v2\/messages\/send\/\d+/i;
const DEFAULTS = {
enabled: true,
retryCount: 1,
retryDelay: 400,
refreshBeforeRetry: true,
notifySuccess: true,
keepLogs: true,
maxLogs: 80,
debug: false
};
const state = {
retrying: false,
panelOpen: false,
panelCreated: false,
lastStatus: null,
last403: null,
lastFix: null,
logs: GM_getValue('kmf_logs', []),
total403: GM_getValue('kmf_total403', 0),
totalFixed: GM_getValue('kmf_totalFixed', 0)
};
const originalFetch = W.fetch.bind(W);
function opt(key) {
return GM_getValue('kmf_' + key, DEFAULTS[key]);
}
function setOpt(key, value) {
GM_setValue('kmf_' + key, value);
}
function now() {
return new Date().toLocaleTimeString('tr-TR', {
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function esc(v) {
return String(v ?? '')
.replaceAll('&', '&')
.replaceAll('<', '<')
.replaceAll('>', '>')
.replaceAll('"', '"')
.replaceAll("'", ''');
}
function saveLogs() {
const max = Number(opt('maxLogs')) || 80;
state.logs = state.logs.slice(-max);
GM_setValue('kmf_logs', state.logs);
}
function log(text, data) {
const row = {
time: now(),
text,
data: data || null
};
if (opt('keepLogs')) {
state.logs.push(row);
saveLogs();
}
if (opt('debug')) {
console.log(`[${APP}] ${row.time} - ${text}`, data || '');
}
updatePanel();
}
function bump(key) {
const storeKey = key === '403' ? 'kmf_total403' : 'kmf_totalFixed';
const next = Number(GM_getValue(storeKey, 0)) + 1;
GM_setValue(storeKey, next);
if (key === '403') state.total403 = next;
if (key === 'fixed') state.totalFixed = next;
updatePanel();
}
function isSendUrl(url) {
try {
const u = new URL(url, W.location.origin);
return SEND_REGEX.test(u.pathname);
} catch {
return false;
}
}
function getCookie(name) {
const safe = name.replace(/[.$?*|{}()[\]\\/+^]/g, '\\$&');
const match = W.document.cookie.match(new RegExp('(?:^|; )' + safe + '=([^;]*)'));
return match ? decodeURIComponent(match[1]) : '';
}
function cloneHeaders(headers) {
const out = new W.Headers(headers || {});
out.set('Accept', out.get('Accept') || 'application/json, text/plain, */*');
out.set('X-Requested-With', 'XMLHttpRequest');
const xsrf = getCookie('XSRF-TOKEN');
if (xsrf && !out.has('X-XSRF-TOKEN')) {
out.set('X-XSRF-TOKEN', xsrf);
}
return out;
}
async function refreshSession() {
const paths = [
'/sanctum/csrf-cookie',
'/api/v2/user',
W.location.pathname
];
log('Oturum/CSRF tazeleniyor');
for (const path of paths) {
try {
const res = await originalFetch(path, {
method: 'GET',
credentials: 'include',
cache: 'no-store',
headers: {
'Accept': 'application/json,text/html,*/*',
'X-Requested-With': 'XMLHttpRequest'
}
});
log(`Refresh: ${path} -> ${res.status}`);
await sleep(220);
} catch (err) {
log(`Refresh hata: ${path}`, {
error: err.message || String(err)
});
}
}
}
async function buildRetryPayload(input, init) {
if (input instanceof W.Request) {
const req = input.clone();
const method = req.method || 'GET';
let body;
if (!['GET', 'HEAD'].includes(method.toUpperCase())) {
try {
body = await req.clone().text();
} catch {
body = undefined;
}
}
return {
input: req.url,
init: {
method,
body,
credentials: 'include',
cache: 'no-store',
mode: req.mode,
redirect: req.redirect,
referrer: req.referrer,
headers: cloneHeaders(req.headers)
}
};
}
return {
input,
init: {
...(init || {}),
credentials: 'include',
cache: 'no-store',
headers: cloneHeaders(init && init.headers)
}
};
}
async function retryMessage(safeInput, safeInit, url) {
if (state.retrying) {
log('Retry zaten çalışıyor, pas geçildi');
return null;
}
state.retrying = true;
updatePanel();
try {
const max = Math.max(1, Number(opt('retryCount')) || 1);
let lastRes = null;
for (let i = 1; i <= max; i++) {
if (opt('refreshBeforeRetry')) {
await refreshSession();
}
await sleep(Number(opt('retryDelay')) || 400);
const payload = await buildRetryPayload(safeInput, safeInit);
log(`403 sonrası mesaj tekrar deneniyor: ${i}/${max}`);
const retryRes = await originalFetch(payload.input, payload.init);
lastRes = retryRes;
state.lastStatus = retryRes.status;
log(`Retry sonucu: ${retryRes.status}`);
if (retryRes.status !== 403) {
state.lastFix = {
time: now(),
status: retryRes.status,
attempt: i,
url
};
bump('fixed');
log('Retry 403 dışında cevap verdi, mesaj düzelmiş olmalı', state.lastFix);
if (retryRes.status >= 200 && retryRes.status < 300 && opt('notifySuccess')) {
showToast('Mesaj gönderildi', `403 sonrası ${i}. denemede düzeldi`);
}
return retryRes;
}
}
log('Retry sonrası 403 devam ediyor');
return lastRes;
} catch (err) {
log('Retry hata verdi', {
error: err.message || String(err)
});
return null;
} finally {
state.retrying = false;
updatePanel();
}
}
W.fetch = async function kickMessageFixPatchedFetch(input, init) {
if (!opt('enabled')) {
return originalFetch(input, init);
}
const url = input instanceof W.Request ? input.url : String(input || '');
const isSend = isSendUrl(url);
let safeInput = input;
let safeInit = init ? { ...init } : init;
if (isSend && input instanceof W.Request) {
try {
safeInput = input.clone();
} catch {
safeInput = input;
}
}
let res;
try {
res = await originalFetch(input, init);
} catch (err) {
if (isSend) {
log('Mesaj fetch hatası', {
error: err.message || String(err)
});
}
throw err;
}
if (!isSend) {
return res;
}
state.lastStatus = res.status;
log(`Mesaj gönderme isteği: ${res.status}`);
if (res.status === 403) {
state.last403 = {
time: now(),
url
};
bump('403');
log('403 yakalandı', state.last403);
const retried = await retryMessage(safeInput, safeInit, url);
if (retried) {
return retried;
}
}
return res;
};
function ensureStyle() {
if (W.document.getElementById('kmf-style')) return;
const style = W.document.createElement('style');
style.id = 'kmf-style';
style.textContent = `
#kmf-toast-wrap {
position: fixed;
left: 50%;
bottom: 34px;
transform: translateX(-50%);
z-index: 2147483647;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
pointer-events: none;
}
.kmf-toast {
min-width: 260px;
max-width: 360px;
padding: 12px 15px;
border-radius: 18px;
background:
radial-gradient(circle at top left, rgba(168,85,247,.30), transparent 42%),
linear-gradient(145deg, rgba(12,9,20,.98), rgba(29,16,46,.98));
color: #fff;
border: 1px solid rgba(216,180,254,.28);
box-shadow: 0 18px 55px rgba(0,0,0,.58), 0 0 34px rgba(168,85,247,.25);
font-family: Arial, sans-serif;
opacity: 0;
transform: translateY(12px) scale(.98);
text-align: center;
animation: kmfToastIn .18s ease forwards, kmfToastOut .25s ease forwards 3.4s;
}
.kmf-toast-title {
font-size: 13px;
font-weight: 950;
}
.kmf-toast-msg {
margin-top: 4px;
font-size: 12px;
color: #d8c6ff;
}
@keyframes kmfToastIn {
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes kmfToastOut {
to {
opacity: 0;
transform: translateY(10px) scale(.98);
}
}
#kmf-overlay {
position: fixed;
inset: 0;
display: none;
z-index: 2147483646;
align-items: center;
justify-content: center;
background:
radial-gradient(circle at 35% 25%, rgba(147,51,234,.22), transparent 34%),
rgba(0,0,0,.62);
backdrop-filter: blur(8px);
font-family: Arial, sans-serif;
}
#kmf-overlay.kmf-show {
display: flex;
}
#kmf-panel {
width: 330px;
border-radius: 22px;
color: #fff;
overflow: hidden;
background:
radial-gradient(circle at top left, rgba(168,85,247,.30), transparent 45%),
linear-gradient(145deg, rgba(10,8,16,.98), rgba(26,14,42,.98));
border: 1px solid rgba(216,180,254,.24);
box-shadow: 0 28px 85px rgba(0,0,0,.68), 0 0 40px rgba(147,51,234,.20);
transform: translateY(10px) scale(.97);
opacity: 0;
animation: kmfPanelIn .18s ease forwards;
}
@keyframes kmfPanelIn {
to {
transform: translateY(0) scale(1);
opacity: 1;
}
}
.kmf-head {
padding: 14px 15px;
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(255,255,255,.045);
border-bottom: 1px solid rgba(255,255,255,.08);
}
.kmf-title {
display: flex;
align-items: center;
gap: 9px;
font-size: 14px;
font-weight: 950;
}
.kmf-dot {
width: 10px;
height: 10px;
border-radius: 99px;
background: #a855f7;
box-shadow: 0 0 16px #a855f7;
}
.kmf-close {
width: 31px;
height: 31px;
border: 0;
border-radius: 12px;
cursor: pointer;
color: #fff;
background: rgba(255,255,255,.09);
font-size: 18px;
}
.kmf-body {
padding: 13px;
}
.kmf-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.kmf-card {
padding: 10px;
border-radius: 15px;
background: rgba(0,0,0,.25);
border: 1px solid rgba(255,255,255,.075);
}
.kmf-card span {
display: block;
font-size: 10px;
color: #bda7e8;
margin-bottom: 6px;
}
.kmf-card b {
display: block;
font-size: 14px;
color: #fff;
}
.kmf-row {
margin-top: 10px;
padding-top: 10px;
border-top: 1px solid rgba(255,255,255,.08);
display: flex;
align-items: center;
justify-content: space-between;
gap: 9px;
color: #dbcaff;
font-size: 12px;
}
.kmf-input {
width: 58px;
border: 1px solid rgba(255,255,255,.12);
background: rgba(0,0,0,.28);
color: #fff;
border-radius: 11px;
padding: 7px;
text-align: center;
font-weight: 900;
outline: none;
}
.kmf-switch {
position: relative;
width: 44px;
height: 25px;
flex: 0 0 auto;
}
.kmf-switch input {
display: none;
}
.kmf-slider {
position: absolute;
inset: 0;
border-radius: 999px;
cursor: pointer;
background: rgba(255,255,255,.13);
}
.kmf-slider:before {
content: "";
position: absolute;
width: 19px;
height: 19px;
left: 3px;
top: 3px;
border-radius: 50%;
background: #fff;
transition: .15s;
}
.kmf-switch input:checked + .kmf-slider {
background: linear-gradient(135deg, #7c3aed, #d946ef);
}
.kmf-switch input:checked + .kmf-slider:before {
transform: translateX(19px);
}
.kmf-actions {
display: flex;
gap: 7px;
margin-top: 11px;
}
.kmf-btn {
flex: 1;
border: 0;
border-radius: 13px;
padding: 8px;
cursor: pointer;
color: #fff;
font-size: 11px;
font-weight: 900;
background: rgba(255,255,255,.09);
border: 1px solid rgba(255,255,255,.08);
}
.kmf-btn.primary {
background: linear-gradient(135deg, #7c3aed, #c026d3);
box-shadow: 0 0 20px rgba(192,38,211,.22);
}
.kmf-log {
margin-top: 11px;
max-height: 135px;
overflow: auto;
padding: 9px;
border-radius: 13px;
background: rgba(0,0,0,.28);
color: #cbb8f7;
font: 10px/1.45 Consolas, monospace;
white-space: pre-wrap;
}
.kmf-maker {
margin-top: 10px;
text-align: center;
color: #8b74bd;
font-size: 10px;
letter-spacing: .2px;
}
`;
W.document.documentElement.appendChild(style);
}
function showToast(title, msg) {
ensureStyle();
let wrap = W.document.getElementById('kmf-toast-wrap');
if (!wrap) {
wrap = W.document.createElement('div');
wrap.id = 'kmf-toast-wrap';
W.document.documentElement.appendChild(wrap);
}
const box = W.document.createElement('div');
box.className = 'kmf-toast';
box.innerHTML = `
<div class="kmf-toast-title">${esc(title)}</div>
<div class="kmf-toast-msg">${esc(msg)}</div>
`;
wrap.appendChild(box);
setTimeout(() => box.remove(), 3900);
}
function createPanel() {
if (state.panelCreated) return;
state.panelCreated = true;
ensureStyle();
const overlay = W.document.createElement('div');
overlay.id = 'kmf-overlay';
overlay.innerHTML = `
<div id="kmf-panel">
<div class="kmf-head">
<div class="kmf-title">
<span class="kmf-dot"></span>
<span>Kick Mesaj Fix</span>
</div>
<button class="kmf-close" id="kmf-close">×</button>
</div>
<div class="kmf-body">
<div class="kmf-grid">
<div class="kmf-card">
<span>Durum</span>
<b id="kmf-status">Aktif</b>
</div>
<div class="kmf-card">
<span>Son istek</span>
<b id="kmf-last">Yok</b>
</div>
<div class="kmf-card">
<span>403</span>
<b id="kmf-403">0</b>
</div>
<div class="kmf-card">
<span>Fix</span>
<b id="kmf-fixed">0</b>
</div>
</div>
<div class="kmf-row">
<span>Fix aktif</span>
<label class="kmf-switch">
<input id="kmf-enabled" type="checkbox">
<span class="kmf-slider"></span>
</label>
</div>
<div class="kmf-row">
<span>Retry</span>
<input id="kmf-retry" class="kmf-input" type="number" min="1" max="5">
</div>
<div class="kmf-row">
<span>Gecikme</span>
<input id="kmf-delay" class="kmf-input" type="number" min="100" max="3000">
</div>
<div class="kmf-actions">
<button class="kmf-btn primary" id="kmf-refresh">Oturum</button>
<button class="kmf-btn" id="kmf-copy">Log</button>
<button class="kmf-btn" id="kmf-clear">Temizle</button>
</div>
<div class="kmf-log" id="kmf-log"></div>
<div class="kmf-maker">${MAKER}</div>
</div>
</div>
`;
W.document.documentElement.appendChild(overlay);
overlay.addEventListener('click', e => {
if (e.target === overlay) closePanel();
});
W.document.getElementById('kmf-close').onclick = closePanel;
W.document.getElementById('kmf-refresh').onclick = async () => {
await refreshSession();
showToast('Oturum tazelendi', 'CSRF/oturum yenileme denendi');
};
W.document.getElementById('kmf-copy').onclick = async () => {
const text = logsText();
try {
await navigator.clipboard.writeText(text);
showToast('Log kopyalandı', 'Panoya aktarıldı');
} catch {
prompt('Loglar:', text);
}
};
W.document.getElementById('kmf-clear').onclick = () => {
state.logs = [];
GM_setValue('kmf_logs', []);
updatePanel();
showToast('Log temizlendi', 'Kayıtlar silindi');
};
W.document.getElementById('kmf-enabled').onchange = e => {
setOpt('enabled', e.target.checked);
updatePanel();
};
W.document.getElementById('kmf-retry').onchange = e => {
let val = Number(e.target.value);
if (!Number.isFinite(val)) val = DEFAULTS.retryCount;
val = Math.max(1, Math.min(5, val));
setOpt('retryCount', val);
e.target.value = val;
};
W.document.getElementById('kmf-delay').onchange = e => {
let val = Number(e.target.value);
if (!Number.isFinite(val)) val = DEFAULTS.retryDelay;
val = Math.max(100, Math.min(3000, val));
setOpt('retryDelay', val);
e.target.value = val;
};
updatePanel();
}
function openPanel() {
createPanel();
state.panelOpen = true;
const overlay = W.document.getElementById('kmf-overlay');
if (overlay) overlay.classList.add('kmf-show');
updatePanel();
}
function closePanel() {
state.panelOpen = false;
const overlay = W.document.getElementById('kmf-overlay');
if (overlay) overlay.classList.remove('kmf-show');
}
function togglePanel() {
if (state.panelOpen) closePanel();
else openPanel();
}
function logsText() {
if (!state.logs.length) return 'Log yok';
return state.logs.map(x => {
let line = `${x.time} ${x.text}`;
if (x.data) line += `\n${JSON.stringify(x.data, null, 2)}`;
return line;
}).join('\n\n');
}
function updatePanel() {
if (!state.panelCreated) return;
const status = W.document.getElementById('kmf-status');
const last = W.document.getElementById('kmf-last');
const c403 = W.document.getElementById('kmf-403');
const fixed = W.document.getElementById('kmf-fixed');
const enabled = W.document.getElementById('kmf-enabled');
const retry = W.document.getElementById('kmf-retry');
const delay = W.document.getElementById('kmf-delay');
const logBox = W.document.getElementById('kmf-log');
if (status) status.textContent = opt('enabled') ? (state.retrying ? 'Deniyor' : 'Aktif') : 'Kapalı';
if (last) last.textContent = state.lastStatus || 'Yok';
if (c403) c403.textContent = String(GM_getValue('kmf_total403', 0));
if (fixed) fixed.textContent = String(GM_getValue('kmf_totalFixed', 0));
if (enabled) enabled.checked = !!opt('enabled');
if (retry) retry.value = opt('retryCount');
if (delay) delay.value = opt('retryDelay');
if (logBox) {
logBox.textContent = state.logs
.slice(-10)
.map(x => `${x.time} ${x.text}`)
.join('\n') || 'Log yok';
}
}
function bootPanelHotkey() {
W.document.addEventListener('keydown', e => {
if (e.altKey && e.shiftKey && e.key.toLowerCase() === 'k') {
e.preventDefault();
togglePanel();
}
});
}
GM_registerMenuCommand('Kick Mesaj Fix Paneli', togglePanel);
GM_registerMenuCommand('Kick Mesaj Fix Aç/Kapat', () => {
setOpt('enabled', !opt('enabled'));
showToast('Kick Mesaj Fix', opt('enabled') ? 'Aktif' : 'Kapalı');
updatePanel();
});
GM_registerMenuCommand('Kick Mesaj Fix Log Temizle', () => {
state.logs = [];
GM_setValue('kmf_logs', []);
updatePanel();
showToast('Log temizlendi', 'Kayıtlar silindi');
});
if (W.document.readyState === 'loading') {
W.document.addEventListener('DOMContentLoaded', () => {
ensureStyle();
bootPanelHotkey();
log('Script aktif');
});
} else {
ensureStyle();
bootPanelHotkey();
log('Script aktif');
}
})();