From dd67eeb241565216b52ca089a9795051d0fa1b87 Mon Sep 17 00:00:00 2001 From: Stian Date: Wed, 1 Apr 2026 13:47:05 +0200 Subject: [PATCH] Update files --- Cmder/config/user_aliases.cmd | 5 +- Default.rdp | Bin 2304 -> 1151 bytes ...ll-Menu Settings.xml => Menu Settings.xml} | 12 +- Vivaldi/mods/CSS/custom.css | 19 +- Vivaldi/mods/JS/tab-cycler-close.js | 469 ++++++++++++++++++ Vivaldi/mods/custom.css | 43 +- Vivaldi/themes/Stians-Theme.zip | Bin 358097 -> 357863 bytes .../Override Client Hints API - Basic.user.js | 52 ++ .../Override Client Hints API.user.js | 92 ++++ 9 files changed, 636 insertions(+), 56 deletions(-) rename Diverse/Classic Start Menu/{Open-Shell-Menu Settings.xml => Menu Settings.xml} (94%) create mode 100755 Vivaldi/mods/JS/tab-cycler-close.js create mode 100755 Vivaldi/userscript/Override Client Hints API - Basic.user.js create mode 100755 Vivaldi/userscript/Override Client Hints API.user.js diff --git a/Cmder/config/user_aliases.cmd b/Cmder/config/user_aliases.cmd index a41da6c..6563592 100755 --- a/Cmder/config/user_aliases.cmd +++ b/Cmder/config/user_aliases.cmd @@ -33,12 +33,10 @@ nginx=start /d d:\bin\Nginx /b d:\bin\Nginx\nginx.exe $* nmap=D:\bin\nettools\Nmap\nmap.exe $* nping=D:\bin\nettools\Nmap\nping.exe $* npp=d:\bin\Notepad++\notepad++.exe $* -pb=sh d:\bin\cygwin\usr\local\bin\pb $* putty=d:\bin\putty\putty.exe $* pwd=cd python=d:\bin\python\python.exe $* rm=rm -i $* -shn=curl -F "format=simple" -F "url=$1" "https://is.gd/create.php" sublime=d:\bin\Sublime\sublime_text.exe $* sudo=gsudo $* tc=d:\bin\totalcmd\totalcmd.exe $* @@ -48,3 +46,6 @@ tshark=d:\bin\Wireshark\tshark.exe $* unalias=alias /d $1 vi=vim $* winscp=d:\bin\WinSCP\winscp.exe $* +pb=curl -sF "files[]=@$*" "https://qu.ax/upload.php" $B jq -r '.files[].url' +shn=curl -F "format=simple" -F "url=$1" "https://is.gd/create.php" +cb=curl -s -F "reqtype=fileupload" -F "fileToUpload=@$*" "https://catbox.moe/user/api.php" diff --git a/Default.rdp b/Default.rdp index e58ba462fe333116bfa5fd142a23b4db6e452839..9c93738bd20a0cdcb3d9d2b2752888a3e0acfd62 100755 GIT binary patch literal 1151 zcmZ8hTW;ep4E!(9JLmxY=(Y=Vk=xJ`6AP9^L8RmQ_Djl-q(#0wLs6U=F_021E+9`r z)p^R5I7*~)iI?}+7wGhria5?hGRAT{7{OLR(D8^VUHGEabBN@j%G@whz`fGY=T8~2 zNc3RWxP*61TKHkdg&KM)1%2nRz>qK~`{(s#fD)D%WVEt?qBT1aB}4V#N37U5vTWxz z)+9A#-Xv+Cr~a~5oAK-QWjA2NcpI(l*N2MV!G5VV`}k*IWE;x)xiK}zC~|soaMw8{ zx5N_6+6LyBUVN|_%j}@U%s+9pC8J?aw=5b!1(kKeHNpCjFn}@Ie3RS)0|Yf_-0C-U zp*C>^kEgjC+sA|u%$}a#P2*f(9U4v8RMqZ}&a*SVVX*8VG1J_B4l%7H*M`k+x!WAgYhzQ>=Y3h4tDKwhd~s$9SN+ zQ~~eNAMu{Jx~yxoKZFD23{AXh{)I0%ShTj9{l-j!P;1&(>9Jz0TCg|+Tr|VgHQsi> z4YexpX^8EuGqW^mx2dAG_Vi{(m**rgAO8e~90S_kGPXN5*K_KL-eW+r*ExWn_E~gH XT?19m(zNgU+vnT6{%n)p`ilJr4s4W@ literal 2304 zcmai$YmVAL5QOJomTi`B`6TsqGt3M)8zE$D_oJq|x|=`0 zmtn8ZF`PpcR-p-F*o8^=z3yvW5Bhb~)2*Iv^{WdDT^Hdwd=KA3?!C6Rll(?`9rc+) zE88w?9(*Qc8uVrn?_{-moTkYoU2mW1cTDwA6`KW{o0JPi+(jC4u`Q?GDPYv$)QGsR=(t=~G)&s_(tT`N?s# zs)ErG{V+Y1zOAfKGJONL|H9Q9uC?k!Z=U2sed&C(LX_BVa|pk(cT_w1*W#4Z{}K<6 zc;Eznj>W)+XuaUDg$W$XAk#i(`V;bIM`Fv7yMr57~87^7WN6T=x;i?+#Yk=W>hph>5$6& zL0`!;s@C9yl>YnG}<1BQ_t1noyxXO{PX6L^%Ffn&V!V-7k(yQ+6rFj zXmiiFZ+V~0gKb9CmU<*@e=5S+__yFXXP(tkPRWb2$&Tn`$qZl$&=K(c9G=3PzFhgf l= - + + @@ -15,7 +16,9 @@ + + @@ -31,8 +34,7 @@ CENTER_NAME=0 SMALL_ICONS=0 LARGE_FONT=0 - ICON_FRAMES=1 - OPAQUE=0 + DISABLE_MASK=1 @@ -44,9 +46,9 @@ OPAQUE=0 + - - + diff --git a/Vivaldi/mods/CSS/custom.css b/Vivaldi/mods/CSS/custom.css index 8cc90b3..0348cc4 100755 --- a/Vivaldi/mods/CSS/custom.css +++ b/Vivaldi/mods/CSS/custom.css @@ -13,6 +13,11 @@ grid-template-columns: unset; } +/* Width of empty spacer before tabs */ +#browser #tabs-tabbar-container .toolbar-tabbar-before { + min-width: 0px; +} + /* Skinnier mainbar */ .mainbar .toolbar-mainbar.toolbar-addressbar, .mainbar .toolbar-mainbar .button-toolbar button { @@ -63,18 +68,13 @@ } /* Hide AddressField stuff */ -.UrlBar-AddressField .permission-popup, +.UrlBar-AddressField .permission-popup.is-blocking, .UrlBar-AddressField .UrlBar-UrlObfuscationWarning, .UrlBar-AddressField .ContentBlocker-Control, .UrlBar-AddressField .ToolbarButton-Button[title^="Translate"] { display: none; } -/* AddressField show allowed permission buttons */ -.UrlBar-AddressField .permission-popup.is-allowing { - display: flex; -} - /* AddressField URL scheme part */ .UrlFragment--Lowlight:first-of-type { color: var(--colorFgFadedMost); @@ -96,7 +96,7 @@ } /* Extensions toggle button */ -.toolbar-extensions .ToolbarButton-Button[name="Extensions"] .button-icon { +.toolbar-extensions .ExtensionToggleIcon .button-icon { opacity: 0.5 !important; } @@ -181,8 +181,3 @@ .overlayinfobubble { display: none; } - -/* Width of empty spacer before tabs */ -.tabs-top #tabs-tabbar-container .toolbar-tabbar-before:empty { - min-width: 0px; -} diff --git a/Vivaldi/mods/JS/tab-cycler-close.js b/Vivaldi/mods/JS/tab-cycler-close.js new file mode 100755 index 0000000..b9356f0 --- /dev/null +++ b/Vivaldi/mods/JS/tab-cycler-close.js @@ -0,0 +1,469 @@ +// ==UserScript== +// @name Mod: Use Tab cycler to close multiple tabs at once +// @description Mark multiple tabs with checkboxes as you navigate the Tab Cycler, then close them all at once. +// @version 1.0 +// @author barbudo2005 +// @match *://*/* +// ==/UserScript== + +(function() { + 'use strict'; + + let cyclerActive = false; + let markedTabIds = new Set(); + let processedTabs = new Set(); + let focusObserver = null; + + console.log('🚀 Tab Cycler Batch Close v1.0 loaded'); + + // M key - toggle checkbox on highlighted tab + window.addEventListener('keydown', function(e) { + if (!cyclerActive) return; + + if (e.code === 'Space' && e.ctrlKey) { + e.preventDefault(); + e.stopPropagation(); + + const activeLi = document.querySelector('.tabswitcher.list li.selected'); + if (!activeLi) return; + + const checkbox = activeLi.querySelector('.tab-checkbox'); + if (!checkbox) return; + + // Toggle checkbox + if (checkbox.checked) { + checkbox.checked = false; + checkbox.removeAttribute('checked'); + } else { + checkbox.checked = true; + checkbox.setAttribute('checked', ''); + } + + const tabId = parseInt(checkbox.dataset.tabId); + + if (checkbox.checked) { + markedTabIds.add(tabId); + } else { + markedTabIds.delete(tabId); + } + + return false; + } + }, true); + + // CTRL or ALT keyup - batch close + document.addEventListener('keyup', function(e) { + if (e.key === 'Control' || e.key === 'Alt') { + if (markedTabIds.size === 0) return; + executeBatchClose(); + } + }, true); + + function executeBatchClose() { + const idsToClose = Array.from(markedTabIds); + + chrome.tabs.remove(idsToClose, () => { + if (chrome.runtime.lastError) { + console.error('❌ Batch close error:', chrome.runtime.lastError); + } + markedTabIds.clear(); + }); + } + + // Polling + setInterval(function() { + const cycler = document.querySelector('.tabswitcher.list'); + + if (cycler) { + if (!cyclerActive) { + cyclerActive = true; + processedTabs.clear(); + markedTabIds.clear(); + applyCustomStyles(); + setupFocusObserver(cycler); + } + injectCheckboxes(cycler); + } else if (cyclerActive) { + cyclerActive = false; + processedTabs.clear(); + + // Batch close when cycler closes (any method) + if (markedTabIds.size > 0) { + executeBatchClose(); + } + + // Cleanup observer + if (focusObserver) { + focusObserver.disconnect(); + focusObserver = null; + } + } + }, 500); + + function setupFocusObserver(cycler) { + if (focusObserver) return; + + focusObserver = new MutationObserver(() => { + const selectedLi = cycler.querySelector('li.selected'); + if (selectedLi) { + const checkbox = selectedLi.querySelector('.tab-checkbox'); + if (checkbox && document.activeElement !== checkbox) { + checkbox.focus(); + } + } + }); + + focusObserver.observe(cycler, { + attributes: true, + attributeFilter: ['class'], + subtree: true + }); + } + + function applyCustomStyles() { + if (document.getElementById('tab-cycler-custom-styles')) return; + + const style = document.createElement('style'); + style.id = 'tab-cycler-custom-styles'; + style.textContent = ` + .tabswitcher.list li.active-page { + filter: brightness(1.4) !important; + } + + .tab-checkbox { + appearance: none; + -webkit-appearance: none; + width: 13px !important; + height: 13px !important; + min-width: 13px !important; + min-height: 13px !important; + border: 1px solid var(--colorFgFadedMost) !important; + border-radius: 2px !important; + background: transparent !important; + cursor: pointer !important; + position: absolute !important; + right: 6px !important; + top: 50% !important; + transform: translateY(-50%) !important; + z-index: 99999 !important; + transition: all 0.15s ease !important; + pointer-events: auto !important; + } + + .tab-checkbox:hover { + border-color: var(--colorFgFaded) !important; + } + + /* Override Vivaldi's checkbox ::before and ::after */ + input[type=checkbox].tab-checkbox::before, + input[type=checkbox].tab-checkbox::after { + display: none !important; + content: none !important; + } + + .tab-checkbox:checked { + border-color: var(--colorFg) !important; + background-color: var(--colorHighlightBg) !important; + } + + /* Our custom checkmark - more specific selector */ + + input[type=checkbox].tab-checkbox:checked::after { + content: '✓' !important; + display: block !important; + color: var(--colorFg) !important; + font-size: 10px !important; + font-weight: bold !important; + position: absolute !important; + top: -1px !important; + left: 1px !important; + transform: none !important; + background: none !important; + } + `; + document.head.appendChild(style); + } + + function injectCheckboxes(cycler) { + const tabItems = cycler.querySelectorAll('ul.listed-tabs > li.visual-list'); + + chrome.tabs.query({currentWindow: true}, (allTabs) => { + tabItems.forEach((li) => { + if (processedTabs.has(li)) return; + if (li.querySelector('.tab-checkbox')) { + processedTabs.add(li); + return; + } + + let matchingTab = null; + + // Get URL from favicon + const favicon = li.querySelector('.visual-tab-list-favicon'); + + if (favicon && favicon.srcset) { + const match = favicon.srcset.match(/chrome:\/\/favicon\/size\/\d+\/(.+?)\s/); + if (match) { + const tabUrl = match[1]; + matchingTab = allTabs.find(t => t.url === tabUrl); + } + } + + if (!matchingTab) { + processedTabs.add(li); + return; + } + + li.style.position = 'relative'; + li.style.display = 'flex'; + li.style.alignItems = 'center'; + li.style.gap = '8px'; + li.style.paddingRight = '25px'; + li.style.minWidth = '0'; + + const textNodes = Array.from(li.childNodes).filter(node => node.nodeType === Node.TEXT_NODE); + textNodes.forEach(textNode => { + if (textNode.textContent.trim()) { + const span = document.createElement('span'); + span.textContent = textNode.textContent; + span.style.overflow = 'hidden'; + span.style.textOverflow = 'ellipsis'; + span.style.whiteSpace = 'nowrap'; + span.style.flex = '1'; + span.style.minWidth = '0'; + span.style.cursor = 'pointer'; + + span.addEventListener('mousedown', function(e) { + chrome.tabs.update(matchingTab.id, {active: true}); + + if (markedTabIds.size > 0) { + executeBatchClose(); + } + }, true); + + li.replaceChild(span, textNode); + } + }); + + if (favicon) { + favicon.style.flexShrink = '0'; + } + + const checkbox = document.createElement('input'); + checkbox.type = 'checkbox'; + checkbox.className = 'tab-checkbox'; + checkbox.dataset.tabId = matchingTab.id; + + checkbox.addEventListener('change', function() { + const tabId = parseInt(checkbox.dataset.tabId); + + if (checkbox.checked) { + markedTabIds.add(tabId); + } else { + markedTabIds.delete(tabId); + } + }); + + // Block propagation to prevent tab activation + checkbox.addEventListener('mousedown', function(e) { + e.stopPropagation(); + e.stopImmediatePropagation(); + }, true); + + checkbox.addEventListener('mouseup', function(e) { + e.stopPropagation(); + e.stopImmediatePropagation(); + }, true); + + checkbox.addEventListener('click', function(e) { + e.stopPropagation(); + e.stopImmediatePropagation(); + }, true); + + li.appendChild(checkbox); + processedTabs.add(li); + }); + }); + } + + console.log('✅ Active'); +})(); + + +/*****************************************************************************************************************/ + +(async () => { + 'use strict'; + + const config = { + // タブスタックにベースドメインを使用する (true: 有効, false: 無効) + // Use the base domain for tab stacks (true: enabled, false: disabled) + base_domain: true, + + // タブスタックの名前を自動的に変更する (0: 無効, 1: ホスト名を使用, 2: ベースドメインから生成) + // Automatically change the name of the tab stack (0: disabled, 1: use hostname, 2: generate from base domain) + rename_stack: 0, + + // 自動タブスタックを許可するワークスペース (完全一致もしくは ) + // * 未設定の場合はすべてのワークスペースで自動タブスタックを許可する + // Workspaces that allow automatic tab stacking (exact match or ) + // * If not set, automatic tab stacking is allowed in all workspaces + allow_workspaces: [ + // "", + // "Shopping", + ], + + // 自動タブスタックを許可するドメイン (完全一致もしくは正規表現) + // * 未設定の場合はすべてのドメインで自動タブスタックを許可する + // Domains that allow automatic tab stacking (exact match or regular expression) + // * If not set, automatic tab stacking is allowed for all domains + allow_domains: [ + // "www.example.com", + // /^(.+\.)?example\.net$/, + ], + + // 自動タブスタックから除外するドメイン (完全一致もしくは正規表現) + // Domains to exclude from automatic tab stacking (exact match or regular expression) + block_domains: [ + // "www.example.com", + // /^(.+\.)?example\.net$/, + ], + }; + + const mergeArrays = (...arrays) => [...new Set(arrays.flat())]; + + const getUrlFragments = (url) => vivaldi.utilities.getUrlFragments(url); + + const getBaseDomain = (url) => { + const {hostForSecurityDisplay, tld} = getUrlFragments(url); + return hostForSecurityDisplay.match(`([^.]+\\.${ tld })$`)?.[1] || hostForSecurityDisplay; + }; + + const getHostname = (url) => { + const {hostForSecurityDisplay} = getUrlFragments(url); + return config.base_domain ? getBaseDomain(url) : hostForSecurityDisplay; + }; + + const matchHostRule = (url, rule) => { + const {hostForSecurityDisplay} = getUrlFragments(url); + return rule instanceof RegExp ? rule.test(hostForSecurityDisplay) : hostForSecurityDisplay === rule; + }; + + const getTab = async (tabId) => { + const tab = await chrome.tabs.get(tabId); + + if (tab.vivExtData) { + tab.vivExtData = JSON.parse(tab.vivExtData); + return tab; + } + }; + + const getTabIndex = async (tabId) => (await getTab(tabId)).index; + + const getWorkspaceName = async (workspaceId) => { + if (!workspaceId) { + return ''; + } + const workspaceList = await vivaldi.prefs.get('vivaldi.workspaces.list'); + return workspaceList.find(item => item.id === workspaceId).name; + }; + + const getTabsByWorkspace = async () => { + const tabs = (await chrome.tabs.query({ currentWindow: true })) + .filter(tab => tab.id !== -1 && tab.vivExtData) + .map(tab => Object.assign(tab, { vivExtData: JSON.parse(tab.vivExtData) })) + .filter(tab => !tab.pinned && !tab.vivExtData.panelId) + .filter(tab => !config.allow_domains.length || config.allow_domains.find(rule => matchHostRule(tab.url, rule))) + .filter(tab => !config.block_domains.length || !config.block_domains.find(rule => matchHostRule(tab.url, rule))); + + return Object.groupBy(tabs, tab => tab.vivExtData.workspaceId); + }; + + const getTabsByStack = (tabs) => Object.groupBy(tabs, tab => tab.vivExtData.group); + + const getTabsByHost = (tabs) => Object.groupBy(tabs, tab => getHostname(tab.url)); + + const getMaxTabsStackId = (tabsByStack, targetHost) => { + const counts = {}; + + for (const [stackId, tabs] of Object.entries(tabsByStack)) { + if (stackId !== 'undefined') { + const tabsByHost = getTabsByHost(tabs); + const count = tabsByHost[targetHost]?.length || 0; + + delete tabsByHost[targetHost]; + counts[stackId] = Object.values(tabsByHost) + .reduce((acc, tabs) => { + return acc > tabs.length ? acc : 0; + }, count); + } + } + + return Object.entries(counts) + .reduce((acc, [stackId, count]) => { + return acc[1] < count ? [stackId, count] : acc; + }, [, 0])[0]; + }; + + const getTabStackName = (url) => { + let stackName; + + switch (config.rename_stack) { + case 1: + stackName = getHostname(url); + break; + case 2: + stackName = getBaseDomain(url).split('.')[0]; + stackName = stackName.charAt(0).toUpperCase() + stackName.slice(1); + break; + } + return stackName; + }; + + const addTabStack = async (tabId, stackId, stackName) => { + const {vivExtData} = await getTab(tabId); + + if (stackName) { + vivExtData.fixedGroupTitle = stackName; + } + vivExtData.group = stackId; + chrome.tabs.update(tabId, { vivExtData: JSON.stringify(vivExtData) }); + }; + + const stackingTabs = async (workspaceId) => { + const workspaceName = await getWorkspaceName(workspaceId); + + if (!config.allow_workspaces.length || config.allow_workspaces.includes(workspaceName)) { + const tabsByWorkspace = await getTabsByWorkspace(); + const tabsByStack = getTabsByStack(tabsByWorkspace[workspaceId]); + const tabsByHost = getTabsByHost(tabsByWorkspace[workspaceId]); + + for (const [host, tabs] of Object.entries(tabsByHost)) { + const targetStackId = getMaxTabsStackId(tabsByStack, host) || crypto.randomUUID(); + const targetStackTabs = tabsByStack[targetStackId] ? getTabsByHost(tabsByStack[targetStackId])[host] : []; + const targetTabs = mergeArrays(targetStackTabs, tabs); + const targetStackName = getTabStackName(tabs[0].pendingUrl || tabs[0].url); + + let tabIndex = await getTabIndex(targetTabs[0].id); + + for (const tab of targetTabs) { + addTabStack(tab.id, targetStackId, targetStackName); + chrome.tabs.move(tab.id, { index: tabIndex }); + tabIndex++; + } + } + } + }; + + chrome.webNavigation.onCommitted.addListener(async details => { + if (details.tabId !== -1) { + const tab = await getTab(details.tabId); + + if (tab && !tab.pinned && !tab.vivExtData.panelId && details.frameType === 'outermost_frame') { + const workspaceId = tab.vivExtData.workspaceId; + stackingTabs(workspaceId); + } + } + }); +})(); + + diff --git a/Vivaldi/mods/custom.css b/Vivaldi/mods/custom.css index 408c2f9..de57d31 100755 --- a/Vivaldi/mods/custom.css +++ b/Vivaldi/mods/custom.css @@ -13,6 +13,11 @@ grid-template-columns: unset; } +/* Width of empty spacer before tabs */ +#browser #tabs-tabbar-container .toolbar-tabbar-before { + min-width: 0px; +} + /* Skinnier mainbar */ .mainbar .toolbar-mainbar.toolbar-addressbar, .mainbar .toolbar-mainbar .button-toolbar button { @@ -42,34 +47,6 @@ border-top: unset !important; } -/* SiteInfoButton */ -.SiteInfoButton { - opacity: unset !important; - height: 22px; - width: 22px; -} - -/* SiteInfo V logo */ -.SiteInfoButton.internal svg path, .SiteInfoButton.warning svg path { - d: path("M10.4 5c-.4-.8 0-1.8 1-2 .7 0 1.5.4 1.6 1.2a1.4 1.4 0 0 1-.2 1l-4 7c-.3.5-.7.8-1.2.8-.6 0-1-.2-1.3-.7L3.8 7.8 2.2 5c-.5-.8 0-2 1-2 .7 0 1 .2 1.4.7l1 2 1 1.4a2 2 0 0 0 1.7 1.5 2.2 2.2 0 0 0 2.3-2V6c0-.3 0-.6-.2-1z"); -} - -/* SiteInfo old style lock */ -.SiteInfoButton.secure svg path, .SiteInfoButton.certified svg path { - d: path("M12.2 6.7C12.2 4 10.5 2 8 2 5.8 2 3.8 4 3.8 6.7V7H3v7h10V7h-.8v-.3zM10.7 8H5.3V6.7c0-1.7 1.4-3 2.8-3 1.7 0 2.8 1.3 2.8 3V8z"); - fill: #00a100; -} - -/* SiteInfo secure background colour */ -.SiteInfoButton.secure, .SiteInfoButton.certified { - background-color: #004400 !important; -} - -/* SiteInfo hide EV/Not Secure text */ -.SiteInfoButton .siteinfo-text { - display: none; -} - /* Remove panels switch top padding */ #switch { padding-top: unset; @@ -124,7 +101,7 @@ } /* Extensions toggle button */ -.toolbar-extensions .ToolbarButton-Button[name="Extensions"] .button-icon { +.toolbar-extensions .ExtensionToggleIcon .button-icon { opacity: 0.5 !important; } @@ -210,12 +187,4 @@ display: none; } -/* Width of empty spacer before tabs */ -.tabs-top #tabs-tabbar-container .toolbar-tabbar-before:empty { - min-width: 0px; -} -/* TEMP FIX FOR TAB BAR */ -#tabs-tabbar-container.top { - padding-top: unset; -} diff --git a/Vivaldi/themes/Stians-Theme.zip b/Vivaldi/themes/Stians-Theme.zip index 76f7ec0da68fcfa12668fd2e8930f126c8d113a0..ad08545ca8d1fcf4ce01927befb0f32af584e532 100755 GIT binary patch delta 3908 zcmZuz2|SePAAb#x7>se>XYO1pt%SlZ#u+(sGr}0hjQdJW32m{ZV?CtWEhD8?s>KT1 zwPkg%QYo9zZf&xT{&d(j{hyil9lO)Vd_M2T*Z28-f4}eVx!$*bQt8hLC5pedsF($Y zNnn^`KmS5?d{RHU)CO|@o|vzypTSv$Msqf z6p`w=y8)VgRofj?^rSp%N_V8`+3KdR^>k;*(K2V=j(ui#BBpA5G}085kG}isu(7_~ zpS|3u!@b_J@W$clZ3)z%Q-AHWco6hR=CVnnayxlsS4(n8yrhMQxN`aC*OzHm7AQM3 zJ{;)s+09C+k9vG}Tt?8p1iyb+EdgHQO_xc62YA!5WROcVH6#UIknr&v6flU7ACm=F z@o~tI14B6JCJ%NHqT32Uj(}r|U?%}nl>wW8BPt-2fDUS)kAM^n@ErlqYl1ies%nEw z0`AcP4+)s82WoL&ZIT8+f?$;l7Ymyi0S|)BF$OmY_=PEmCSbi8m>}RmbI?LSUrW$K zKy_=NMwGC#0r~_RwGx;TaJQ|%B%E0VCh=()`y5yk<4UW+5CLcH!9PTvFV60<5P=1_CluVr#~ES3jnFrTg9QSfj|Arl zm=q1l3HUY!>>=PTI*`N89Zt-nT?(6gu7iS+Szc=i+F&HKR)2 zA5C9&*mEA-;Uqj*C%Q3Ge)wWyz5L1joux^J+3!O_yfTd5UwFdal*Fd=_%U97)M#&; ztgUo@*!Ak(+;JuCIWfgBa$KEr@I_{y{Wb4kN!uMMN!>H_n-je?QdaM7%T{F4{!H{g zv?j;#pJ&k3$kVCFqu?Ql8=jtl?Mv?6xAujz%jh>MoW!SdmsA>;bvz4mmwB2jyG7J> zQvT5d_P?LY_j~^kv|RMNfiw^C%XT$RyA6!rxAynW%g59OMT(t2bh$LXUbko1U{lE$ zLtlYGJ)&?UX2a~gb{lC+lX%L>XJ95<*}=HNO3(e}W%r%^c3W-6!pp9#s+xOI>CV~C znqOYOmVG;&b632^VXcMLHv4s*`dkz4t-PtyALPl<>e!FcN2g7@yDAeK`_#wc6~CVk zzAJ0w%52f{drYpJnp*X@)LfBslNK$qt|_(ZY%n&_nBG8oxW0I&p=QDTwJJxlL`I$$ z&Xpvv+*1lY|8|?NHi<~nDiF8fX7%QQucEG9^pj|thMcNacj|K=m*W>td@ol#DaEae zFfOs8YTY|Yed9HETsxwsi$8Wi@!+*^;}TBn<6<4vWpbPKv+8BZ3#X%1w~T=<$xg?-;R|X- zmV^1aXCx;$E37(QsGdLR(qw-BWu?FARhzKD*DZTZq7OAct=Cy|o_m^dgdAJV(qo+q zFQ{%EIy6Yu(LdOH<~LH)n_qPj$mEW?!>66*c9b76*UL!ivym+OIG|ai;MbmS+wiL0 zG?wF3ah%=X_LBx%zkiGE*P{mKPcE;1G2;2hH}Up{VXRtCS4T&8c46^7jkCLCzujro z-(0@Z@N%lv=7sgI6wDf*PmV`77`BgsUKaapysWL^4n@Vsjh$w3;w4Xh;_OwP?SDGH z9&rOt+ zUl->OQ!C{!cuZE%HXKy6+ckRd#)+3J0>Z+_^#h8Xt2EoFeZG&=dPTV^O(mz=TJzqs z<_0%%n$9jP^Y<1ZVVL6LOSmC|e*Mb@F^u=Xt%<;#f3?7-L|}uXKN5i*j&vBn5l1l$ z;DDo}46qVMP3b@#PBVc1r>|k1Bw&e$1SA1_t&{#c5QLM3#e6$v_uptdbG4Pab+F1LGK8rsn(aEorEL6Y5S*sNM+$+^KIC!zDYL zzA0%byNX>Zy;evbg5>f-^3RlDKPt<&D_t}xtAV0kqes{-R~gEs5KYlIR|of_h-Q>a zI0{pRekclWft`X{7*K=dDT1cX@|Zjgcq>IP)ssACSQAR73RGsPhzZn&?x{eV?`eJG zsfkcjX-W*kl!TmH>B5p!upF;;ny2g4hutU>U$^OLWx^d4{~L0uETkJ+4#m=d0j@Jo zL%L}LNJ}GhW%+Nt%8NtnQ%G=YN|9+PYerCGp`?|@ybKA(p& zg`iASAqQbAU;vBgPy2XFYzd0$M?Is2bi_a}Hjsu@Y@o)^)gu;i zx*6s2K)Hn1y2Tduu?4F(!^?GI6_idFE!AP& zV2_6s%Fjkh;pLk5fvhaSa_!9m>Kr#elq-{+U$9i7j8hwaZ9s{f#6L}>><_RF3G{d- zi{Zn}%3eyGTPfFWhs2u51zE`dLI6*k%BII#LX&JDi|-7FY@og-Bak`MXrEZ&tv>zVM;F0;~&L;Kf*Cg*hB;!p2-C|;`s>xP0{}W6k5kD delta 4519 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 value, + configurable: true + }); + } + + override(navigator, "userAgent", ua); + override(navigator, "appVersion", ua.slice("Mozilla/".length)); + override(navigator, "vendor", "Google Inc."); + override(navigator, "platform", "Win32"); + override(navigator, "hardwareConcurrency", 8); + override(navigator, "deviceMemory", 8); + + override(navigator, "userAgentData", { + brands: [ + { brand: "Vivaldi", version: "7.10" }, + { brand: "Chromium", version: "146" }, + { brand: "Not=A?Brand", version: "24" } + ], + fullVersionList: [ + { brand: "Vivaldi", version: "7.10" }, + { brand: "Chromium", version: "146.0.0.0" }, + { brand: "Not=A?Brand", version: "24.0.0.0" } + ], + uaFullVersion: "146.0.0.0", + platform: "Windows", + platformVersion: "15.0.0", + architecture: "x86", + bitness: "64", + mobile: false, + wow64: false + }); + +})(); diff --git a/Vivaldi/userscript/Override Client Hints API.user.js b/Vivaldi/userscript/Override Client Hints API.user.js new file mode 100755 index 0000000..36e8c48 --- /dev/null +++ b/Vivaldi/userscript/Override Client Hints API.user.js @@ -0,0 +1,92 @@ +// ==UserScript== +// @name Override Client Hints API +// @namespace +// @version 1.0 +// @description A script to override the Client Hints JavaScript API +// @author +// @match https://*/* +// @run-at document-start +// @grant none +// @license MIT +// ==/UserScript== + +(function() { + 'use strict'; + + // Use full version in UA string (e.g. Chrome/133.0.6943.126) to match real Chrome + const VIVALDI_VERSION = "7.10"; + const VIVALDI_FULL_VERSION = "7.10"; + const CHROME_VERSION = "146"; + const CHROME_FULL_VERSION = "146.0.7680.171"; + const chromeUA = `Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/146.0.0.0 Safari/537.36`; + + function override(obj, prop, value) { + Object.defineProperty(obj, prop, { + get: () => value, + configurable: true, + enumerable: true + }); + } + + + // Basic navigator properties + override(Navigator.prototype, "userAgent", chromeUA); + override(Navigator.prototype, "appVersion", chromeUA.slice("Mozilla/".length)); + override(Navigator.prototype, "platform", "Win32"); + override(Navigator.prototype, "vendor", "Google Inc."); + override(Navigator.prototype, "oscpu", undefined); // Chrome doesn't expose this; Firefox does + override(Navigator.prototype, "productSub", "20030107"); // Chrome: "20030107", Firefox: "20100101" + + // User-Agent Client Hints + // GREASE brand string and version rotate based on Chrome major version to prevent fingerprinting + function getGreasedBrand(major) { + const n = parseInt(major, 10) % 3; + const brandStrings = ["Not A(Brand", "Not)A;Brand", "Not:A-Brand"]; + const brandVersions = ["8", "99", "24"]; + return { brand: brandStrings[n], version: brandVersions[n] }; + } + + const greaseBrand = getGreasedBrand(CHROME_VERSION); + const greaseBrandFull = { brand: greaseBrand.brand, version: `${greaseBrand.version}.0.0.0` }; + + const brands = [ + greaseBrand, + { brand: "Vivaldi", version: VIVALDI_VERSION }, + { brand: "Chromium", version: CHROME_VERSION } + ]; + const fullVersionList = [ + greaseBrandFull, + { brand: "Vivaldi", version: VIVALDI_FULL_VERSION }, + { brand: "Chromium", version: CHROME_FULL_VERSION } + ]; + + // Built once, reused on every getHighEntropyValues() call + const highEntropyMap = { + architecture: "x86", + bitness: "64", + brands, + fullVersionList, + mobile: false, + model: "", + platform: "Windows", + platformVersion: "15.0.0", + uaFullVersion: CHROME_FULL_VERSION + }; + + const uaData = { + brands, + mobile: false, + platform: "Windows", + getHighEntropyValues: async function(hints) { + const result = {}; + for (const hint of hints) { + if (hint in highEntropyMap) result[hint] = highEntropyMap[hint]; + } + return result; + }, + toJSON: function() { + return { brands, mobile: false, platform: "Windows" }; + } + }; + override(Navigator.prototype, "userAgentData", uaData); +})();