Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- // ==UserScript==
- // @name Neochan extension
- // @namespace http://tampermonkey.net/
- // @version 1.5.2
- // @description Расширение неочана
- // @match https://neochan.org/*/*
- // @match https://neochan.net/*/*
- // @match https://neochan.cc/*/*
- // @grant none
- // ==/UserScript==
- (function() {
- 'use strict';
- 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;
- 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);
- 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';
- 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);
- }
- 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;
- const previewLink = document.querySelector(`.post-file-link[href="${src}"]`);
- if (!previewLink) return;
- const originalName = previewLink.getAttribute('name');
- if (!originalName) return;
- 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 filterThreads() {
- const enabled = localStorage.getItem('threadFilterEnabled') === '1';
- const rawFilter = (localStorage.getItem('threadFilterText') || '').trim();
- document.querySelectorAll('#index-thread-container .thread').forEach(thread => {
- thread.style.display = '';
- const next = thread.nextElementSibling;
- if (next && next.tagName === 'HR') {
- next.style.display = '';
- }
- if (!enabled || !rawFilter) return;
- let text = '';
- const subject = thread.querySelector('.post-subject');
- if (subject) text += ' ' + (subject.textContent || '');
- const messages = thread.querySelectorAll('.post_op .post-message');
- messages.forEach(msg => {
- text += ' ' + (msg.textContent || '');
- });
- let isMatch = false;
- const regexMatch = rawFilter.match(/^\/(.+)\/([gimsuy]*)$/);
- if (regexMatch) {
- try {
- const regex = new RegExp(regexMatch[1], regexMatch[2]);
- isMatch = regex.test(text);
- } catch (e) {
- isMatch = text.toLowerCase().includes(rawFilter.toLowerCase());
- }
- } else {
- isMatch = text.toLowerCase().includes(rawFilter.toLowerCase());
- }
- if (isMatch) {
- thread.style.display = 'none';
- if (next && next.tagName === 'HR') {
- next.style.display = 'none';
- }
- }
- });
- }
- 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);
- 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);
- });
- 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 item5 = document.createElement('div');
- item5.className = 'options-item';
- item5.style.display = 'flex';
- item5.style.alignItems = 'center';
- item5.style.gap = '8px';
- item5.style.flexWrap = 'wrap';
- item5.innerHTML = `
- <label class="checktainer_xs" style="margin-right: 4px;">
- Фильтр тредов
- <input type="checkbox" id="optionThreadFilter">
- <span class="checkmark_xs"></span>
- </label>
- <input type="text" id="optionThreadFilterText" placeholder="Текст для скрытия..."
- style="flex: 1; min-width: 120px; padding: 3px 6px; font-size: 12px; border-radius: 3px; border: 1px solid #666; background: #222; color: #eee;">
- `;
- options.appendChild(item5);
- const filterEnabled = localStorage.getItem('threadFilterEnabled') === '1';
- const filterText = localStorage.getItem('threadFilterText') || '';
- const cb5 = document.querySelector('#optionThreadFilter');
- const input5 = document.querySelector('#optionThreadFilterText');
- cb5.checked = filterEnabled;
- input5.value = filterText;
- cb5.addEventListener('change', e => {
- const state = e.target.checked;
- localStorage.setItem('threadFilterEnabled', state ? '1' : '0');
- filterThreads();
- });
- input5.addEventListener('input', e => {
- localStorage.setItem('threadFilterText', e.target.value);
- filterThreads();
- });
- filterThreads();
- }
- 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();
- filterThreads();
- const observer = new MutationObserver(() => {
- processFiles();
- addOptionCheckbox();
- fixPlayerDownloadButton();
- filterThreads();
- });
- observer.observe(document.body, { childList: true, subtree: true });
- })();
- (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) {
- let ext = value.name.match(/\.[a-zA-Z0-9]+$/)?.[0] || '';
- if (ext.toLowerCase() === '.mkv') {
- ext = '.mp4';
- }
- let newName;
- if (enabled) {
- newName = Date.now() - Math.round(Math.random() * 15768e7) + ext;
- } else {
- newName = value.name.replace(/\.[a-zA-Z0-9]+$/, ext);
- }
- 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);
- };
- })();
Advertisement
Add Comment
Please, Sign In to add comment