diff --git a/INFOS/GOG Links.md b/INFOS/GOG Links.md index 5775e0c..d7d5c2d 100644 --- a/INFOS/GOG Links.md +++ b/INFOS/GOG Links.md @@ -1,7 +1,8 @@ ### USE CASE: -------------- Lazy game discovery. This userscript will add 2 new buttons to Gog.com; one to search for non(voice)commented gameplay videos -on youtube to roughly judge gameplay, and one for searching that game on gog-games.to for download. +on youtube to roughly judge gameplay, and one for searching that game on torrminatorr forum for download. +ATTENTION: IT IS NOW REQUIRED TO HAVE A USER ON TORRMINATORR TO WORK WITH THIS EXTENSION. ### USAGE: --------------- diff --git a/INFOS/GOG-Games Links.md b/INFOS/GOG-Games Links.md index 464997b..7173420 100644 --- a/INFOS/GOG-Games Links.md +++ b/INFOS/GOG-Games Links.md @@ -1,35 +1,27 @@ ### USE CASE: -------------- -Direct discovery of games on gog-games. +~~Direct discovery of games on gog-games.~~ ### USAGE: --------------- -Just click the purple button to search for a gameplay video of the selected game without annoying youtubers -screaming and mumbling word salads or click the orange button to HOPEFULLY get a changelog FROM STEAM. +~~Just click the purple button to search for a gameplay video of the selected game without annoying youtubers +screaming and mumbling word salads or click the orange button to HOPEFULLY get a changelog FROM STEAM.~~ -DISCLAIMER: -The script will search the dedicated steam "news hub" for the game using DuckDuckGo (because changelog pages -use the game numeric id instead of game name) in an effort to show you a changelog of the game but there are many conditions -for this to serve its purpose: +~~Gog-games will go offline on September 6 2026~~, according to the site owner: +> PLEASE READ: It has been decided that all operations will cease (no new +games, updates, etc.) and the site will go offline in 90 days from this +message (September 6). This was a personal decision and not because of +any legal pressure. All remaining funds (around 700 EUR in XMR) will be +donated to torrminatorr. ❤️ A huge thank you to anyone who donated. Your funds will be going to a good home. ❤️ -1- It simply makes a search on DDG for the "steam news hub" for that specific game, you will have to click the result to get -there (usually "[GAMENAME] - Steam News Hub" result) +I cannot imagine the face of the donors ❤️❤️❤️ 🤣😂 -2- I've chosen "steam news hub" to check for changelogs since developers focus more on steam than on gog releases of -their games; in other words, __they usually don't create changelogs for gog released games.__ Gogdb changelogs are -mostly outdated for a lot of games. - -3- Steam changelogs may differ from gog release of the same game in some aspects. - -4- There may not be any changelogs or may be completely abandoned. - -5- We rely on the devs maintaining the changelogs, so check the version before assuming anything. ### WHY?: --------------- -Sometimes, you just start your search for "available" games on gog-games directly instead of gog.com. The problem is that gog-games didn't have the possibility to quickly search for gameplay videos and changelogs. This userscript comes to fix that. +~~Sometimes, you just start your search for "available" games on gog-games directly instead of gog.com. The problem is that gog-games didn't have the possibility to quickly search for gameplay videos and changelogs. This userscript comes to fix that.~~ --------------- -[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG-Games%20Links--.user.js) -You must have violentmonkey installed for this to work. +~~[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG-Games%20Links--.user.js) +You must have violentmonkey installed for this to work.~~ diff --git a/INFOS/ImageFap User Gallery Hider.md b/INFOS/ImageFap User Gallery Hider.md index 26c32b3..5aa5d2c 100644 --- a/INFOS/ImageFap User Gallery Hider.md +++ b/INFOS/ImageFap User Gallery Hider.md @@ -5,7 +5,7 @@ in the main gallery search results. A red "X" button will appear on the users av from you view forever. If you want to restore users open the code view of the userscript, then go to "values" and remove them (keep the format and commas). It is persistent so you don't have to do this each time you visit the site. To put it clear, the sense in doing this is because you are effectively removing users from your sight site-wide so even -if they uploaded more stuff on the site before or after, you will not see them again and all in one click! +if they uploaded more stuff, you will not see them again and all in one click! UPDATE: It will also hide galleries with less than 4 pictures in them automatically. @@ -25,16 +25,18 @@ UPDATE 2.2: Added back France and added Spain. Europe seems to be derailing very ### USAGE: --------------- -Just click the red X button on the avatar of the offender. After applying to a user or many in the page. -All instances (past and future) will not appear anymore in your searches. +Just click the red X button on the avatar of the offender. +All content of the user (past and future) will not appear anymore in your searches. You can also collapse or extend the left categories menu. ### WHY?: --------------- That site is a dump filled with weirdos trying to somehow convince people to become gay. The site doesn't offer -any blocking features only if you sign you get some filtering by tags, but the weirdos don't use tags or use -false ones to get exposition. The site doesn't mind that, it seems. So here is your tool to clean it. LGBT my ass, gender +any blocking features only if you sign in, you get some filtering by tags but the weirdos don't use tags or use +false ones to get exposition and the tags you can filter in the options are limited in numbers + the site loves +profiling users. +The site doesn't mind that. So here is your tool to clean it. LGBT my ass, gender dysphoria is not a virtue, nor normal. --------------- diff --git a/INFOS/NSFW.XXX_Downloader.md b/INFOS/NSFW.XXX_Downloader.md new file mode 100644 index 0000000..db4a4e6 --- /dev/null +++ b/INFOS/NSFW.XXX_Downloader.md @@ -0,0 +1,22 @@ +### USE CASE: +-------------- +One click download. + +### USAGE: +--------------- +Click on the green download button. + +UPDATE 3.1: Fixed the script for the changes in nsfw.xxx. Changed button size and color. + + +### WHY?: +--------------- +Just a helper. + +--------------- +[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/NSFW/NSFW.XXX%20Downloader.user.js) +You must have violentmonkey installed for this to work. + + + + diff --git a/INFOS/Pornolab_Post_Preview.md b/INFOS/Pornolab_Post_Preview.md new file mode 100644 index 0000000..3c2f59c --- /dev/null +++ b/INFOS/Pornolab_Post_Preview.md @@ -0,0 +1,23 @@ +### USE CASE: +-------------- +Lets you see the first post image in a medium sized window pop up while you are hovering the links of the posts. +This makes it easier to check what is inside without having to click each link. + +### USAGE: +--------------- +Just hover the mouse over the post link. + + +### WHY?: +--------------- +Lazy discovery of content. + + + +--------------- +[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Pornolab_Post_Preview.user.js) +You must have violentmonkey installed for this to work. + + + + diff --git a/INFOS/NSFW Button Downloaders.md b/INFOS/Realbooru_Downloader.md similarity index 73% rename from INFOS/NSFW Button Downloaders.md rename to INFOS/Realbooru_Downloader.md index b7b85ae..1889e91 100644 --- a/INFOS/NSFW Button Downloaders.md +++ b/INFOS/Realbooru_Downloader.md @@ -1,11 +1,12 @@ ### USE CASE: -------------- -Download individual files from this especific site. If you want massive downloading, use gallery-dl instead! +Download individual files from realbooru with a single click on a button. If you want massive downloading, use gallery-dl instead! ### USAGE: --------------- Just click the "Download" button. +1.8: Updated to work again. ### WHY?: --------------- diff --git a/INFOS/Steam Links.md b/INFOS/Steam Links.md index 61972a9..65d69dc 100644 --- a/INFOS/Steam Links.md +++ b/INFOS/Steam Links.md @@ -4,7 +4,7 @@ Lazy game discovery and acquisition on clean sites, using Steam as a catalog. ### USAGE: --------------- -On each game shop page, you will have 6 big green buttons, each will search for the game in +On each game shop page, you will have 5 big green buttons, each will search for the game in different known-to-be-safe sites, some for review, some for dl. It also has one link to search for non-commented youtube gameplay videos, and one for checking if the game has been "infected" with DEI policies by searching for it on deidetected.com. diff --git a/INFOS/Steam Search: Hide Games Under Minimum Price.md b/INFOS/Steam Search: Hide Games Under Minimum Price.md index 025735b..ab4c074 100644 --- a/INFOS/Steam Search: Hide Games Under Minimum Price.md +++ b/INFOS/Steam Search: Hide Games Under Minimum Price.md @@ -32,7 +32,7 @@ I created the [long-waited MINIMUM PRICE filter](https://www.reddit.com/r/Steam/ functionalities, I added further filters by using the reviews system. If you are thinking "why don't you simply use steamdb?" I answer you: steamdb has a hard-limit on tags you can filter, meaning it is also useless. -Enjoy browsing a clean, completely filtrable Steam catalog now! +Enjoy browsing a clean, completely filterable Steam catalog now! --------------- [INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--Steam%20Search-%20Hide%20Games%20Under%20Minimum%20Price--.user.js) diff --git a/INFOS/deepseek-answer-in-english.md b/INFOS/deepseek-answer-in-english.md new file mode 100644 index 0000000..5bcc430 --- /dev/null +++ b/INFOS/deepseek-answer-in-english.md @@ -0,0 +1,22 @@ +### USE CASE: +-------------- +Force Deepseek to use english. + +### USAGE: +--------------- +Just install it. A Checkbox will appear on the top right to turn ON or OFF this script. + + +### WHY?: +--------------- +Recently Deepseek started giving me a lot of answers in chinese (randomly and in the middle of an ongoing prompt). +This started randomly in very rare ocassions but now it has became an annoyance for the non-chinese speakers. So +with this dumb script we will append "answer in english" to every prompt. + +--------------- +[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/SFW/deepseek-answer-in-english.user.js) +You must have violentmonkey installed for this to work. + + + + diff --git a/INFOS/deepseek-concise.md b/INFOS/deepseek-concise.md new file mode 100644 index 0000000..8f321d1 --- /dev/null +++ b/INFOS/deepseek-concise.md @@ -0,0 +1,19 @@ +### USE CASE: +-------------- +Stop deepseek quackery and get to the point. + +### USAGE: +--------------- +Click the checkmark in the new box that appears on the upper right of the page. + +### WHY?: +--------------- +Deepseek is unbearable with its answers being walls of text and things nobody asked for that will quickly fill your context. +This script is very dumb, it only adds the phrase "be concise" at the end of every prompt you write after sending it, so +DS stops giving walls of useless text. + +--------------- +[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/deepseek-concise.user.js) +You must have violentmonkey installed for this to work. + + diff --git a/INFOS/reddtastic_downloader.md b/INFOS/reddtastic_downloader.md new file mode 100644 index 0000000..4f01dff --- /dev/null +++ b/INFOS/reddtastic_downloader.md @@ -0,0 +1,20 @@ +### USE CASE: +-------------- +Download individual files from reddtastic with a single click on a button. If you want massive downloading, use gallery-dl instead! + +### USAGE: +--------------- +Just click the "Download" button. + +1.4.9: Removed "IDM" references that never worked. Changed button color for better visibility. Made button smaller. + +### WHY?: +--------------- +To avoid downloading trash and focus on what we want. Also it is way more lazy than having to +follow each link and manually clicking "save as". + +--------------- +[INSTALL LINK](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Reddtastic%20Downloader.user.js) +You must have violentmonkey installed for this to work. + + diff --git a/NSFW/NSFW.XXX Downloader.user.js b/NSFW/NSFW.XXX Downloader.user.js index 19a5372..edfb0a0 100644 --- a/NSFW/NSFW.XXX Downloader.user.js +++ b/NSFW/NSFW.XXX Downloader.user.js @@ -1,9 +1,9 @@ // ==UserScript== // @name NSFW.XXX Downloader // @namespace NSFW.XXX-Downloader -// @version 3.0 +// @version 3.1 // @icon https://nsfw.xxx/favicon.ico -// @description Download full-resolution images/videos from NSFW.XXX posts +// @description Download full-resolution images/videos from NSFW.XXX // @author masterofobzene // @homepage https://github.com/masterofobzene/UserScriptRepo // @match https://nsfw.xxx/* @@ -13,6 +13,8 @@ // @grant GM_addStyle // @connect nsfw.xxx // @connect cdn2.nsfw.xxx +// @connect cdn3.nsfw.xxx +// @connect cdn4.nsfw.xxx // @connect * // @downloadURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/NSFW.XXX%20Downloader.user.js // @updateURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/NSFW.XXX%20Downloader.user.js @@ -21,15 +23,14 @@ (function() { 'use strict'; - // --- Button styles --- GM_addStyle(` .nsfw-download-btn { position: absolute !important; bottom: 10px !important; right: 10px !important; z-index: 99999 !important; - background: #4CAF50 !important; - color: white !important; + background: #19FF19 !important; + color: black !important; border: none !important; border-radius: 4px !important; padding: 5px 10px !important; @@ -38,72 +39,39 @@ opacity: 0.9 !important; transition: opacity 0.2s !important; } - .nsfw-download-btn:hover { - opacity: 1 !important; - } - .media-container { - position: relative !important; - } + .nsfw-download-btn:hover { opacity: 1 !important; } + .post { position: relative !important; } `); - // --- Inject buttons into each media container --- function addDownloadButtons() { - document.querySelectorAll('.sh-section__image, .sh-section__media, .post-media, .image-container').forEach(container => { - if (container.querySelector('.nsfw-download-btn')) return; + document.querySelectorAll('.post:not([data-nsfw-dl])').forEach(card => { + card.setAttribute('data-nsfw-dl', '1'); const btn = document.createElement('button'); btn.className = 'nsfw-download-btn'; - btn.textContent = '↓ Download'; + btn.textContent = 'DL↓'; btn.addEventListener('click', async e => { - e.preventDefault(); e.stopPropagation(); - await onDownloadClick(container, btn); + e.preventDefault(); + e.stopPropagation(); + await onDownloadClick(card, btn); }); - container.classList.add('media-container'); - container.appendChild(btn); + card.appendChild(btn); }); } - // --- Handle button click --- - async function onDownloadClick(container, btn) { + async function onDownloadClick(card, btn) { btn.disabled = true; btn.textContent = 'Finding source...'; try { - let url = await extractMediaUrl(container); - if (!url) throw new Error('No media source found'); + const postLink = card.querySelector('a.post--link'); + if (!postLink || !postLink.href) throw new Error('No post link found'); + const url = await fetchFullResFromPost(postLink.href); + if (!url) throw new Error('No media source found on post page'); await downloadFile(url, btn); } catch (err) { notifyError(err, btn); } } - // --- Extract the best media URL from the container --- - async function extractMediaUrl(container) { - // 1. Direct full-res image already in page? - let img = container.querySelector('img'); - if (img && img.src && !img.src.includes('/thumbnails/')) { - return img.src; - } - - // 2. If it's still a thumbnail, follow the post link - if (img && (img.src.includes('/thumbnails/') || img.dataset.src?.includes('/thumbnails/'))) { - const link = img.closest('a.slider_init_href'); - if (!link || !link.href) { - throw new Error('Cannot find post link for thumbnail'); - } - return await fetchFullResFromPost(link.href); - } - - // 3. Fallback: video preview container - const vid = container.querySelector('video source'); - if (vid && vid.src) return vid.src; - - // 4. Iframe or other embed - const iframe = container.querySelector('iframe'); - if (iframe && iframe.src) return iframe.src; - - return null; - } - - // --- Fetch the post page and scrape the real full-res media URL --- async function fetchFullResFromPost(postUrl) { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ @@ -113,17 +81,25 @@ try { const doc = new DOMParser().parseFromString(response.responseText, 'text/html'); - // Look *only* inside the .sh-section__image container for big images - const images = doc.querySelectorAll('.sh-section__image img'); - for (let img of images) { - const src = img.getAttribute('src') || img.dataset.src; - // Must be a real upload (not still thumbnail) - if (src && !src.includes('/thumbnails/') && src.includes('/uploads')) { + // 1. Look for an with /uploads in the src (not thumbnails) + for (const img of doc.querySelectorAll('img')) { + const src = img.getAttribute('src') || img.dataset.src || ''; + if (src.includes('/uploads') && !src.includes('/thumbnails/')) { return resolve(src); } } - // If no suitable image, try video + // 2. Check Vuetify background image on .v-image__image + const bgDiv = doc.querySelector('.v-image__image'); + if (bgDiv) { + const style = bgDiv.getAttribute('style') || ''; + const match = style.match(/url\("([^"]+)"\)/); + if (match && match[1] && match[1].includes('/uploads') && !match[1].includes('/thumbnails/')) { + return resolve(match[1]); + } + } + + // 3. Video source const videoSrc = doc.querySelector('video source')?.src; if (videoSrc) return resolve(videoSrc); @@ -132,14 +108,11 @@ reject(new Error('Error parsing post page HTML.')); } }, - onerror() { - reject(new Error('Failed to fetch post page.')); - } + onerror() { reject(new Error('Failed to fetch post page.')); } }); }); } - // --- Download via GM_download and update button state --- async function downloadFile(url, btn) { let filename = url.split('/').pop().split('?')[0].replace(/[^a-zA-Z0-9\.\-_]/g, '_'); btn.textContent = 'Downloading...'; @@ -148,32 +121,25 @@ onload() { btn.textContent = '✓ Done'; setTimeout(() => { - btn.textContent = '↓ Download'; + btn.textContent = 'DL↓'; btn.disabled = false; }, 2000); }, - onerror(err) { - notifyError(new Error(`Download failed: ${err.error}`), btn); - }, - ontimeout() { - notifyError(new Error('Download timed out'), btn); - } + onerror(err) { notifyError(new Error(`Download failed: ${err.error}`), btn); }, + ontimeout() { notifyError(new Error('Download timed out'), btn); } }); } - // --- Error handler --- function notifyError(err, btn) { console.error(err); GM_notification({ title: 'Download Error', text: err.message, timeout: 5000 }); - btn.textContent = '↓ Download'; + btn.textContent = 'DL↓'; btn.disabled = false; } - // --- Watch for dynamically loaded content --- - new MutationObserver(muts => muts.forEach(m => m.addedNodes.length && addDownloadButtons())) - .observe(document.body, { childList: true, subtree: true }); + new MutationObserver(muts => { + for (const m of muts) if (m.addedNodes.length) addDownloadButtons(); + }).observe(document.body, { childList: true, subtree: true }); - // --- Initial run --- addDownloadButtons(); - })(); diff --git a/NSFW/Pornolab_Post_Preview.user.js b/NSFW/Pornolab_Post_Preview.user.js new file mode 100644 index 0000000..853d41c --- /dev/null +++ b/NSFW/Pornolab_Post_Preview.user.js @@ -0,0 +1,417 @@ +// ==UserScript== +// @name Pornolab Post Preview +// @namespace PornolabPreview +// @version 1.0 +// @description Shows first real post image when hovering topic links +// @author masterofobzene +// @match *://pornolab.net/forum/* +// @grant GM_xmlhttpRequest +// @grant GM_addStyle +// @connect * +// @run-at document-idle +// @icon https://pornolab.net/favicon.ico +// @downloadURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Pornolab_Post_Preview.user.js +// @updateURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Pornolab_Post_Preview.user.js +// ==/UserScript== + +(() => { + 'use strict'; + + console.log('[HoverPreview] Initialized'); + + const CACHE = new Map(); + const REQUESTS = new Map(); + + const POPUP_WIDTH = 480; + const POPUP_HEIGHT = 360; + const HOVER_DELAY = 250; + + let hoverTimer = null; + let currentAnchor = null; + + GM_addStyle(` + #vm-hover-preview { + position: fixed; + z-index: 999999; + display: none; + width: ${POPUP_WIDTH}px; + background: rgba(20,20,20,0.97); + border: 1px solid #555; + border-radius: 8px; + box-shadow: 0 8px 30px rgba(0,0,0,0.7); + overflow: hidden; + padding: 8px; + pointer-events: none; + } + + #vm-hover-preview img { + width: 100%; + height: auto; + max-height: ${POPUP_HEIGHT}px; + object-fit: contain; + display: block; + border-radius: 4px; + } + + #vm-hover-preview .vm-msg { + color: #ddd; + font-family: sans-serif; + font-size: 14px; + text-align: center; + padding: 30px 10px; + } + `); + + const popup = document.createElement('div'); + + popup.id = 'vm-hover-preview'; + + document.body.appendChild(popup); + + function isTopicLink(anchor) { + if (!anchor?.href) { + return false; + } + + try { + const url = new URL(anchor.href); + + return ( + url.pathname.includes('/forum/viewtopic.php') && + url.searchParams.has('t') + ); + } catch { + return false; + } + } + + function showMessage(message, event) { + popup.innerHTML = `
${message}
`; + popup.style.display = 'block'; + + if (event) { + positionPopup(event); + } + } + + function hidePopup() { + popup.style.display = 'none'; + popup.innerHTML = ''; + } + + function positionPopup(event) { + const margin = 20; + + let left = event.clientX + margin; + let top = event.clientY + margin; + + if (left + POPUP_WIDTH > window.innerWidth) { + left = event.clientX - POPUP_WIDTH - margin; + } + + if (top + POPUP_HEIGHT > window.innerHeight) { + top = window.innerHeight - POPUP_HEIGHT - margin; + } + + popup.style.left = `${Math.max(10, left)}px`; + popup.style.top = `${Math.max(10, top)}px`; + } + + function normalizeUrl(url, base) { + try { + return new URL(url, base).href; + } catch { + return null; + } + } + + function extractFirstImage(html, pageUrl) { + try { + const parser = new DOMParser(); + + const doc = parser.parseFromString(html, 'text/html'); + + /* + REAL PORNOLAB STRUCTURE: + + + + + Source verified from post-source.txt + */ + + const vars = [ + ...doc.querySelectorAll('var.postImg') + ]; + + console.log( + '[HoverPreview] postImg count:', + vars.length + ); + + for (const node of vars) { + const title = node.getAttribute('title'); + + if (!title) { + continue; + } + + const imageUrl = normalizeUrl(title, pageUrl); + + if (!imageUrl) { + continue; + } + + const lower = imageUrl.toLowerCase(); + + // Ignore ads/logos/etc + if ( + lower.includes('logo') || + lower.includes('banner') || + lower.includes('avatar') || + lower.includes('ads') || + lower.includes('smile') || + lower.includes('static.pornolab') + ) { + continue; + } + + console.log( + '[HoverPreview] Selected post image:', + imageUrl + ); + + return imageUrl; + } + + console.warn('[HoverPreview] No postImg found'); + + return null; + } catch (err) { + console.error( + '[HoverPreview] Failed extracting image:', + err + ); + + return null; + } + } + + async function fetchTopicImage(url) { + if (CACHE.has(url)) { + return CACHE.get(url); + } + + if (REQUESTS.has(url)) { + return REQUESTS.get(url); + } + + const promise = new Promise((resolve, reject) => { + console.log('[HoverPreview] Fetching topic:', url); + + GM_xmlhttpRequest({ + method: 'GET', + url, + anonymous: false, + timeout: 15000, + + headers: { + Referer: location.href + }, + + onload: response => { + try { + if (response.status !== 200) { + reject( + new Error( + `HTTP ${response.status}` + ) + ); + + return; + } + + const imageUrl = extractFirstImage( + response.responseText, + url + ); + + CACHE.set(url, imageUrl); + + resolve(imageUrl); + } catch (err) { + reject(err); + } finally { + REQUESTS.delete(url); + } + }, + + onerror: err => { + REQUESTS.delete(url); + + reject(err); + }, + + ontimeout: () => { + REQUESTS.delete(url); + + reject(new Error('Timeout')); + } + }); + }); + + REQUESTS.set(url, promise); + + return promise; + } + + async function fetchImageBlob(url) { + return new Promise((resolve, reject) => { + console.log('[HoverPreview] Fetching image:', url); + + GM_xmlhttpRequest({ + method: 'GET', + url, + responseType: 'blob', + anonymous: false, + timeout: 15000, + + headers: { + Referer: location.href + }, + + onload: response => { + try { + if (response.status !== 200) { + reject( + new Error( + `Image HTTP ${response.status}` + ) + ); + + return; + } + + const blobUrl = URL.createObjectURL( + response.response + ); + + resolve(blobUrl); + } catch (err) { + reject(err); + } + }, + + onerror: reject, + + ontimeout: () => { + reject(new Error('Image timeout')); + } + }); + }); + } + + async function handleHover(anchor, event) { + try { + const topicUrl = anchor.href; + + showMessage('Loading preview...', event); + + const imageUrl = await fetchTopicImage(topicUrl); + + if (currentAnchor !== anchor) { + return; + } + + if (!imageUrl) { + showMessage('No image found', event); + + return; + } + + const blobUrl = await fetchImageBlob(imageUrl); + + if (currentAnchor !== anchor) { + URL.revokeObjectURL(blobUrl); + + return; + } + + popup.innerHTML = ''; + + const img = document.createElement('img'); + + img.onload = () => { + console.log('[HoverPreview] Image displayed'); + }; + + img.onerror = err => { + console.error( + '[HoverPreview] Failed displaying image:', + err + ); + + showMessage('Failed displaying image', event); + }; + + img.src = blobUrl; + + popup.appendChild(img); + + popup.style.display = 'block'; + + positionPopup(event); + } catch (err) { + console.error('[HoverPreview] Hover failed:', err); + + showMessage('Failed loading image', event); + } + } + + document.addEventListener('mouseover', event => { + const anchor = event.target.closest('a'); + + if (!isTopicLink(anchor)) { + return; + } + + currentAnchor = anchor; + + clearTimeout(hoverTimer); + + hoverTimer = setTimeout(() => { + handleHover(anchor, event); + }, HOVER_DELAY); + }); + + document.addEventListener('mousemove', event => { + if (popup.style.display === 'block') { + positionPopup(event); + } + }); + + document.addEventListener('mouseout', event => { + const anchor = event.target.closest('a'); + + if (!anchor || anchor !== currentAnchor) { + return; + } + + clearTimeout(hoverTimer); + + currentAnchor = null; + + hidePopup(); + }); + + window.addEventListener( + 'scroll', + () => { + hidePopup(); + }, + { passive: true } + ); + + console.log('[HoverPreview] Ready'); +})(); diff --git a/NSFW/Realbooru Downloader.user.js b/NSFW/Realbooru Downloader.user.js index 8a254c3..aaa829b 100644 --- a/NSFW/Realbooru Downloader.user.js +++ b/NSFW/Realbooru Downloader.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name Realbooru Downloader // @namespace Realbooru-Downloader -// @version 1.7 +// @version 1.8 // @icon https://realbooru.com/favicon.ico // @author masterofobzene // @homepage https://github.com/masterofobzene/UserScriptRepo @@ -29,7 +29,7 @@ border-radius: 3px; cursor: pointer; font-weight: bold; - font-size: 10px; + font-size: 17px; z-index: 9999; `; @@ -38,29 +38,59 @@ const pathname = new URL(url).pathname; return decodeURIComponent(pathname.substring(pathname.lastIndexOf('/') + 1)) || 'file'; } catch (e) { - console.error('❌ Error parsing filename from URL:', url, e); + console.error('❌ Error parsing filename:', url, e); return 'file'; } } - function extractFullMediaUrl(html) { + function extractFullMediaUrl(html, baseUrl) { const doc = new DOMParser().parseFromString(html, 'text/html'); + const base = baseUrl || doc.baseURI; - const image = doc.querySelector('#image'); - if (image?.src) return image.src; + const img = doc.querySelector('#image'); + if (img?.src) return new URL(img.getAttribute('src'), base).href; - const video = doc.querySelector('video source'); - if (video?.src) return video.src; + const videoSrc = doc.querySelector('video source'); + if (videoSrc?.src) return new URL(videoSrc.getAttribute('src'), base).href; - const meta = doc.querySelector('meta[property="og:video"]'); - if (meta?.content) return meta.content; + const metaImg = doc.querySelector('meta[property="og:image"]'); + if (metaImg?.content) return new URL(metaImg.content, base).href; + const metaVid = doc.querySelector('meta[property="og:video"]'); + if (metaVid?.content) return new URL(metaVid.content, base).href; - const ogImage = doc.querySelector('meta[property="og:image"]'); - if (ogImage?.content) return ogImage.content; + const origLink = doc.querySelector('a[href*="/images/"]'); + if (origLink) return new URL(origLink.getAttribute('href'), base).href; return null; } + function downloadFile(url, filename, referer) { + GM_xmlhttpRequest({ + method: 'GET', + url: url, + responseType: 'blob', + headers: { 'Referer': referer }, + onload: function (resp) { + if (resp.status === 200) { + const blob = resp.response; + const blobUrl = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = blobUrl; + a.download = filename; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + URL.revokeObjectURL(blobUrl); + } else { + console.error('❌ Download failed, status:', resp.status); + } + }, + onerror: function (err) { + console.error('❌ Download error:', err); + } + }); + } + function createDownloadButton(postUrl) { const btn = document.createElement('button'); btn.textContent = '⬇'; @@ -70,7 +100,6 @@ btn.addEventListener('click', async (e) => { e.preventDefault(); e.stopPropagation(); - console.log(`🔄 Fetching: ${postUrl}`); try { const response = await new Promise((resolve, reject) => { @@ -82,20 +111,13 @@ }); }); - const fullUrl = extractFullMediaUrl(response.responseText); + const fullUrl = extractFullMediaUrl(response.responseText, postUrl); if (!fullUrl) { - console.error('❌ Media URL not found'); + console.error('❌ Could not extract media URL.'); return; } - const filename = getFilenameFromUrl(fullUrl); - GM_download({ - url: fullUrl, - name: filename, - saveAs: false, - onerror: (err) => console.error('❌ Download error:', err), - onload: () => console.log('✅ Downloaded:', filename), - }); + downloadFile(fullUrl, getFilenameFromUrl(fullUrl), postUrl); } catch (err) { console.error('❌ Failed to fetch post page:', err); } @@ -117,19 +139,10 @@ selectors.forEach(selector => { document.querySelectorAll(selector).forEach(element => { - // Find the main post container const container = element.closest('div.thumb, article.thumbnail-preview, div.thumbnail-container') || element; - - // Skip if container already has a button if (container.querySelector('.realbooru-download-button')) return; - - // Find the post link - const link = element.href ? element : - element.querySelector('a[href*="page=post&s=view"]'); - + const link = element.href ? element : element.querySelector('a[href*="page=post&s=view"]'); if (!link || !link.href) return; - - // Prepare container and add button container.style.position = 'relative'; const btn = createDownloadButton(link.href); container.appendChild(btn); @@ -140,10 +153,8 @@ if (count > 0) console.log(`✅ Added ${count} download button(s).`); } - // Initial run setTimeout(addButtons, 1000); - // MutationObserver for dynamic content new MutationObserver(addButtons).observe(document.body, { childList: true, subtree: true diff --git a/NSFW/Reddtastic Downloader.user.js b/NSFW/Reddtastic Downloader.user.js index d1a73a8..6cae203 100644 --- a/NSFW/Reddtastic Downloader.user.js +++ b/NSFW/Reddtastic Downloader.user.js @@ -1,16 +1,12 @@ // ==UserScript== // @name Reddtastic Downloader -// @namespace Reddtastic-Downloader -// @homepage https://github.com/masterofobzene/UserScriptRepo -// @author masterofobzene -// @version 1.4.8 -// @description Shows a download button for each thumbnail to download the full res file. +// @namespace https://reddtastic.com/ +// @version 1.4.9 +// @description Shows a button for downloading media on each thumbnail. // @match https://reddtastic.com/* // @icon https://reddtastic.com/favicon.ico // @grant GM_download // @grant GM_xmlhttpRequest -// @grant GM_registerMenuCommand -// @connect * // @downloadURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Reddtastic%20Downloader.user.js // @updateURL https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Reddtastic%20Downloader.user.js // ==/UserScript== @@ -27,7 +23,7 @@ top: 5px; right: 5px; z-index: 9999; padding: 4px 8px; - background: rebeccapurple; + background: #FF00FF; color: white; font-size: 12px; font-weight: bold; @@ -39,14 +35,7 @@ `; document.head.appendChild(style); - GM_registerMenuCommand("Set Download Method", function () { - const method = prompt("Choose download method:\n1. IDM (recommended)\n2. Firefox Native", "1"); - localStorage.setItem('rdDownloadMethod', method === "2" ? "firefox" : "idm"); - }); - - function getDownloadMethod() { - return localStorage.getItem('rdDownloadMethod') || 'idm'; - } + // ---- removed: GM_registerMenuCommand & getDownloadMethod ---- function randStr(len = 8) { return Math.random().toString(36).substr(2, len); @@ -70,40 +59,18 @@ }); } - function triggerIDMDownload(url, filename) { - const a = document.createElement('a'); - a.href = url; - a.download = filename || url.split('/').pop() || `reddit_${randStr()}.jpg`; - a.style.display = 'none'; - document.body.appendChild(a); - a.click(); - setTimeout(() => document.body.removeChild(a), 100); - } + // ---- removed: triggerIDMDownload ---- function doDownload(url, defaultName) { - const method = getDownloadMethod(); - - if (method === 'firefox') { - GM_download({ - url: url, - name: defaultName, - saveAs: false, - onerror: () => { - console.warn('GM_download failed, falling back to IDM click method.'); - triggerIDMDownload(url, defaultName); - } - }); - } else { - GM_download({ - url: url, - name: defaultName, - saveAs: false, - onerror: () => { - console.warn('GM_download failed, falling back to fake link click.'); - triggerIDMDownload(url, defaultName); - } - }); - } + // Always use native GM_download – fallback to IDM removed + GM_download({ + url: url, + name: defaultName, + saveAs: false, + onerror: () => { + console.warn('GM_download failed for:', url); + } + }); } function makeBtn(parent, onClick) { @@ -112,8 +79,8 @@ parent.classList.add('vm-dl-wrap'); const btn = document.createElement('button'); btn.className = 'vm-dl-btn'; - btn.textContent = 'Download (IDM)'; - btn.title = 'Download with IDM'; + btn.textContent = 'DL⬇'; // changed from "Download (IDM)" + btn.title = 'Download'; // changed from "Download with IDM" btn.addEventListener('click', e => { e.stopPropagation(); e.preventDefault(); diff --git a/README.md b/README.md index d47a8e0..12840a4 100644 --- a/README.md +++ b/README.md @@ -10,22 +10,25 @@ Supports: ## INSTALL INDEX You must have violentmonkey, tampermonkey or another script manager for these to work. -Click on the script name to install it. All scripts here were made for Firefox/Librewolf. +Click on the script name to install it. All scripts here were made for Firefox/Librewolf but you can try them on Chrome/Brave and see if they work. ### SFW ----------- -[::GOG Links::](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG%20Links--.user.js) Adds links for GOG store to gameplay videos without youtubers comments and direct search on Gog-games for free clean download. +[::GOG Links::](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG%20Links--.user.js) Adds links for GOG store to gameplay videos without youtubers comments and direct search on Torrminatorr for free clean download. - +GeaabI9Wys [DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/GOG%20Links.md) ----------- -[::GOG-Games Links::](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG-Games%20Links--.user.js) Adds "Gameplay Video" and "Changelog" buttons per game card to search for no-commentary gameplay videos and if lucky, a changelog. +~~[::GOG-Games Links::](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--GOG-Games%20Links--.user.js)~~ ~~Adds "Gameplay Video" and "Changelog" buttons per game card to search for no-commentary gameplay videos and if lucky, a changelog.~~ + +Gog-games admin says he is not updating the site anymore and will close the site on September 6/2026. RIP gog-games.to 👋, now GET YOUR torrminatorr account created (free) and use the other scripts that are updated for it to search for your games! 😁 + +so long gog-games - [DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/GOG-Games%20Links.md) @@ -34,7 +37,8 @@ Click on the script name to install it. All scripts here were made for Firefox/L [::Steam Links::](https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/--Steam%20Links--.user.js) Lets you easily search the games on clean DL sites, watch gameplays without youtuber's comments and see if the game is woke-oriented. - +E9MtE02Pbt + [DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/Steam%20Links.md) @@ -99,7 +103,7 @@ above if you see prices in other currency. ----------- -[Steam Bad Reviews](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/SFW/Steam_Bad_Reviews.user.js) Automatically switches to "bad reviews" when visiting the game page so you can judge the game a little bit better. +[Steam Bad Reviews](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/SFW/Steam_Bad_Reviews.user.js) Automatically switches to "negative reviews" when visiting the game page so you can judge the game a little bit better. @@ -163,6 +167,28 @@ above if you see prices in other currency. ----------- +[DeepSeek be concise](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/SFW/deepseek-concise.user.js) Makes DeepSeek give concise answers instead of walls of text. + +e65aw4NRcw + + + +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/deepseek-concise.md) + +----------- + + +[DeepSeek always in english](https://github.com/masterofobzene/UserScriptRepo/raw/refs/heads/main/SFW/deepseek-answer-in-english.user.js) Makes DeepSeek give answers in english instead of randomly using chinese. + +2MepdZsKOX + + + + +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/deepseek-answer-in-english.md) + +----------- +


@@ -174,24 +200,17 @@ above if you see prices in other currency. [NSFW.XXX Downloader](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/NSFW.XXX%20Downloader.user.js) Download full-resolution images/videos from NSFW.XXX posts -[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/NSFW%20Button%20Downloaders.md) +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/NSFW.XXX_Downloader.md) ----------- [Realbooru Downloader](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Realbooru%20Downloader.user.js) Download full-resolution images and videos from Realbooru posts with a small button -[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/NSFW%20Button%20Downloaders.md) +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/Realbooru_Downloader.md) ----------- [Reddtastic Downloader](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Reddtastic%20Downloader.user.js) Shows a download button for each thumbnail to download the full res file. -[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/NSFW%20Button%20Downloaders.md) - ------------ -~~Successor of zzup~~ - -~~[up2img Infinite Scroll](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/ZZup%20Infinite%20Scroll.user.js)~~ ~~Repaginates up2img.com so you get "infinite scroll"~~ - -~~[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/ZZup%20Infinite%20Scroll.md)~~ +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/reddtastic_downloader.md) ----------- [Pornpaw Gallery Filter](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/ZZup%20Infinite%20Scroll.user.js) Lets you filter galleries by using words that can be found in any the title or the tags of the galleries @@ -239,6 +258,14 @@ NO it doesn't do what you are thinking. [DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/Heavy-R_Tag_Hider.md) +----------- + +[Pornolab post preview](https://github.com/masterofobzene/UserScriptRepo/raw/main/NSFW/Pornolab_Post_Preview.user.js +) Shows the first post image in a dynamic popup while hovering on a post link, so you can preview the contents without having to click the link. + +[DESCRIPTION](https://github.com/masterofobzene/UserScriptRepo/blob/main/INFOS/Pornolab_Post_Preview.md) + +


@@ -256,10 +283,18 @@ Description: Select text -> right click -> "search this" -> searches those words +
+
+
+
+-------------------------- - +> [!TIP] +> - azRggiVKee – This button is **not** for installing anything. +> - g41eGfc03s – This button is to show that you **like** this repo. +> - fIVz9FY5CQ – This button is to **get spam** every time I modify something. diff --git a/SFW/--GOG Links--.user.js b/SFW/--GOG Links--.user.js index 0a55ebb..cca96c2 100644 --- a/SFW/--GOG Links--.user.js +++ b/SFW/--GOG Links--.user.js @@ -2,8 +2,8 @@ // @name ::GOG Links:: // @namespace masterofobzene // @author masterofobzene -// @version 1.9 -// @description Adds links for GOG store to gameplay videos without youtubers comments and direct search on Gog-games for free clean download. +// @version 2.0 +// @description Adds links for GOG store to gameplay videos without youtubers comments and direct search on Torrminatorr for free clean download. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @match *://www.gog.com/*/game/* // @homepage https://github.com/masterofobzene/UserScriptRepo @@ -17,9 +17,9 @@ var pirateLinks = [ { - url: "https://gog-games.to/?search=", + url: "https://forum.torrminatorr.com/search.php?keywords=", urlSpecial: "", - title: "Search on GoG-Games" + title: "Search on Torrminatorr" }, { url: "https://www.youtube.com/results?search_query=", diff --git a/SFW/--Steam Links--.user.js b/SFW/--Steam Links--.user.js index 48031f3..25566b3 100644 --- a/SFW/--Steam Links--.user.js +++ b/SFW/--Steam Links--.user.js @@ -1,7 +1,7 @@ // ==UserScript== // @name ::Steam Links:: // @namespace Steam-Links -// @version 2.2 +// @version 2.3 // @icon https://cdn.freebiesupply.com/images/large/2x/steam-logo-black-transparent.png // @description Lets you easily search the games on clean DL sites, watch gameplays without youtuber's comments and see if the game is woke-oriented. // @author masterofobzene @@ -32,11 +32,10 @@ // Sites configuration const sites = [ - { name: 'GOG Games', url: `https://gog-games.to/?search=${encodeURIComponent(gameName)}`}, { name: 'CS.RIN.RU', url: `https://cs.rin.ru/forum/search.php?keywords=${encodeURIComponent(gameName)}` + '&terms=any&author=&sc=1&sf=titleonly&sk=t&sd=d&sr=topics&st=0&ch=300&t=0&submit=Search'}, { name: 'Torrminatorr', url: `https://forum.torrminatorr.com/search.php?keywords=${encodeURIComponent(gameName)}` }, { name: 'FitGirl Repacks', url: `https://fitgirl-repacks.site/?s=${encodeURIComponent(gameName)}` }, - { name: 'DElDETECTED', url: `https://deidetected.com/games/?search=${encodeURIComponent(gameName)}` }, + { name: 'DEIDETECTED', url: `https://deidetected.com/games/?search=${encodeURIComponent(gameName)}` }, { name: 'YouTube (No Commentary)', url: `https://www.youtube.com/results?search_query=${encodeURIComponent(gameName + ' no commentary')}` } ]; diff --git a/SFW/Steam_Block_Games.user.js b/SFW/Steam_Block_Games.user.js index 00a6f25..55ee769 100644 --- a/SFW/Steam_Block_Games.user.js +++ b/SFW/Steam_Block_Games.user.js @@ -1,10 +1,10 @@ // ==UserScript== // @name Steam Search Block Games // @namespace Steam_Search_Block_Games -// @version 1.1 +// @version 1.2 // @author masterofobzene // @description Block button to hide individual games. -// @match https://store.steampowered.com/search/* +// @match https://store.steampowered.com/search* // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand diff --git a/SFW/deepseek-answer-in-english.user.js b/SFW/deepseek-answer-in-english.user.js new file mode 100644 index 0000000..0679abb --- /dev/null +++ b/SFW/deepseek-answer-in-english.user.js @@ -0,0 +1,178 @@ +// ==UserScript== +// @name DeepSeek Answer in English +// @namespace DeepSeek_Answer_In_English +// @version 1.0 +// @description Appends "answer in english" to the prompt before sending and provides an enable/disable toggle. +// @author masterofobzene +// @match *://*.deepseek.com/* +// @grant GM_getValue +// @grant GM_setValue +// @run-at document-idle +// @license GNU GPLv3 +// @icon https://deepseek.com/favicon.ico +// @downloadURL https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/deepseek-answer-in-english.user.js +// @updateURL https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/deepseek-answer-in-english.user.js +// ==/UserScript== + +(() => { + 'use strict'; + + console.log('[Answer in English] Script loaded'); + + let enabled = GM_getValue('answerInEnglishEnabled', true); + const SUFFIX = ' answer in english'; + + function appendSuffixToInput() { + try { + if (!enabled) { + return; + } + + const input = + document.querySelector('textarea') || + document.querySelector('[contenteditable="true"]'); + + if (!input) { + console.warn('[Answer in English] Input not found'); + return; + } + + const currentText = + input instanceof HTMLTextAreaElement + ? input.value + : input.textContent || ''; + + if (!currentText.trim()) { + return; + } + + if (currentText.endsWith(SUFFIX)) { + return; + } + + const newText = currentText + SUFFIX; + + if (input instanceof HTMLTextAreaElement) { + const descriptor = Object.getOwnPropertyDescriptor( + HTMLTextAreaElement.prototype, + 'value' + ); + + if (descriptor?.set) { + descriptor.set.call(input, newText); + } else { + input.value = newText; + } + } else { + input.textContent = newText; + } + + input.dispatchEvent( + new InputEvent('input', { + bubbles: true, + cancelable: true + }) + ); + + input.dispatchEvent( + new Event('change', { + bubbles: true + }) + ); + + console.log('[Answer in English] Prompt modified'); + } catch (error) { + console.error('[Answer in English]', error); + } + } + + document.addEventListener( + 'keydown', + event => { + try { + if (!enabled) { + return; + } + + if (event.key !== 'Enter') { + return; + } + + if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) { + return; + } + + appendSuffixToInput(); + } catch (error) { + console.error('[Answer in English]', error); + } + }, + true + ); + + function createToggle() { + try { + if (!document.body) { + setTimeout(createToggle, 500); + return; + } + + if (document.getElementById('answer-in-english-toggle')) { + return; + } + + const container = document.createElement('div'); + container.id = 'answer-in-english-toggle'; + + Object.assign(container.style, { + position: 'fixed', + top: '60px', + right: '10px', + zIndex: '2147483647', + background: 'rgba(255,255,255,0.95)', + border: '1px solid #ccc', + borderRadius: '8px', + padding: '8px 12px', + boxShadow: '0 2px 10px rgba(0,0,0,0.15)', + fontFamily: 'system-ui, sans-serif', + fontSize: '13px', + color: '#000' + }); + + container.innerHTML = ` + + `; + + document.body.appendChild(container); + + const checkbox = container.querySelector('#answer-in-english-checkbox'); + + checkbox.addEventListener('change', event => { + enabled = event.target.checked; + GM_setValue('answerInEnglishEnabled', enabled); + + console.log('[Answer in English] Enabled:', enabled); + }); + + console.log('[Answer in English] Toggle added'); + } catch (error) { + console.error('[Answer in English]', error); + } + } + + createToggle(); + + const observer = new MutationObserver(() => { + if (!document.getElementById('answer-in-english-toggle')) { + createToggle(); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); +})(); diff --git a/SFW/deepseek-concise.user.js b/SFW/deepseek-concise.user.js new file mode 100644 index 0000000..a8fc653 --- /dev/null +++ b/SFW/deepseek-concise.user.js @@ -0,0 +1,178 @@ +// ==UserScript== +// @name DeepSeek Be Concise +// @namespace DeepSeek_Be_Concise +// @version 1.0 +// @description Appends "be concise" to the prompt before sending and provides an enable/disable toggle. +// @author masterofobzene +// @match *://*.deepseek.com/* +// @grant GM_getValue +// @grant GM_setValue +// @run-at document-idle +// @license GNU GPLv3 +// @icon https://deepseek.com/favicon.ico +// @downloadURL https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/deepseek-concise.user.js +// @updateURL https://github.com/masterofobzene/UserScriptRepo/raw/main/SFW/deepseek-concise.user.js +// ==/UserScript== + +(() => { + 'use strict'; + + console.log('[Be Concise] Script loaded'); + + let enabled = GM_getValue('beConciseEnabled', true); + const SUFFIX = ' be concise'; + + function appendSuffixToInput() { + try { + if (!enabled) { + return; + } + + const input = + document.querySelector('textarea') || + document.querySelector('[contenteditable="true"]'); + + if (!input) { + console.warn('[Be Concise] Input not found'); + return; + } + + const currentText = + input instanceof HTMLTextAreaElement + ? input.value + : input.textContent || ''; + + if (!currentText.trim()) { + return; + } + + if (currentText.endsWith(SUFFIX)) { + return; + } + + const newText = currentText + SUFFIX; + + if (input instanceof HTMLTextAreaElement) { + const descriptor = Object.getOwnPropertyDescriptor( + HTMLTextAreaElement.prototype, + 'value' + ); + + if (descriptor?.set) { + descriptor.set.call(input, newText); + } else { + input.value = newText; + } + } else { + input.textContent = newText; + } + + input.dispatchEvent( + new InputEvent('input', { + bubbles: true, + cancelable: true + }) + ); + + input.dispatchEvent( + new Event('change', { + bubbles: true + }) + ); + + console.log('[Be Concise] Prompt modified'); + } catch (error) { + console.error('[Be Concise]', error); + } + } + + document.addEventListener( + 'keydown', + event => { + try { + if (!enabled) { + return; + } + + if (event.key !== 'Enter') { + return; + } + + if (event.shiftKey || event.ctrlKey || event.altKey || event.metaKey) { + return; + } + + appendSuffixToInput(); + } catch (error) { + console.error('[Be Concise]', error); + } + }, + true + ); + + function createToggle() { + try { + if (!document.body) { + setTimeout(createToggle, 500); + return; + } + + if (document.getElementById('be-concise-toggle')) { + return; + } + + const container = document.createElement('div'); + container.id = 'be-concise-toggle'; + + Object.assign(container.style, { + position: 'fixed', + top: '10px', + right: '10px', + zIndex: '2147483647', + background: 'rgba(255,255,255,0.95)', + border: '1px solid #ccc', + borderRadius: '8px', + padding: '8px 12px', + boxShadow: '0 2px 10px rgba(0,0,0,0.15)', + fontFamily: 'system-ui, sans-serif', + fontSize: '13px', + color: '#000' + }); + + container.innerHTML = ` + + `; + + document.body.appendChild(container); + + const checkbox = container.querySelector('#be-concise-checkbox'); + + checkbox.addEventListener('change', event => { + enabled = event.target.checked; + GM_setValue('beConciseEnabled', enabled); + + console.log('[Be Concise] Enabled:', enabled); + }); + + console.log('[Be Concise] Toggle added'); + } catch (error) { + console.error('[Be Concise]', error); + } + } + + createToggle(); + + const observer = new MutationObserver(() => { + if (!document.getElementById('be-concise-toggle')) { + createToggle(); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); +})();