ControlC ControlC · Pastebin

Neochan extension

Pasted: Apr 23, 2026, 12:22:08 pm · Views: 59
// ==UserScript==
// @name Neochan extension
// @namespace http://tampermonkey.net/
// @version 1.4
// @description Расширение неочана
// @match https://neochan.org/*/*
// @match https://neochan.net/*/*
// @grant none
// ==/UserScript==

(function() {
'use strict';
console.log('DEBUGGG')

function resetPreviewFileBlock(fileBlock) {
// Удаляем добавленные элементы
fileBlock.querySelectorAll('.post-file-name-visible').forEach(el => el.remove());
fileBlock.querySelectorAll('.tiktok-icon').forEach(el => el.remove());

// Убираем флаг обработки
fileBlock.classList.remove('fnAdded');
}

function decodeHtml(str) {
const parser = new DOMParser();
const doc = parser.parseFromString(str, "text/html");
return doc.documentElement.textContent;
}
function processFiles() {
document.querySelectorAll('.post-file').forEach(fileBlock => {
const article = fileBlock.closest('article');
const isPreview = article && article.classList.contains('hover');

// Если это превью — всегда очищаем и обрабатываем заново
if (isPreview) {
if (fileBlock.dataset.fnPreviewAdded) return;
fileBlock.dataset.fnPreviewAdded = "1";
resetPreviewFileBlock(fileBlock);
} else {
if (fileBlock.dataset.fnAdded) return;
fileBlock.dataset.fnAdded = "1";
}


const link = fileBlock.querySelector('.post-file-link');
if (!link) return;

const originalName = decodeHtml(link.getAttribute('name'));
if (!originalName) return;

const infoDiv = link.getAttribute('data-func') !== 'PlayFirstEmbed' ? link.parentElement.querySelector('.post-file-info') : null;
if (!infoDiv) return;

// === Создаём блок с названием ===
const nameDiv = document.createElement('div');

// === Клик по названию файла → скачать оригинал ===
nameDiv.style.cursor = 'pointer';
nameDiv.addEventListener('click', () => {
const a = document.createElement('a');
a.href = link.href;
a.download = originalName;
document.body.appendChild(a);
a.click();
a.remove();
});

// === Правый клик → копировать имя файла ===
nameDiv.addEventListener('contextmenu', (e) => {
e.preventDefault();

navigator.clipboard.writeText(nameDiv.textContent)
.then(() => {
const toast = document.createElement('div');
toast.className = 'copy-toast';
toast.textContent = 'Скопировано!';

// Позиционируем рядом с элементом
const rect = nameDiv.getBoundingClientRect();
toast.style.left = rect.left + window.scrollX + 'px';
toast.style.top = rect.top + window.scrollY - 30 + 'px';

document.body.appendChild(toast);

// Плавное появление
requestAnimationFrame(() => {
toast.classList.add('visible');
});

// Удаление
setTimeout(() => {
toast.classList.remove('visible');
setTimeout(() => toast.remove(), 200);
}, 1000);
});
});


nameDiv.className = 'post-file-name-visible';
nameDiv.textContent = originalName;
nameDiv.title = originalName;

// Цвет как у post-file-info
const computed = getComputedStyle(infoDiv);
nameDiv.style.color = computed.color;

// Ширина как у превью
const preview = fileBlock.querySelector('img.preview');
if (preview) nameDiv.style.maxWidth = preview.width + 'px';

// Учитываем настройку
const showNames = localStorage.getItem('showFilenames') === '1';
nameDiv.style.display = showNames ? '' : 'none';

infoDiv.insertAdjacentElement('afterend', nameDiv);

// === TikTok ID ===
const showTikTok = localStorage.getItem('showTikTokIcons') === '1';
const match = originalName.match(/[67]\d{18}/);

if (match && showTikTok) {
const id = match[0];

const icon = document.createElement('span');
icon.className = 'tiktok-icon';
icon.title = "Открыть TikTok видео";
icon.style.cursor = 'pointer';
icon.style.marginLeft = '6px';
icon.style.opacity = '0.8';

// SVG иконка TikTok
icon.innerHTML = `
<svg width="16" height="16" viewBox="0 0 48 48">
<path fill="currentColor" d="M30 4h6c0 4 3 8 8 8v6c-4 0-8-2-11-5v17a14 14 0 1 1-14-14h2v6h-2a8 8 0 1 0 8 8V4z"/>
</svg>
`;

icon.addEventListener('click', () => {
window.open(`https://www.tiktok.com/@/video/${id}`, '_blank');
});

infoDiv.appendChild(icon);
}

// === GIF превью ===
const showGIFs = localStorage.getItem('showGIFs') === '1';
const href = link.getAttribute('href');

if (href?.endsWith('.gif') && showGIFs) {
const img = fileBlock.querySelector('img.preview');
if (img) {
const marker = fileBlock.querySelector('.post-gif-marker');
if (marker) marker.remove();

img.src = img.src
.replace('/thumb/', '/src/')
.replace(/\.\w+$/, '.gif');
}
}
});
}

function toggleFilenames(enabled) {
document.querySelectorAll('.post-file-name-visible').forEach(el => {
el.style.display = enabled ? '' : 'none';
});
}

function toggleGIFs(enabled) {
document.querySelectorAll('.post-file').forEach(fileBlock => {
const link = fileBlock.querySelector('.post-file-link');
if (!link) return;

const href = link.getAttribute('href');
if (!href || !href.endsWith('.gif')) return;

const img = fileBlock.querySelector('img.preview');
const marker = fileBlock.querySelector('.post-gif-marker');

if (enabled) {
if (marker) marker.style.display = 'none';

if (img) {
img.src = img.src
.replace('/thumb/', '/src/')
.replace(/\.\w+$/, '.gif');
}
} else {
if (marker) marker.style.display = '';

if (img) {
img.src = img.src
.replace('/src/', '/thumb/')
.replace(/\.gif$/, '.jpg');
}
}
});
}

function toggleTikTokIcons(enabled) {
document.querySelectorAll('.tiktok-icon').forEach(el => {
el.style.display = enabled ? '' : 'none';
});
}

function fixPlayerDownloadButton() {
const player = document.querySelector('#player-container');
if (!player) return;

const video = player.querySelector('video');
if (!video) return;

const source = video.querySelector('source');
if (!source) return;

const src = source.getAttribute('src');
if (!src) return;

// Ищем превью с таким же src
const previewLink = document.querySelector(`.post-file-link[href="${src}"]`);
if (!previewLink) return;

const originalName = previewLink.getAttribute('name');
if (!originalName) return;

// Ищем кнопку Plyr
const btn = player.querySelector('#plyr-download');
if (!btn) return;

// Заменяем действие кнопки
const parentButton = btn.closest('button');
if (!parentButton) return;

parentButton.onclick = (e) => {
e.stopPropagation();
e.preventDefault();

const a = document.createElement('a');
a.href = src;
a.download = originalName;
document.body.appendChild(a);
a.click();
a.remove();
};
}


function addOptionCheckbox() {
const options = document.querySelector('#options .options-tab');
if (!options || document.querySelector('#optionShowFilenames')) return;

// Заголовок секции
const header = document.createElement('div');
header.className = 'options-item';
header.style.marginTop = '10px';
header.innerHTML = `<span style="font-weight: bold; opacity: 0.8;">Расширение</span>`;
options.appendChild(header);

const hr = document.createElement('hr');
hr.style.margin = '5px 0 10px 0';
options.appendChild(hr);

// Чекбокс: имена файлов
const item1 = document.createElement('div');
item1.className = 'options-item';
item1.innerHTML = `
<label class="checktainer_xs">
Показывать имена файлов
<input type="checkbox" id="optionShowFilenames">
<span class="checkmark_xs"></span>
</label>
`;
options.appendChild(item1);

// Чекбокс: TikTok иконки
const item2 = document.createElement('div');
item2.className = 'options-item';
item2.innerHTML = `
<label class="checktainer_xs">
Показывать соус TikTok-ов
<input type="checkbox" id="optionShowTikTokIcons">
<span class="checkmark_xs"></span>
</label>
`;
options.appendChild(item2);

// Состояния
const showNames = localStorage.getItem('showFilenames') === '1';
const showTikTok = localStorage.getItem('showTikTokIcons') === '1';

const cb1 = document.querySelector('#optionShowFilenames');
const cb2 = document.querySelector('#optionShowTikTokIcons');

cb1.checked = showNames;
cb2.checked = showTikTok;

cb1.addEventListener('change', e => {
const state = e.target.checked;
localStorage.setItem('showFilenames', state ? '1' : '0');
toggleFilenames(state);
});

cb2.addEventListener('change', e => {
const state = e.target.checked;
localStorage.setItem('showTikTokIcons', state ? '1' : '0');
toggleTikTokIcons(state);
});

// Чекбокс: GIF → оригинал
const item3 = document.createElement('div');
item3.className = 'options-item';
item3.innerHTML = `
<label class="checktainer_xs">
Показывать GIF
<input type="checkbox" id="optionShowGIFs">
<span class="checkmark_xs"></span>
</label>
`;
options.appendChild(item3);

const showGIFs = localStorage.getItem('showGIFs') === '1';
const cb3 = document.querySelector('#optionShowGIFs');
cb3.checked = showGIFs;

cb3.addEventListener('change', e => {
const state = e.target.checked;
localStorage.setItem('showGIFs', state ? '1' : '0');
toggleGIFs(state);
});

// Чекбокс: рандомизация имён файлов
const item4 = document.createElement('div');
item4.className = 'options-item';
item4.innerHTML = `
<label class="checktainer_xs">
Удалять имена загружаемых файлов
<input type="checkbox" id="optionRandomizeFilenames">
<span class="checkmark_xs"></span>
</label>
`;
options.appendChild(item4);

const randomEnabled = localStorage.getItem('randomizeFilenames') === '1';
const cb4 = document.querySelector('#optionRandomizeFilenames');
cb4.checked = randomEnabled;

cb4.addEventListener('change', e => {
const state = e.target.checked;
localStorage.setItem('randomizeFilenames', state ? '1' : '0');
});
}

// Стили
const style = document.createElement('style');
style.textContent = `
.post-file-name-visible {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: smaller;
font-style: italic;
padding-right: 5px;
transition: filter 0.15s ease-in-out;
cursor: pointer;
}
.post-file-name-visible:hover {
transform: scale(1.05);
}
.tiktok-icon svg {
vertical-align: middle;
}
.copy-toast {
position: absolute;
padding: 6px 12px;
background: rgba(0, 0, 0, 0.75);
color: #fff;
border-radius: 6px;
font-size: 13px;
pointer-events: none;
opacity: 0;
transition: opacity 0.2s ease;
z-index: 9999;
}

.copy-toast.visible {
opacity: 1;
}
`;
document.head.appendChild(style);

const styleFix = document.createElement('style');
styleFix.textContent = `
.plyr__poster {
pointer-events: none !important;
}
`;
document.head.appendChild(styleFix);

// Запуск
processFiles();
toggleFilenames(localStorage.getItem('showFilenames') === '1');
toggleTikTokIcons(localStorage.getItem('showTikTokIcons') === '1');
toggleGIFs(localStorage.getItem('showGIFs') === '1');
addOptionCheckbox();

const observer = new MutationObserver(() => {
processFiles();
addOptionCheckbox();
fixPlayerDownloadButton();
});
observer.observe(document.body, { childList: true, subtree: true });
})();


