Skip to content

Commit 8c3ce14

Browse files
committed
feat: add custom domain redirects & strip tracking parameters
1 parent 8f07414 commit 8c3ce14

6 files changed

Lines changed: 74 additions & 0 deletions

File tree

_locales/en/messages.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,12 @@
371371
"redirectToTwitterLabel": {
372372
"message": "Redirect to twitter.com"
373373
},
374+
"redirectTwitterLinksLabel": {
375+
"message": "Redirect Twitter/X links to"
376+
},
377+
"redirectTwitterLinksInfo": {
378+
"message": "Rewrite twitter.com/x.com links to an alternative frontend (leave empty to disable)"
379+
},
374380
"reduceAlgorithmicContentOptionsLabel": {
375381
"message": "Remove algorithmic content"
376382
},

options.css

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,19 @@ section.textarea button {
142142
text-align: center;
143143
}
144144

145+
section.text label {
146+
display: flex;
147+
flex-direction: column;
148+
gap: 8px;
149+
}
150+
151+
section.text input[type="text"] {
152+
padding: 6px 8px;
153+
border: 1px solid #ccc;
154+
border-radius: 4px;
155+
font-size: inherit;
156+
}
157+
145158
textarea {
146159
resize: vertical;
147160
}
@@ -449,6 +462,11 @@ body.iOS.safari .checkbox input:focus + .toggle {
449462
section.group > section > * {
450463
color: rgb(154, 160, 166);
451464
}
465+
section.text input[type="text"] {
466+
background-color: #3f4042;
467+
border-color: #5f6368;
468+
color: #e8eaed;
469+
}
452470

453471
/* Edge dark mode overrides */
454472
body.edge {

options.html

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,15 @@
293293
Replace t.co links with the actual destination URL
294294
</p>
295295
</section>
296+
<section class="text">
297+
<label>
298+
<span id="redirectTwitterLinksLabel">Redirect Twitter/X links to</span>
299+
<input type="text" name="redirectTwitterLinks" placeholder="e.g., nitter.net">
300+
</label>
301+
<p id="redirectTwitterLinksInfo">
302+
Rewrite twitter.com/x.com links to an alternative frontend (leave empty to disable)
303+
</p>
304+
</section>
296305
<section class="checkbox">
297306
<label>
298307
<span id="restoreQuoteTweetsLinkLabel">Restore Quote Tweets link under Tweets</span>

options.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,8 @@ for (let translationId of [
121121
'quoteTweetsLabel',
122122
'redirectChatNavLabel',
123123
'redirectToTwitterLabel',
124+
'redirectTwitterLinksLabel',
125+
'redirectTwitterLinksInfo',
124126
'reduceAlgorithmicContentOptionsLabel',
125127
'reduceEngagementOptionsLabel',
126128
'reducedInteractionModeInfo',
@@ -263,6 +265,7 @@ const defaultConfig = {
263265
quoteTweets: 'ignore',
264266
redirectChatNav: false,
265267
redirectToTwitter: false,
268+
redirectTwitterLinks: '',
266269
reducedInteractionMode: false,
267270
replaceLogo: true,
268271
restoreLinkHeadlines: true,

script.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,42 @@ XMLHttpRequest.prototype.send = function(body) {
9292
return XMLHttpRequest_send.apply(this, [body])
9393
}
9494

95+
/** Tracking parameters to strip from Twitter/X URLs */
96+
const TRACKING_PARAMS = ['s', 't', 'ref_src', 'ref_url', 'src', 'cxt', 'vertical', 'mx']
97+
98+
/**
99+
* Strip tracking params from a URL, optionally redirect to a custom domain
100+
*/
101+
function cleanTwitterUrl(urlString, targetDomain) {
102+
try {
103+
let url = new URL(urlString)
104+
TRACKING_PARAMS.forEach(param => url.searchParams.delete(param))
105+
let search = url.searchParams.toString()
106+
let domain = targetDomain || url.hostname
107+
return `https://${domain}${url.pathname}${search ? '?' + search : ''}${url.hash}`
108+
} catch (e) {
109+
return urlString
110+
}
111+
}
112+
113+
// Intercept copy events to strip tracking params and optionally redirect
114+
document.addEventListener('copy', function(e) {
115+
if (!config.enabled) return
116+
117+
let selection = window.getSelection()?.toString() || ''
118+
let urlPattern = /https?:\/\/(www\.)?(twitter\.com|x\.com|mobile\.twitter\.com|mobile\.x\.com)(\/[^\s]*)?/gi
119+
120+
if (urlPattern.test(selection)) {
121+
urlPattern.lastIndex = 0
122+
let targetDomain = config.redirectTwitterLinks?.trim().replace(/^https?:\/\//, '') || null
123+
let newText = selection.replace(urlPattern, (match) => cleanTwitterUrl(match, targetDomain))
124+
if (newText !== selection) {
125+
e.preventDefault()
126+
e.clipboardData?.setData('text/plain', newText)
127+
}
128+
}
129+
}, true)
130+
95131
let debug = false
96132

97133
/** @type {boolean} */
@@ -181,6 +217,7 @@ const config = {
181217
quoteTweets: 'ignore',
182218
redirectChatNav: false,
183219
redirectToTwitter: false,
220+
redirectTwitterLinks: '',
184221
reducedInteractionMode: false,
185222
replaceLogo: true,
186223
restoreLinkHeadlines: true,

types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export type Config = {
6868
quoteTweets: SharedTweetsConfig
6969
redirectChatNav: boolean
7070
redirectToTwitter: boolean
71+
redirectTwitterLinks: string
7172
reducedInteractionMode: boolean
7273
// XXX This now controls all replacement of X brand changes
7374
replaceLogo: boolean

0 commit comments

Comments
 (0)