function unixtimeFilename(ext) {
const ts = Math.floor(Date.now() / 1000); // unixtime
return ts + ext;
}

(function interceptXHR() {
const origSend = XMLHttpRequest.prototype.send;

XMLHttpRequest.prototype.send = function(body) {
try {
const enabled = localStorage.getItem('randomizeFilenames') === '1';

if (body instanceof FormData) {
const newForm = new FormData();

for (const [key, value] of body.entries()) {
if (value instanceof File) {

// 1. Определяем расширение
let ext = value.name.match(/\.[a-zA-Z0-9]+$/)?.[0] || '';

// 2. Если .mov → заменяем на .mp4 (всегда)
if (ext.toLowerCase() === '.mov') {
ext = '.mp4';
}

// 3. Выбираем имя файла
let newName;

if (enabled) {
// Рандомизация включена → генерируем новое имя
newName = unixtimeFilename(ext);
} else {
// Рандомизация выключена → просто меняем расширение
newName = value.name.replace(/\.[a-zA-Z0-9]+$/, ext);
}

// 4. Создаём новый файл
const newFile = new File([value], newName, { type: value.type });

newForm.append(key, newFile);
} else {
newForm.append(key, value);
}
}

return origSend.call(this, newForm);
}
} catch (e) {
console.error("Filename randomizer error:", e);
}

return origSend.call(this, body);
};
})();