├── README.md ├── goodtwitter2.user.js ├── love.png └── twitter2015.user.css /README.md: -------------------------------------------------------------------------------- 1 | # Don't use this!! Use [OldTwitter](https://github.com/dimdenGD/OldTwitter) instead!! 2 | # Don't use this!! Use [OldTwitter](https://github.com/dimdenGD/OldTwitter) instead!! 3 | # Don't use this!! Use [OldTwitter](https://github.com/dimdenGD/OldTwitter) instead!! 4 | 5 | # Twitter2015 6 | Finally, a way to return old and great Twitter's look. 7 | ![love](https://raw.githubusercontent.com/dimdenGD/Twitter2015/main/love.png) 8 | 9 | ## Installation 10 | 1) Install [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ru) 11 | 2) Click [here](https://github.com/dimdenGD/Twitter2015/raw/main/goodtwitter2.user.js) and install userscript 12 | 3) Install [Stylus](https://chrome.google.com/webstore/detail/stylus/clngdbkpkpeebahjckkjfobafhncgmne?hl=en) 13 | 4) Click [here](https://github.com/dimdenGD/Twitter2015/raw/main/twitter2015.user.css) and install userstyle 14 | 5) Go to Twitter settings, GoodTwitter2 tab and set: 15 | * Legacy Profile Layout - ✅ 16 | * Square Avatars - ✅ 17 | * Left Sidebar Media - ✅ 18 | * Use Custom Font - "sans-serif" (without quotes) 19 | * Rosetta Icons - ✅ 20 | 6) Finally, good Twitter is back! 21 | -------------------------------------------------------------------------------- /goodtwitter2.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name GoodTwitter 2 - Electric Boogaloo 3 | // @version 0.0.31.1 4 | // @description A try to make Twitter look good again 5 | // @author schwarzkatz 6 | // @license MIT 7 | // @match https://twitter.com/* 8 | // @exclude https://twitter.com/i/cards/* 9 | // @exclude https://twitter.com/i/release_notes 10 | // @exclude https://twitter.com/*/privacy 11 | // @exclude https://twitter.com/*/tos 12 | // @exclude https://twitter.com/account/access 13 | // @grant GM_deleteValue 14 | // @grant GM_getResourceText 15 | // @grant GM_getResourceURL 16 | // @grant GM_getValue 17 | // @grant GM_setValue 18 | // @grant GM_info 19 | // @grant GM_xmlhttpRequest 20 | // @connect api.twitter.com 21 | // @resource css https://github.com/Bl4Cc4t/GoodTwitter2/raw/master/twitter.gt2eb.style.css 22 | // @resource emojiRegex https://github.com/mathiasbynens/emoji-regex/raw/main/es2015/index.js 23 | // @resource pickrCss https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/classic.min.css 24 | // @require https://github.com/Bl4Cc4t/GoodTwitter2/raw/master/twitter.gt2eb.i18n.js 25 | // @require https://github.com/Bl4Cc4t/GoodTwitter2/raw/master/twitter.gt2eb.polyfills.js 26 | // @require https://code.jquery.com/jquery-3.5.1.min.js 27 | // @require https://gist.github.com/raw/2625891/waitForKeyElements.js 28 | // @require https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.es5.min.js 29 | // @updateURL https://github.com/Bl4Cc4t/GoodTwitter2/raw/master/twitter.gt2eb.user.js 30 | // @downloadURL https://github.com/Bl4Cc4t/GoodTwitter2/raw/master/twitter.gt2eb.user.js 31 | // ==/UserScript== 32 | 33 | (function($, waitForKeyElements) { 34 | "use strict" 35 | 36 | // do not execute on these pages 37 | if (getPath().match(/^login(\?.*)?$/) || (!isLoggedIn() && getPath().match(/^(\?.*)?$/))) { 38 | return 39 | } 40 | 41 | 42 | 43 | // ########################### 44 | // # convenience functions # 45 | // ########################### 46 | 47 | 48 | // seperate number with commas 49 | Number.prototype.humanize = function() { 50 | let t = this.toString().split("") 51 | let out = "" 52 | let c = 1 53 | for (let i=t.length-1; i>=0; i--) { 54 | out = `${t[i]}${out}` 55 | if (c++ % 3 == 0 && i-1 >= 0) { 56 | out = `,${out}` 57 | } 58 | } 59 | return out 60 | } 61 | 62 | 63 | // shorter version: 1.4M, 23.4K, etc 64 | Number.prototype.humanizeShort = function() { 65 | let t = this.toString() 66 | if (this >= 1000000) { 67 | t = t.slice(0, -5) 68 | return `${t.slice(0, -1)}${t.slice(-1) != 0 ? `.${t.slice(-1)}` : ""}M` 69 | } else if (this >= 10000) { 70 | t = t.slice(0, -2) 71 | return `${t.slice(0, -1)}${t.slice(-1) != 0 ? `.${t.slice(-1)}` : ""}K` 72 | } else return this.humanize() 73 | } 74 | 75 | 76 | // get kebab case (thisIsAString -> this-is-a-string) 77 | String.prototype.toKebab = function() { 78 | let out = "" 79 | for (let e of this.toString().split("")) { 80 | out += e == e.toUpperCase() ? `-${e.toLowerCase()}` : e 81 | } 82 | return out 83 | } 84 | 85 | String.prototype.replaceAt = function(index, length, text) { 86 | return `${[...this.toString()].slice(0, index).join("")}${text}${[...this.toString()].slice(index + length).join("")}` 87 | } 88 | 89 | String.prototype.insertAt = function(index, text) { 90 | return this.toString().replaceAt(index, 0, text) 91 | } 92 | 93 | const defaultAvatarUrl = "https://abs.twimg.com/sticky/default_profile_images/default_profile.png" 94 | 95 | 96 | // get account information 97 | function getInfo() { 98 | let sel = "#react-root ~ script" 99 | let infoScript = $(sel).text() 100 | function x(reg, defaultVal="") { 101 | let m = infoScript.match(reg) 102 | return m ? m[1] : defaultVal 103 | } 104 | return { 105 | bannerUrl: x(/profile_banner_url\":\"(.+?)\",/), 106 | avatarUrl: x(/profile_image_url_https\":\"(.+?)\",/, defaultAvatarUrl), 107 | screenName: x(/screen_name\":\"(.+?)\",/, "youarenotloggedin"), 108 | name: x(/name\":\"(.+?)\",/, "Anonymous"), 109 | id: x(/id_str\":\"(\d+)\"/, "0"), 110 | stats: { 111 | tweets: parseInt(x(/statuses_count\":(\d+),/, "0")), 112 | followers: parseInt(x(/\"followers_count\":(\d+),/, "0")), 113 | following: parseInt(x(/friends_count\":(\d+),/, "0")), 114 | } 115 | } 116 | } 117 | 118 | 119 | // get current display language 120 | function getLang() { 121 | return $("html").attr("lang").trim() 122 | } 123 | 124 | 125 | // check if the user is logged in 126 | function isLoggedIn() { 127 | return document.cookie.match(/ twid=/) 128 | } 129 | 130 | 131 | // get localized version of a string. 132 | // defaults to english version. 133 | function getLocStr(key) { 134 | let lang = getLang() 135 | lang = Object.keys(i18n).includes(lang) ? lang : "en" 136 | if (Object.keys(i18n[lang]).includes(key) && !i18n[lang][key].startsWith("*NEW*")) { 137 | return i18n[lang][key] 138 | } else { 139 | return i18n.en[key] 140 | } 141 | } 142 | 143 | 144 | // current path 145 | function getPath() { 146 | return window.location.href.replace(/.*?twitter\.com\//, "") 147 | } 148 | 149 | 150 | // svg convenience 151 | function getSvg(key) { 152 | let svgs = { 153 | lightning: ``, 154 | caret: ``, 155 | tick: ``, 156 | moon: ``, 157 | x: ``, 158 | google: ``, 159 | arrow: ``, 160 | location: ``, 161 | url: ``, 162 | calendar: ``, 163 | balloon: ``, 164 | } 165 | return ` 166 | 167 | ${svgs[key]} 168 | ` 169 | } 170 | 171 | 172 | // request headers 173 | function getRequestHeaders(additionalHeaders) { 174 | // found in https://abs.twimg.com/responsive-web/web/main.5c0baa34.js 175 | let publicBearer = "AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA" 176 | let csrf = window.document.cookie.match(/ct0=([^;]+)(;|$)/)[1] 177 | 178 | let out = { 179 | authorization: `Bearer ${publicBearer}`, 180 | origin: "https://twitter.com", 181 | referer: window.location.href, 182 | "x-twitter-client-language": getLang(), 183 | "x-csrf-token": csrf, 184 | "x-twitter-active-user": "yes", 185 | // "x-twitter-auth-type": "OAuth2Session" 186 | } 187 | Object.assign(out, additionalHeaders) 188 | return out 189 | } 190 | 191 | 192 | function getRequestURL(base, param) { 193 | let out = base 194 | for (let [key, val] of Object.entries(param)) { 195 | if (typeof val === "object") val = encodeURIComponent(JSON.stringify(val)) 196 | out += `&${key}=${val}` 197 | } 198 | return `${out.replace("&", "?")}` 199 | } 200 | 201 | 202 | function requestTweet(id, cb) { 203 | GM_xmlhttpRequest({ 204 | method: "GET", 205 | url: getRequestURL("https://twitter.com/i/api/1.1/statuses/show.json", { 206 | id, 207 | tweet_mode: "extended", 208 | trim_user: true, 209 | include_cards: 1 210 | }), 211 | headers: getRequestHeaders(), 212 | onload: function(res) { 213 | if (res.status == "200") { 214 | cb(JSON.parse(res.response)) 215 | } else { 216 | console.warn(res) 217 | } 218 | } 219 | }) 220 | } 221 | 222 | 223 | function requestUser(screenName, cb) { 224 | GM_xmlhttpRequest({ 225 | method: "GET", 226 | url: getRequestURL(`https://twitter.com/i/api/graphql/jMaTS-_Ea8vh9rpKggJbCQ/UserByScreenName`, { 227 | variables: { 228 | screen_name: screenName, 229 | withHighlightedLabel: true 230 | } 231 | }), 232 | headers: getRequestHeaders(), 233 | onload: function(res) { 234 | if (res.status == "200") { 235 | cb(JSON.parse(res.response)) 236 | } else { 237 | console.warn(res) 238 | } 239 | } 240 | }) 241 | } 242 | 243 | 244 | function blockUser(user_id, block, cb) { 245 | GM_xmlhttpRequest({ 246 | method: "POST", 247 | url: getRequestURL(`https://api.twitter.com/1.1/blocks/${block ? "create" : "destroy"}.json`, { 248 | user_id, 249 | skip_status: true 250 | }), 251 | headers: getRequestHeaders(), 252 | onload: function(res) { 253 | if (res.status == "200") { 254 | cb() 255 | } else { 256 | console.warn(res) 257 | } 258 | } 259 | }) 260 | } 261 | 262 | 263 | // adds links from an entities object to a text 264 | String.prototype.populateWithEntities = function(entities) { 265 | let text = this.toString() 266 | let out = text 267 | 268 | let toReplace = [] 269 | 270 | // urls 271 | if (entities.urls) { 272 | for (let url of entities.urls) { 273 | toReplace.push({ 274 | [url.indices[0]]: `${url.display_url} ` 276 | }) 277 | } 278 | } 279 | 280 | // users 281 | if (entities.user_mentions) { 282 | for (let user of entities.user_mentions) { 283 | let x = text.slice(user.indices[0], user.indices[0]+1) == "@" ? 0 : 1 284 | toReplace.push({ 285 | [user.indices[0]+x]: ``, 286 | [user.indices[1]+x]: ` ` 287 | }) 288 | } 289 | } 290 | 291 | // hashtags 292 | if (entities.hashtags) { 293 | for (let hashtag of entities.hashtags) { 294 | let x = text.slice(hashtag.indices[0], hashtag.indices[0]+1) == "#" ? 0 : 1 295 | toReplace.push({ 296 | [hashtag.indices[0]+x]: ``, 297 | [hashtag.indices[1]+x]: ` ` 298 | }) 299 | } 300 | } 301 | 302 | // sort array 303 | toReplace = toReplace.sort((a, b) => parseInt(Object.keys(a)[0]) - parseInt(Object.keys(b)[0])) 304 | 305 | // replace values 306 | let offset = 0 307 | for (let e of toReplace) { 308 | for (let [index, value] of Object.entries(e)) { 309 | out = out.insertAt(parseInt(index) + offset, value) 310 | offset += value.length 311 | } 312 | } 313 | 314 | if (GM_getValue("opt_gt2").expandTcoShortlinks) { 315 | let re = /href="(https:\/\/t\.co\/[^"]+)"/ 316 | let match 317 | while ((match = re.exec(out)) != null) { 318 | out = out.replace(new RegExp(`href="${match[1]}"`), `href="${entities.urls.find(e => e.url == match[1]).expanded_url}"`) 319 | } 320 | } 321 | 322 | return out 323 | } 324 | 325 | 326 | // replace emojis with the twitter svgs 327 | String.prototype.replaceEmojis = function() { 328 | let text = this.toString() 329 | 330 | let out = text 331 | let re = new RegExp(`(${GM_getResourceText("emojiRegex").match(/return \/(.*)\/gu/)[1]})`, "gu") 332 | let match 333 | let offset = 0 334 | while ((match = re.exec(text)) != null) { 335 | let e = match[1] 336 | // get unicode of emoji 337 | let uni = e.codePointAt(0).toString(16) 338 | if (e.length == 4) { 339 | uni += `-${e.codePointAt(2).toString(16)}` 340 | } 341 | 342 | // replace with image 343 | let img = `${e}` 344 | out = out.replaceAt(match.index + offset, e.length, img) 345 | 346 | offset += img.length - e.length 347 | } 348 | 349 | return out 350 | } 351 | 352 | 353 | 354 | // ################### 355 | // # GT2 settings # 356 | // ################### 357 | 358 | 359 | // custom options and their default values 360 | const opt_gt2 = { 361 | forceLatest: false, 362 | disableAutoRefresh: false, 363 | keepTweetsInTL: true, 364 | biggerPreviews: true, 365 | 366 | hideTranslateTweetButton: false, 367 | tweetIconsPullLeft: false, 368 | 369 | stickySidebars: true, 370 | smallSidebars: false, 371 | hideTrends: false, 372 | leftTrends: true, 373 | show10Trends: false, 374 | 375 | legacyProfile: true, 376 | squareAvatars: true, 377 | enableQuickBlock: false, 378 | leftMedia: false, 379 | 380 | hideFollowSuggestions: false, 381 | hideFollowSuggestionsSel: 7, 382 | fontOverride: true, 383 | fontOverrideValue: "sans-serif", 384 | colorOverride: false, 385 | colorOverrideValue: "85, 102, 68", 386 | hideMessageBox: true, 387 | rosettaIcons: true, 388 | favoriteLikes: false, 389 | 390 | updateNotifications: false, 391 | expandTcoShortlinks: true, 392 | } 393 | 394 | // set default options 395 | if (GM_getValue("opt_gt2") == undefined) GM_setValue("opt_gt2", opt_gt2) 396 | 397 | // add previously non existant options 398 | if (JSON.stringify(Object.keys(GM_getValue("opt_gt2"))) != JSON.stringify(Object.keys(opt_gt2))) { 399 | let old = GM_getValue("opt_gt2") 400 | 401 | // remove default options that are modified 402 | for (let k of Object.keys(opt_gt2)) { 403 | if (Object.keys(old).includes(k)) delete opt_gt2[k] 404 | } 405 | 406 | // remove old options 407 | for (let k of Object.keys(old)) { 408 | if (Object.keys(opt_gt2).includes(k)) delete old[k] 409 | } 410 | 411 | Object.assign(old, opt_gt2) 412 | GM_setValue("opt_gt2", old) 413 | } 414 | 415 | 416 | // toggle opt_gt2 value 417 | function toggleGt2Opt(key) { 418 | let x = GM_getValue("opt_gt2") 419 | x[key] = !x[key] 420 | GM_setValue("opt_gt2", x) 421 | } 422 | 423 | 424 | // insert the menu item 425 | function addSettingsToggle() { 426 | if (!$(".gt2-toggle-settings").length) { 427 | $(`main section[aria-labelledby=root-header] div[role=tablist], 428 | main > div > div > div > div:last-child > div[role=tablist], 429 | main div[data-testid=loggedOutPrivacySection]`).append(` 430 | 431 |
432 | GoodTwitter2 433 | ${getSvg("caret")} 434 |
435 |
436 | `) 437 | } 438 | } 439 | 440 | 441 | // toggle settings display 442 | $("body").on("click", ".gt2-toggle-settings", function(event) { 443 | event.preventDefault() 444 | window.history.pushState({}, "", $(this).attr("href")) 445 | addSettings() 446 | changeSettingsTitle() 447 | }) 448 | 449 | 450 | // disable settings display again when clicking on another menu item 451 | $("body").on("click", `main section[aria-labelledby=root-header] div[role=tablist] a:not(.gt2-toggle-settings), 452 | main section[aria-labelledby=root-header] div[data-testid=loggedOutPrivacySection] a:not(.gt2-toggle-settings)`, () => { 453 | $(".gt2-settings-header, .gt2-settings").remove() 454 | }) 455 | 456 | 457 | // get html for a gt2 toggle (checkbox) 458 | function getSettingTogglePart(name, additionalHTML="") { 459 | let d = `${name}Desc` 460 | return ` 461 |
462 |
463 | ${getLocStr(name)} 464 |
465 |
466 |
467 | ${getSvg("tick")} 468 |
469 |
470 |
471 | ${additionalHTML} 472 | ${getLocStr(d) ? `${getLocStr(d)}` : ""} 473 |
` 474 | } 475 | 476 | 477 | // add the settings to the display (does not yet work on screens smaller than 1050px) 478 | function addSettings() { 479 | if (!$(".gt2-settings").length) { 480 | let elem = ` 481 |
482 |
483 |
484 | ${getSvg("arrow")} 485 |
486 | GoodTwitter2 v${GM_info.script.version} 487 |
488 |
489 |
${getLocStr("settingsHeaderTimeline")}
490 | ${getSettingTogglePart("forceLatest")} 491 | ${getSettingTogglePart("disableAutoRefresh")} 492 | ${getSettingTogglePart("keepTweetsInTL")} 493 | ${getSettingTogglePart("biggerPreviews")} 494 |
495 | 496 |
${getLocStr("statsTweets")}
497 | ${getSettingTogglePart("hideTranslateTweetButton")} 498 | ${getSettingTogglePart("tweetIconsPullLeft")} 499 |
500 | 501 |
${getLocStr("settingsHeaderSidebars")}
502 | ${getSettingTogglePart("stickySidebars")} 503 | ${getSettingTogglePart("smallSidebars")} 504 | ${getSettingTogglePart("hideTrends")} 505 | ${getSettingTogglePart("leftTrends")} 506 | ${getSettingTogglePart("show10Trends")} 507 |
508 | 509 |
${getLocStr("navProfile")}
510 | ${getSettingTogglePart("legacyProfile")} 511 | ${getSettingTogglePart("squareAvatars")} 512 | ${getSettingTogglePart("enableQuickBlock")} 513 | ${getSettingTogglePart("leftMedia")} 514 |
515 | 516 |
${getLocStr("settingsHeaderGlobalLook")}
517 | ${getSettingTogglePart("hideFollowSuggestions", ` 518 |
519 | ${["topics", "users", "navLists"].map((e, i) => { 520 | let x = Math.pow(2, i) 521 | return `
522 | ${getLocStr(e)} 523 |
524 |
525 |
${getSvg("tick")}
526 |
527 |
528 | `}).join("")} 529 |
530 | `)} 531 | ${getSettingTogglePart("fontOverride", ` 532 |
533 | 534 |
535 | `)} 536 | ${getSettingTogglePart("colorOverride", `
`)} 537 | ${getSettingTogglePart("hideMessageBox")} 538 | ${getSettingTogglePart("rosettaIcons")} 539 | ${getSettingTogglePart("favoriteLikes")} 540 |
541 | 542 |
${getLocStr("settingsHeaderOther")}
543 | ${getSettingTogglePart("updateNotifications")} 544 | ${getSettingTogglePart("expandTcoShortlinks")} 545 |
546 | ` 547 | let $s = $("main section[aria-labelledby=detail-header]") 548 | if ($s.length) { 549 | $s.prepend(elem) 550 | } else { 551 | $("main > div > div > div").append(` 552 |
${elem}
553 | `) 554 | } 555 | // add color pickr 556 | Pickr.create({ 557 | el: ".gt2-pickr", 558 | theme: "classic", 559 | lockOpacity: true, 560 | useAsButton: true, 561 | appClass: "gt2-color-override-pickr", 562 | inline: true, 563 | default: `rgb(${GM_getValue("opt_gt2").colorOverrideValue})`, 564 | components: { 565 | preview: true, 566 | hue: true, 567 | interaction: { 568 | hex: true, 569 | rgba: true, 570 | hsla: true, 571 | hsva: true, 572 | cmyk: true, 573 | input: true 574 | } 575 | } 576 | }) 577 | .on("change", e => { 578 | let val = e.toRGBA().toString(0).slice(5, -4) 579 | GM_setValue("opt_gt2", Object.assign(GM_getValue("opt_gt2"), { colorOverrideValue: val})) 580 | document.documentElement.style.setProperty("--color-override", val) 581 | }) 582 | disableTogglesIfNeeded() 583 | } 584 | } 585 | 586 | 587 | // change the title to display GoodTwitter2 588 | function changeSettingsTitle() { 589 | let t = $("title").html() 590 | $("title").html(`${t.startsWith("(") ? `${t.split(" ")[0]} ` : ""}GoodTwitter2 / Twitter`) 591 | } 592 | 593 | 594 | // handler for the toggles 595 | $("body").on("click", ".gt2-setting-toggle:not(.gt2-disabled)", function() { 596 | $(this).toggleClass("gt2-active") 597 | if ($(this).is("[data-setting-name]")) { 598 | let name = $(this).attr("data-setting-name").trim() 599 | toggleGt2Opt(name) 600 | $("body").toggleClass(`gt2-opt-${name.toKebab()}`) 601 | } 602 | 603 | // hide follow suggestions 604 | if ($(this).is("[data-hfs-type]")) { 605 | let opt = GM_getValue("opt_gt2") 606 | GM_setValue("opt_gt2", Object.assign(opt, { ["hideFollowSuggestionsSel"]: opt.hideFollowSuggestionsSel ^ parseInt($(this).attr("data-hfs-type")) })) 607 | } 608 | disableTogglesIfNeeded() 609 | }) 610 | 611 | // handler for inputs 612 | $("body").on("keyup", ".gt2-setting-input input", function() { 613 | let name = $(this).parent().attr("data-setting-name").trim() 614 | let val = $(this).val().trim() 615 | 616 | GM_setValue("opt_gt2", Object.assign(GM_getValue("opt_gt2"), { [name]: val})) 617 | document.documentElement.style.setProperty(`--${name.replace("Value", "").toKebab()}`, val) 618 | }) 619 | 620 | 621 | function disableTogglesIfNeeded() { 622 | // when autoRefresh is on, keepTweetsInTL must also be on and can not be deactivated (it is disabled) 623 | let $t = $("div[data-setting-name=keepTweetsInTL]") 624 | if (GM_getValue("opt_gt2").disableAutoRefresh) { 625 | if (!GM_getValue("opt_gt2").keepTweetsInTL) { 626 | $t.click() 627 | } 628 | $t.addClass("gt2-disabled") 629 | } else { 630 | $t.removeClass("gt2-disabled") 631 | } 632 | 633 | // other trend related toggles are not needed when the trends are disabled 634 | $("div[data-setting-name=leftTrends], div[data-setting-name=show10Trends]") 635 | [GM_getValue("opt_gt2").hideTrends ? "addClass" : "removeClass"]("gt2-disabled") 636 | 637 | // hide font input if fontOverride is disabled 638 | $("[data-setting-name=fontOverrideValue]") 639 | [GM_getValue("opt_gt2").fontOverride ? "removeClass" : "addClass"]("gt2-hidden") 640 | 641 | // hide color input if colorOverride is disabled 642 | $(".gt2-color-override-pickr") 643 | [GM_getValue("opt_gt2").colorOverride ? "removeClass" : "addClass"]("gt2-hidden") 644 | 645 | // hide follow suggestions 646 | $("[data-setting-name=hideFollowSuggestionsSel]") 647 | [GM_getValue("opt_gt2").hideFollowSuggestions ? "removeClass" : "addClass"]("gt2-hidden") 648 | } 649 | 650 | 651 | // click on the back button 652 | $("body").on("click", ".gt2-settings-back", () => window.history.back()) 653 | 654 | 655 | 656 | // ####################### 657 | // # various functions # 658 | // ####################### 659 | 660 | 661 | // add navbar 662 | function addNavbar() { 663 | waitForKeyElements(`nav > a[href="/home"]`, () => { 664 | if ($(".gt2-nav").length) return 665 | 666 | $("main").before(` 667 | 680 |
681 | `) 682 | 683 | // home, notifications, messages 684 | for (let e of [ 685 | "Home", 686 | "Notifications", 687 | "Messages", 688 | window.innerWidth < 1005 ? "Explore" : null 689 | ]) { 690 | if (!e) continue 691 | let $e = $(`nav > a[href="/${e.toLowerCase()}"]`) 692 | if (!e.length) continue 693 | $e.appendTo(".gt2-nav-left") 694 | $(`.gt2-nav a[href="/${e.toLowerCase()}"] > div`) 695 | .append(` 696 |
697 | ${getLocStr(`nav${e}`)} 698 |
699 | `) 700 | .attr("data-gt2-color-override-ignore", "") 701 | } 702 | 703 | // highlight current location 704 | $(`.gt2-nav a[href^='/${getPath().split("/")[0]}']`).addClass("active") 705 | 706 | // twitter logo 707 | $("h1 a[href='/home'] svg") 708 | .appendTo(".gt2-nav-center a") 709 | }) 710 | } 711 | 712 | // add navbar 713 | function addNavbarLoggedOut() { 714 | waitForKeyElements("nav > a[data-testid=AppTabBar_Explore_Link]", () => { 715 | if ($(".gt2-nav").length) return 716 | 717 | $("body").prepend(` 718 | 727 |
728 | `) 729 | 730 | // explore and settings 731 | $(`nav > a[data-testid=AppTabBar_Explore_Link], 732 | nav > a[href="/settings"]`) 733 | .appendTo(".gt2-nav-left") 734 | $(`.gt2-nav a[data-testid=AppTabBar_Explore_Link] > div`) 735 | .append(` 736 |
737 | ${getLocStr(`navExplore`)} 738 |
739 | `) 740 | $(`.gt2-nav a[href="/settings"] > div`) 741 | .append(` 742 |
743 | ${$(`.gt2-nav a[href="/settings"]`).attr("aria-label")} 744 |
745 | `) 746 | 747 | // highlight current location 748 | $(`.gt2-nav a[href^='/${getPath().split("/")[0]}']`).addClass("active") 749 | 750 | // twitter logo 751 | $("header h1 a[href='/'] svg") 752 | .appendTo(".gt2-nav-center a") 753 | }) 754 | } 755 | 756 | 757 | // add search 758 | function addSearch() { 759 | let search = "div[data-testid=sidebarColumn] > div > div:nth-child(2) > div > div > div > div:nth-child(1)" 760 | waitForKeyElements(`${search} input[data-testid=SearchBox_Search_Input]`, () => { 761 | // remove if added previously 762 | $(".gt2-search").empty() 763 | // add search 764 | $(search) 765 | .prependTo(".gt2-search") 766 | $("body").addClass("gt2-search-added") 767 | }) 768 | } 769 | 770 | 771 | // add element to sidebar 772 | function addToSidebar(elements) { 773 | let w = window.innerWidth 774 | let insertAt = ".gt2-left-sidebar" 775 | 776 | // insert into the right sidebar 777 | if ((!GM_getValue("opt_gt2").smallSidebars && w <= 1350) || 778 | ( GM_getValue("opt_gt2").smallSidebars && w <= 1230)) { 779 | insertAt = "div[data-testid=sidebarColumn] > div > div:nth-child(2) > div > div > div" 780 | } 781 | 782 | elements.unshift(`
`) 783 | waitForKeyElements(insertAt, () => { 784 | if (!$(insertAt).find(".gt2-legacy-profile-info").length) { 785 | for (let elem of elements.slice().reverse()) { 786 | if (insertAt.startsWith(".gt2")) { 787 | $(insertAt).prepend(elem) 788 | } else { 789 | $(`${insertAt} > div:empty:not(.gt2-legacy-profile-info)`).after(elem) 790 | } 791 | } 792 | } 793 | if ($(".gt2-dashboard-profile").length > 1) { 794 | $(".gt2-dashboard-profile").last().remove() 795 | } 796 | 797 | }) 798 | } 799 | 800 | 801 | // profile view left sidebar 802 | function getDashboardProfile() { 803 | let i = getInfo() 804 | // console.log(`userInformation:\n${JSON.stringify(i, null, 2)}`) 805 | let href = isLoggedIn() ? "href" : "data-href" 806 | return ` 807 |
808 | 809 |
810 | 811 | 812 | 813 | 819 |
820 |
821 | ${getSvg(isLoggedIn() ? "caret" : "moon")} 822 |
823 | 845 |
846 |
847 | ` 848 | } 849 | 850 | 851 | // gt2 update notice 852 | function getUpdateNotice() { 853 | let v = GM_info.script.version 854 | return ` 855 |
856 |
857 | GoodTwitter 2 858 |
859 |
860 | ${getSvg("x")} 861 |
862 |
863 |
864 | ${getSvg("tick")} ${getLocStr("updatedInfo").replace("$version$", `v${v}`)}
865 | 868 | ${getLocStr("updatedInfoChangelog")} 869 | 870 |
871 |
872 | ` 873 | } 874 | 875 | 876 | // recreate the legacy profile layout 877 | function rebuildLegacyProfile() { 878 | let currentScreenName = getPath().split("/")[0].split("?")[0].split("#")[0] 879 | console.log(`rebuild: ${currentScreenName}`) 880 | 881 | 882 | let profileSel = "div[data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(1) > div:nth-child(2)" 883 | 884 | waitForKeyElements(`div[data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(1)`, () => { 885 | // observe changes 886 | let lplMut = new MutationObserver(mut => { 887 | mut.forEach(m => { 888 | console.log(m.target) 889 | }) 890 | }).observe($(`div[data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(1)`)[0], { 891 | childList: true, 892 | subtree: true 893 | }) 894 | }) 895 | 896 | waitForKeyElements(`a[href='/${currentScreenName}/photo' i] img`, () => { 897 | // remove previously added profile 898 | if ($(".gt2-legacy-profile-nav").length) { 899 | $(".gt2-legacy-profile-banner, .gt2-legacy-profile-nav").remove() 900 | $(".gt2-legacy-profile-info").empty() 901 | } 902 | 903 | 904 | let $profile = $(profileSel) 905 | 906 | // information (constant) 907 | const i = { 908 | $banner: $("a[href$='/header_photo'] img"), 909 | avatarUrl: $("a[href$='/photo'] img"), 910 | screenName: $profile.find("> div:nth-child(2) > div > div > div:nth-child(2) > div:nth-child(1) > span").text().slice(1), 911 | followsYou: $profile.find("> div:nth-child(2) > div > div > div:nth-child(2) > div:nth-child(2)"), 912 | nameHTML: $profile.find("> div:nth-child(2) > div > div > div:nth-child(1) > div").html(), 913 | joinDateHTML: $profile.find("div[data-testid=UserProfileHeader_Items] > span:last-child").html(), 914 | followingRnd: $profile.find(`a[href$="/following"] > span:first-child, > div:not(:first-child) div:nth-child(1) > [role=button]:first-child:last-child > span:first-child`).first().text().trim(), 915 | followersRnd: $profile.find(`a[href$="/followers"] > span:first-child, > div:not(:first-child) div:nth-child(2) > [role=button]:first-child:last-child > span:first-child`).first().text().trim(), 916 | screenNameOnly: false 917 | } 918 | 919 | if (i.screenName == "") { 920 | i.screenNameOnly = true 921 | i.screenName = $(i.nameHTML).text().trim().slice(1) 922 | } 923 | 924 | 925 | if (!$(".gt2-legacy-profile-banner").length) { 926 | $("header").before(` 927 |
928 | ${i.$banner.length ? `` : ""} 929 |
930 |
931 |
932 | 933 |
934 |
${i.nameHTML}
935 |
936 | ${i.screenNameOnly ? "" : ` 937 |
938 | @${i.screenName} 939 |
940 | `} 941 | ${i.followsYou.length ? i.followsYou.prop("outerHTML") : ""} 942 |
943 |
944 |
945 |
946 | 947 |
${getLocStr("statsTweets")}
948 |
0
949 |
950 | 951 |
${getLocStr("statsFollowing")}
952 |
${i.followingRnd}
953 |
954 | 955 |
${getLocStr("statsFollowers")}
956 |
${i.followersRnd}
957 |
958 | 959 |
${getLocStr("statsLikes")}
960 |
0
961 |
962 | 972 |
973 |
974 |
975 | `) 976 | } 977 | 978 | // add like and tweet count 979 | requestUser(i.screenName, res => { 980 | let profileData = res.data.user 981 | let pleg = profileData.legacy 982 | 983 | // profile id 984 | $(".gt2-legacy-profile-info").attr("data-profile-id", profileData.rest_id) 985 | 986 | // change stats 987 | for (let tmp of [ 988 | [i.screenName, "statuses_count"], 989 | ["following", "friends_count"], 990 | ["followers", "followers_count"], 991 | ["likes", "favourites_count"] 992 | ]) { 993 | $(`.gt2-legacy-profile-nav-center a[href$="/${tmp[0]}"]`) 994 | .attr("title", pleg[tmp[1]].humanize()) 995 | .find("div:nth-child(2)").html(pleg[tmp[1]].humanizeShort()) 996 | } 997 | 998 | // expand t.co links 999 | if (GM_getValue("opt_gt2").expandTcoShortlinks) { 1000 | let urls = pleg.entities.description.urls.concat(pleg.entities.url ? pleg.entities.url.urls : []) 1001 | $(`.gt2-legacy-profile-info a[href^="https://t.co"]`).each(function() { 1002 | $(this).attr("href", urls.find(e => e.url == $(this).attr("href").split("?")[0]).expanded_url) 1003 | }) 1004 | } 1005 | }) 1006 | 1007 | // sidebar profile information 1008 | waitForKeyElements(`[href="/${getPath().split("/")[0].split("?")[0].split("#")[0]}/following" i]`, () => { 1009 | $(".gt2-legacy-profile-info").data("alreadyFound", false) 1010 | waitForKeyElements(".gt2-legacy-profile-info", () => { 1011 | if (!$(".gt2-legacy-profile-info .gt2-legacy-profile-name").length) { 1012 | // elements 1013 | let e = { 1014 | $description: $profile.find("div[data-testid=UserDescription]"), 1015 | $items: $profile.find("div[data-testid=UserProfileHeader_Items]"), 1016 | $fyk: $profile.find("> div:last-child:not(:nth-child(2)) > div:last-child:first-child") 1017 | } 1018 | i.screenName = $profile.find("> div:nth-child(2) > div > div > div:nth-child(2) > div:nth-child(1) > span").text().slice(1) 1019 | i.followsYou = $profile.find("> div:nth-child(2) > div > div > div:nth-child(2) > div:nth-child(2)") 1020 | i.nameHTML = $profile.find("> div:nth-child(2) > div > div > div:nth-child(1) > div").html() 1021 | if (i.screenName == "") { 1022 | i.screenNameOnly = true 1023 | i.screenName = $(i.nameHTML).text().trim().slice(1) 1024 | } 1025 | 1026 | $(".gt2-legacy-profile-info").append(` 1027 |
${i.nameHTML}
1028 |
1029 | ${i.screenNameOnly ? "" : ` 1030 |
1031 | @${i.screenName} 1032 |
1033 | `} 1034 | ${i.followsYou.length ? i.followsYou.prop("outerHTML") : ""} 1035 |
1036 | ${e.$description.length ? `
${e.$description.parent().html()}
` : ""} 1037 |
${e.$items.length ? e.$items.html() : ""}
1038 | ${e.$fyk.length ? `
${e.$fyk.prop("outerHTML")}
` : ""} 1039 | `) 1040 | 1041 | GM_setValue("hasRun_InsertFYK", false) 1042 | waitForKeyElements(`a[href$="/followers_you_follow"] img`, e => { 1043 | if (!GM_getValue("hasRun_InsertFYK")) { 1044 | $(".gt2-legacy-profile-fyk").html($(e).parents(`a[href$="/followers_you_follow"]`).prop("outerHTML")) 1045 | GM_setValue("hasRun_InsertFYK", true) 1046 | } 1047 | }) 1048 | } 1049 | }) 1050 | }) 1051 | 1052 | // buttons 1053 | if (!$(".gt2-legacy-profile-nav-right > div").length) { 1054 | $profile.find("> div:nth-child(1) > div").detach().appendTo(".gt2-legacy-profile-nav-right") 1055 | } 1056 | 1057 | }) 1058 | 1059 | // profile suspended / not found / temporarily restricted (first view) 1060 | waitForKeyElements([ 1061 | `[data-testid=emptyState] > div:nth-child(2) > *:not(a)`, // not found 1062 | `[data-testid=emptyState] [href="https://support.twitter.com/articles/18311"]`, // suspended 1063 | `[data-testid=emptyState] [href="https://support.twitter.com/articles/20169222"]`, // withheld in country 1064 | `[data-testid=UserDescription] [href="https://support.twitter.com/articles/20169199"]` // temporarily unavailable (Media Policy Violation) 1065 | ].join(", "), () => { 1066 | let $tmp = $(profileSel).find("> div:nth-child(2) > div > div") 1067 | let i = { 1068 | screenName: $tmp.find("> div:nth-last-child(1)").text().trim().slice(1), 1069 | nameHTML: $tmp.find("> div").length > 1 ? $tmp.find("> div:nth-child(1)").html() : null, 1070 | avatarUrl: defaultAvatarUrl 1071 | } 1072 | $("body").addClass("gt2-profile-not-found") 1073 | $("header").before(` 1074 |
1075 | 1076 |
1077 |
1078 |
1079 | 1080 |
1081 | ${i.nameHTML ? i.nameHTML : `@${i.screenName}`} 1082 | ${i.nameHTML ? ` 1083 | 1088 | ` : ""} 1089 |
1090 |
1091 |
1092 | 1093 |
${getLocStr("statsTweets")}
1094 |
0
1095 |
1096 | 1097 |
${getLocStr("statsFollowing")}
1098 |
0
1099 |
1100 | 1101 |
${getLocStr("statsFollowers")}
1102 |
0
1103 |
1104 | 1105 |
${getLocStr("statsLikes")}
1106 |
0
1107 |
1108 |
1109 |
1110 |
1111 | `) 1112 | waitForKeyElements(".gt2-legacy-profile-info", () => { 1113 | $(".gt2-legacy-profile-info").append(` 1114 | ${i.nameHTML ? i.nameHTML : `@${i.screenName}`} 1115 | ${i.nameHTML ? ` 1116 |
1117 | 1118 | @${i.screenName} 1119 | 1120 |
1121 | ` : ""} 1122 | `) 1123 | }) 1124 | }) 1125 | } 1126 | 1127 | 1128 | // force latest tweets view. 1129 | function forceLatest() { 1130 | let sparkOptToggle = "div[data-testid=primaryColumn] > div > div:nth-child(1) > div:nth-child(1) > div > div > div > div > div:nth-child(2) div[aria-haspopup]" 1131 | let sparkOpt = "#react-root h2 + div > div:nth-child(2) > div > div > div > div:nth-child(2) > div:nth-child(3)" 1132 | 1133 | GM_setValue("hasRun_forceLatest", false) 1134 | waitForKeyElements(sparkOptToggle, () => { 1135 | if (!GM_getValue("hasRun_forceLatest")) { 1136 | $(sparkOptToggle).click() 1137 | $("body").addClass("gt2-hide-spark-opt") 1138 | } 1139 | 1140 | waitForKeyElements(`${sparkOpt} a[href='/settings/content_preferences']`, () => { 1141 | if (!GM_getValue("hasRun_forceLatest")) { 1142 | GM_setValue("hasRun_forceLatest", true) 1143 | if ($(sparkOpt).find("> div:nth-child(1) path").length == 3) { 1144 | $(sparkOpt).children().eq(1).click() 1145 | } else { 1146 | $(sparkOptToggle).click() 1147 | } 1148 | $("body").removeClass("gt2-hide-spark-opt") 1149 | } 1150 | }) 1151 | }) 1152 | } 1153 | 1154 | 1155 | // handle trends (wrap, move and show10) 1156 | function handleTrends() { 1157 | let w = window.innerWidth 1158 | let trends = `div[data-testid=trend]:not(.gt2-trend-wrapped), 1159 | section[aria-labelledby^=accessible-list] a[href="/explore/tabs/for-you"] > div > span:not(.gt2-trend-wrapped)` 1160 | 1161 | waitForKeyElements(trends, () => { 1162 | 1163 | // actions for the whole container 1164 | if (!$(trends).parents("section").hasClass("gt2-trends-handled") 1165 | && $(trends).parents("div[data-testid=sidebarColumn]").length 1166 | ) { 1167 | $(trends).parents("section").addClass("gt2-trends-handled") 1168 | 1169 | // hide trends 1170 | if (GM_getValue("opt_gt2").hideTrends) { 1171 | $(trends).parents("section").parent().parent().parent().remove() 1172 | return 1173 | } 1174 | 1175 | // move trends 1176 | if (GM_getValue("opt_gt2").leftTrends 1177 | && ((!GM_getValue("opt_gt2").smallSidebars && w > 1350) 1178 | || (GM_getValue("opt_gt2").smallSidebars && w > 1230))) { 1179 | if ($(".gt2-trends").length) $(".gt2-trends").remove() 1180 | 1181 | $(trends).parents("section").parent().parent().parent() 1182 | .detach().addClass("gt2-trends") 1183 | .appendTo(".gt2-left-sidebar") 1184 | } 1185 | 1186 | // show 10 trends 1187 | if (GM_getValue("opt_gt2").show10Trends) { 1188 | if ($(trends).parent().parent().find("> div").length == 7) { 1189 | $(trends).parent().parent().find("> div[role=button]").click() 1190 | } 1191 | } 1192 | } 1193 | 1194 | // wrap trends in anchors 1195 | $(trends).each(function() { 1196 | let $toWrap = $(this).find("> div > div:nth-child(2) > span[dir]") 1197 | if ($toWrap.length) { 1198 | $(this).addClass("gt2-trend-wrapped") 1199 | let txt = $toWrap.text() 1200 | let query = encodeURIComponent($toWrap.text().replace(/%/g, "%25")) 1201 | .replace(/'/g, "%27") 1202 | .replace(/(^\"|\"$)/g, "") 1203 | 1204 | $toWrap.html(`${txt}`) 1205 | } 1206 | }) 1207 | }) 1208 | } 1209 | 1210 | 1211 | function getFollowersYouKnowHTML(screenName, profileID, callback) { 1212 | GM_xmlhttpRequest({ 1213 | method: "GET", 1214 | url: getRequestURL("https://twitter.com/i/api/1.1/friends/following/list.json", { 1215 | include_profile_interstitial_type: 1, 1216 | include_blocking: 1, 1217 | include_blocked_by: 1, 1218 | include_followed_by: 1, 1219 | include_want_retweets: 1, 1220 | include_mute_edge: 1, 1221 | include_can_dm: 1, 1222 | include_can_media_tag: 1, 1223 | skip_status: 1, 1224 | cursor: -1, 1225 | user_id: profileID, 1226 | count: 3, 1227 | with_total_count: true 1228 | }), 1229 | headers: getRequestHeaders(), 1230 | onload: res => { 1231 | if (res.status == 200) { 1232 | 1233 | // followers you know 1234 | let fyk = JSON.parse(res.response) 1235 | 1236 | let fykText 1237 | if (fyk.total_count < 4) { 1238 | fykText = getLocStr(`followedBy${fyk.total_count}`) 1239 | .replace("$p1$", fyk.users.length > 0 ? fyk.users[0].name : "") 1240 | .replace("$p2$", fyk.users.length > 1 ? fyk.users[1].name : "") 1241 | .replace("$p3$", fyk.users.length > 2 ? fyk.users[2].name : "") 1242 | } else { 1243 | fykText = getLocStr("followedBy4Plus") 1244 | .replace("$p1$", fyk.users[0].name) 1245 | .replace("$p2$", fyk.users[1].name) 1246 | .replace("$nr$", fyk.total_count - 2) 1247 | } 1248 | 1249 | let fykImg = "" 1250 | for (let u of fyk.users) { 1251 | fykImg += `${u.name}` 1252 | } 1253 | 1254 | callback(` 1255 | 1256 | ${fykImg} 1257 | 1258 | ${fykText.replaceEmojis()} 1259 | 1260 | 1261 | `) 1262 | } else if (res.status == 401) { 1263 | callback("") 1264 | } 1265 | } 1266 | }) 1267 | } 1268 | 1269 | // display standard information for blocked profile 1270 | function displayBlockedProfileData() { 1271 | let screenName = getPath().split("/")[0].split("?")[0].split("#")[0] 1272 | 1273 | requestUser(screenName, res => { 1274 | let profileData = res.data.user 1275 | 1276 | // get “x persons you follow follow this account” stuff 1277 | getFollowersYouKnowHTML(screenName, profileData.rest_id, fykHTML => { 1278 | 1279 | let pleg = profileData.legacy 1280 | 1281 | // join date 1282 | let joinDate = new Date(pleg.created_at) 1283 | 1284 | let p = { 1285 | description: pleg.description 1286 | .populateWithEntities(pleg.entities.description) 1287 | .replaceEmojis(), 1288 | location: pleg.location != "" ? ` 1289 |
1290 | ${getSvg("location")} 1291 | ${pleg.location.replaceEmojis()} 1292 |
` : null, 1293 | url: pleg.url ? ` 1294 | 1295 | ${getSvg("url")} 1296 | ${pleg.entities.url.urls[0].display_url} 1297 | ` : null, 1298 | joinDate: `
1299 | ${getSvg("calendar")} 1300 | 1301 | ${ 1302 | getLocStr("joinDate") 1303 | .replace("$date$", joinDate.toLocaleDateString(getLang(), { month: "long", year: "numeric" })) 1304 | } 1305 | 1306 |
`, 1307 | birthday: profileData.legacy_extended_profile && profileData.legacy_extended_profile.birthdate ? (() => { 1308 | let bd = profileData.legacy_extended_profile.birthdate 1309 | let bdText 1310 | let date = new Date(Date.UTC(bd.year || 1970, bd.month || 1, bd.day || 1)) 1311 | if (bd.year && !bd.month && !bd.day) { 1312 | bdText = getLocStr("bornYear").replace("$year$", date.toLocaleDateString(getLang(), { year: "numeric"})) 1313 | } else { 1314 | let opt = {} 1315 | if (bd.year) opt.year = "numeric" 1316 | if (bd.month) opt.month = "long" 1317 | if (bd.day) opt.day = "numeric" 1318 | bdText = getLocStr("bornDate").replace("$date$", date.toLocaleDateString(getLang(), opt)) 1319 | } 1320 | 1321 | return ` 1322 |
1323 | ${getSvg("balloon")} 1324 | ${bdText} 1325 |
` 1326 | })() : null 1327 | 1328 | } 1329 | 1330 | // description: add links for mentioned users 1331 | for (let m of p.description.match(/(@[0-9A-Za-z_]+)/g) || []) { 1332 | p.description = p.description.replace(m, `${m}`) 1333 | } 1334 | 1335 | // add profile info 1336 | $("a[href$='/header_photo'] + div > div:nth-child(2)").after(` 1337 |
${p.description}
1338 |
1339 | ${p.location ? p.location : ""} 1340 | ${p.url ? p.url : ""} 1341 | ${p.birthday ? p.birthday : ""} 1342 | ${p.joinDate} 1343 |
1344 | `) 1345 | 1346 | // add followers/following count 1347 | if (!$(`.gt2-blocked-profile-items + div [href$="/following"]`).length) { 1348 | $(".gt2-blocked-profile-items").after(` 1349 |
1350 | 1351 | ${pleg.friends_count.humanizeShort()} ${getLocStr("statsFollowing")} 1352 | 1353 | 1354 | ${pleg.followers_count.humanizeShort()} ${getLocStr("statsFollowers")} 1355 | 1356 |
1357 | `) 1358 | } 1359 | 1360 | // followersYouKnow 1361 | $(".gt2-blocked-profile-items + div").after(fykHTML) 1362 | 1363 | 1364 | // add legacy sidebar profile information 1365 | waitForKeyElements(".gt2-legacy-profile-name", () => { 1366 | if (!$(".gt2-legacy-profile-info .gt2-legacy-profile-fyk").length) { 1367 | $(".gt2-legacy-profile-info .gt2-legacy-profile-items").append(` 1368 | ${p.description ? `
${p.description}
` : ""} 1369 | ${p.location ? `
${p.location}
` : ""} 1370 | ${p.url ? `
${p.url}
` : ""} 1371 | ${p.birthday ? `
${p.birthday}
` : ""} 1372 |
${p.joinDate}
1373 |
${fykHTML}
1374 | `) 1375 | } 1376 | }) 1377 | 1378 | // profile id 1379 | $(".gt2-legacy-profile-info").attr("data-profile-id", profileData.rest_id) 1380 | }) 1381 | }) 1382 | } 1383 | 1384 | 1385 | // removeChild interception 1386 | /* 1387 | Element.prototype.removeChild = (function(fun) { 1388 | return function(child) { 1389 | // if ([ 1390 | // "a[data-testid=AppTabBar_Home_Link]", 1391 | // "a[data-testid=AppTabBar_Notifications_Link]", 1392 | // "a[data-testid=AppTabBar_DirectMessage_Link]" 1393 | // ].some(e => $(child).parent().parent().is(e))) { 1394 | // return child 1395 | // } 1396 | 1397 | return fun.apply(this, arguments) 1398 | } 1399 | }(Element.prototype.removeChild)) 1400 | */ 1401 | 1402 | 1403 | // 1404 | // $("body").on("ended", "video[poster='https://pbs.twimg.com/ext_tw_video_thumb/']", function(e) { 1405 | // e.preventDefault() 1406 | // console.log("test"); 1407 | // console.log(this); 1408 | // }) 1409 | 1410 | 1411 | 1412 | // ################################## 1413 | // # translate tweets in timelime # 1414 | // ################################## 1415 | 1416 | 1417 | // add translate button 1418 | if (!GM_getValue("opt_gt2").hideTranslateTweetButton) { 1419 | waitForKeyElements("[data-testid=tweet] [lang], [data-testid=tweet] + div > div:nth-child(2) [role=link] [lang]", function(e) { 1420 | let $e = $(e) 1421 | let tweetLang = $e.attr("lang") 1422 | let userLang = getLang() 1423 | userLang = userLang == "en-GB" ? "en" : userLang 1424 | if (tweetLang != userLang && tweetLang != "und") { 1425 | $e.first().after(` 1426 |
1427 | ${getLocStr("translateTweet")} 1428 |
1429 | `) 1430 | } 1431 | }) 1432 | } 1433 | 1434 | 1435 | // translate a tweet or LPL bio 1436 | $("body").on("click", ".gt2-translate-tweet, .gt2-legacy-profile-info [data-testid=UserDescription] + [role=button]", function(event) { 1437 | event.preventDefault() 1438 | 1439 | // already translated 1440 | if ($(this).parent().find(".gt2-translated-tweet").length) { 1441 | $(this).addClass("gt2-hidden") 1442 | $(this).parent().find(".gt2-translated-tweet, .gt2-translated-tweet-info").removeClass("gt2-hidden") 1443 | return 1444 | } 1445 | 1446 | let id = $(this).parents("article").find("div[data-testid=tweet]").length 1447 | ? $(this).parents("article").find(`div[data-testid=tweet] > div:nth-child(2) > div:nth-child(1) a[href*='/status/'], 1448 | div[data-testid=tweet] + div > div:nth-child(3) a[href*='/status/']`).attr("href").split("/")[3] 1449 | : null 1450 | 1451 | // embedded tweet 1452 | if ($(this).parents("[role=link]").parents("article").find("[data-testid=tweet]").length) { 1453 | requestTweet(id, res => translateTweet(this, res.quoted_status_id_str)) 1454 | 1455 | // normal tweet with embedded one 1456 | } else if ($(this).parents("article").find("[data-testid=tweet] [role=link] [lang]").length) { 1457 | console.log("aaa"); 1458 | requestTweet(id, res => translateTweet(this, id, res.quoted_status_id_str)) 1459 | 1460 | // normal tweet or bio 1461 | } else { 1462 | translateTweet(this, id) 1463 | } 1464 | }) 1465 | 1466 | 1467 | function translateTweet(e, id, quoteId) { 1468 | let isTweet = $(e).is(".gt2-translate-tweet") 1469 | GM_setValue("tmp_translatedTweetInfo", getLocStr("translatedTweetInfo")) 1470 | 1471 | GM_xmlhttpRequest({ 1472 | method: "GET", 1473 | url: `https://twitter.com/i/api/1.1/strato/column/None/${isTweet ? `tweetId=${id}` : `profileUserId=${$(".gt2-legacy-profile-info").data("profile-id")}`},destinationLanguage=None,translationSource=Some(Google),feature=None,timeout=None,onlyCached=None/translation/service/translate${isTweet ? "Tweet" : "Profile"}`, 1474 | headers: getRequestHeaders(isTweet ? { 1475 | referer: `https://twitter.com/i/status/${id}` 1476 | } : {}), 1477 | onload: function(res) { 1478 | if (res.status == "200") { 1479 | let o = JSON.parse(res.response) 1480 | if (!isTweet) o = o.profileTranslation 1481 | console.log(o) 1482 | let out = o.translation 1483 | 1484 | // handle entities in tweet 1485 | if (o.entities) { 1486 | // remove embedded url if applicable (https://twitter.com/ella_hollywood/status/1395290916303212544) 1487 | if (quoteId && o.entities.urls) { 1488 | let tco = o.entities.urls.find(x => x.expanded_url.endsWith(quoteId)) 1489 | if (tco) { 1490 | out = out.replace(` ${tco.url}`, "") 1491 | o.entities.urls = o.entities.urls.filter(x => !x.expanded_url.endsWith(quoteId)) 1492 | } 1493 | } 1494 | out = out.populateWithEntities(o.entities) 1495 | } 1496 | 1497 | $(e).addClass("gt2-hidden") 1498 | $(e).after(` 1499 |
1500 | ${GM_getValue("tmp_translatedTweetInfo") 1501 | .replace("$lang$", o.localizedSourceLanguage) 1502 | .replace("$source$", ` 1503 | 1504 | ${getSvg("google")} 1505 | 1506 | `) 1507 | } 1508 |
1509 |
1510 | ${out.replaceEmojis()} 1511 |
1512 | `) 1513 | } else { 1514 | console.error("Error occurred while translating.") 1515 | console.error(res) 1516 | } 1517 | } 1518 | }) 1519 | } 1520 | 1521 | 1522 | // hide translation 1523 | $("body").on("click", ".gt2-translated-tweet-info", function(event) { 1524 | event.preventDefault() 1525 | 1526 | $(this).parent().find(".gt2-translated-tweet, .gt2-translated-tweet-info").addClass("gt2-hidden") 1527 | $(this).prevAll(".gt2-translate-tweet, [role=button]").removeClass("gt2-hidden") 1528 | }) 1529 | 1530 | 1531 | 1532 | 1533 | // ######################## 1534 | // # disableAutoRefresh # 1535 | // ######################## 1536 | 1537 | 1538 | // russian numbering 1539 | function getRusShowNew(nr) { 1540 | let end 1541 | let t1 = nr.toString().slice(-1) 1542 | let t2 = nr.toString().slice(-2) 1543 | 1544 | if (t1 == 1) end = "новый твит" 1545 | if (t1 >= 2 && t1 <= 4) end = "новых твита" 1546 | if (t1 == 0 || (t1 >= 5 && t1 <= 9)) end = "новых твитов" 1547 | if (t2 >= 11 && t2 <= 14) end = "новый твит" 1548 | return `Посмотреть ${nr} ${end}` 1549 | } 1550 | 1551 | // add counter for new tweets 1552 | function updateNewTweetDisplay() { 1553 | let nr = $(".gt2-hidden-tweet").length 1554 | let text = nr == 1 ? getLocStr("showNewSingle") : getLocStr("showNewMulti").replace("$", nr) 1555 | 1556 | // exception for russian 1557 | if (getLang() == "ru") { 1558 | text = getRusShowNew(nr) 1559 | } 1560 | 1561 | if (nr) { 1562 | // add button 1563 | if ($(".gt2-show-hidden-tweets").length == 0) { 1564 | if (window.location.href.split("/")[3].startsWith("home")) { 1565 | $("div[data-testid=primaryColumn] > div > div:nth-child(3)").addClass("gt2-show-hidden-tweets") 1566 | } else { 1567 | $("div[data-testid='primaryColumn'] section > div > div > div > div:nth-child(1)").append(` 1568 |
1569 | `) 1570 | } 1571 | } 1572 | $(".gt2-show-hidden-tweets").html(text) 1573 | let t = $("title").text() 1574 | $("title").text(`[${nr}] ${t.startsWith("(") ? t.split(") ")[1] : t.startsWith("[") ? t.split("] ")[1] : t}`) 1575 | } else { 1576 | $(".gt2-show-hidden-tweets").empty().removeClass("gt2-show-hidden-tweets") 1577 | resetTitle() 1578 | } 1579 | } 1580 | 1581 | 1582 | // show new tweets 1583 | $("body").on("click", ".gt2-show-hidden-tweets", () => { 1584 | let topTweet = $("div[data-testid=tweet]").eq(0).find("> div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(1) > a").attr("href") 1585 | GM_setValue("topTweet", topTweet) 1586 | $(".gt2-hidden-tweet").removeClass("gt2-hidden-tweet") 1587 | $(".gt2-hidden-tweet-part").removeClass("gt2-hidden-tweet-part") 1588 | console.log(`topTweet: ${topTweet}`) 1589 | updateNewTweetDisplay() 1590 | }) 1591 | 1592 | 1593 | // change title to display X new tweets 1594 | function resetTitle() { 1595 | let t = $("title").text() 1596 | let notifications = ".gt2-nav-left a[href='/notifications'] > div > div:nth-child(1) > div:nth-child(2)" 1597 | let messages = ".gt2-nav-left a[href='/messages'] > div > div:nth-child(1) > div:nth-child(2)" 1598 | let nr = 0 1599 | if ($(notifications).length) nr += parseInt($(notifications).text()) 1600 | if ($(messages).length) nr += parseInt($(messages).text()) 1601 | $("title").text(`${nr > 0 ? `(${nr}) ` : ""}${t.startsWith("[") ? t.split("] ")[1] : t}`) 1602 | } 1603 | 1604 | 1605 | // observe and hide auto refreshed tweets 1606 | function hideTweetsOnAutoRefresh() { 1607 | let obsTL = new MutationObserver(mutations => { 1608 | mutations.forEach(m => { 1609 | if (m.addedNodes.length == 1) { 1610 | let $t = $(m.addedNodes[0]) 1611 | if ($t.find("div > div > div > div > article div[data-testid=tweet]").length && $t.nextAll().find(`a[href='${GM_getValue("topTweet")}']`).length) { 1612 | if ($t.find("div[data-testid=tweet] > div:nth-child(1) > div:nth-child(2)").length && (!$("> div > div > div > a[href^='/i/status/']").length || $t.next().find("> div > div > div > a[href^='/i/status/']").length)) { 1613 | $t.addClass("gt2-hidden-tweet-part") 1614 | } else { 1615 | console.log($t); 1616 | $t.addClass("gt2-hidden-tweet") 1617 | updateNewTweetDisplay() 1618 | } 1619 | } else if ($t.find("> div > div > div > a[href^='/i/status/']").length) { 1620 | console.log($t); 1621 | $t.addClass("gt2-hidden-tweet-part") 1622 | } 1623 | } 1624 | }) 1625 | }) 1626 | let tlSel = `div[data-testid=primaryColumn] > div > div:nth-child(4) section > div > div > div, 1627 | div[data-testid=primaryColumn] > div > div:nth-child(2) section > div > div > div` 1628 | waitForKeyElements(tlSel, () => { 1629 | // memorize last tweet 1630 | let topTweet = $(tlSel).find("> div:nth-child(1) div[data-testid=tweet] > div:nth-child(2) > div:nth-child(1) > div > div > div:nth-child(1) > a").attr("href") 1631 | GM_setValue("topTweet", topTweet) 1632 | console.log(`topTweet: ${topTweet}`) 1633 | obsTL.observe($(tlSel)[0], { 1634 | childList: true, 1635 | subtree: true 1636 | }) 1637 | }) 1638 | } 1639 | 1640 | 1641 | // keep the site from removing tweets (not working) 1642 | function keepTweetsInTL() { 1643 | let o = Element.prototype.removeChild 1644 | Element.prototype.removeChild = function(child) { 1645 | // check if element is a tweet 1646 | if ($(child).not("[class]") && $(child).find("> div > div > div > div > article > div > div[data-testid=tweet]").length) { 1647 | console.log($(child)[0]) 1648 | return child 1649 | } else { 1650 | return o.apply(this, arguments) 1651 | } 1652 | } 1653 | } 1654 | 1655 | 1656 | 1657 | // ########################## 1658 | // # misc event handlers # 1659 | // ########################## 1660 | 1661 | 1662 | // compose tweet button 1663 | $("body").on("click", ".gt2-nav .gt2-compose", () => { 1664 | $("header a[href='/compose/tweet'] > div").click() 1665 | }) 1666 | 1667 | 1668 | // add elements to navbar dropdow menu 1669 | $("body").on("click", ".gt2-toggle-navbar-dropdown", () => { 1670 | console.log("navbar toggled"); 1671 | let i = getInfo() 1672 | $("header nav > div[data-testid=AppTabBar_More_Menu]").click() 1673 | let more = "div[role=menu][style^='max-height: calc'].r-ipm5af > div > div > div" 1674 | 1675 | waitForKeyElements(`${more} `, () => { 1676 | if ($(more).find("a[href='/explore']").length) return 1677 | let $hr = $(more).find("> div:empty") // seperator line 1678 | $hr.clone().prependTo(more) 1679 | // items from left menu to attach 1680 | let toAttach = [ 1681 | { 1682 | sel: `a[href='/explore']`, 1683 | name: "Explore" 1684 | }, { 1685 | sel: `a[href='/i/bookmarks']`, 1686 | name: "Bookmarks" 1687 | }, { 1688 | sel: `a[href='/${i.screenName}/lists']`, 1689 | name: "Lists" 1690 | }, { 1691 | sel: `a[href='/${i.screenName}']`, 1692 | name: "Profile" 1693 | } 1694 | ] 1695 | for (let e of toAttach) { 1696 | let $tmp = $("header nav").find(e.sel).clone() 1697 | $tmp.children().append(`${getLocStr(`nav${e.name}`)}`) 1698 | $tmp.prependTo(more) 1699 | } 1700 | 1701 | $hr.clone().appendTo(more) 1702 | $(`Logout`).appendTo(more) 1703 | }) 1704 | 1705 | }) 1706 | 1707 | 1708 | // acc switcher dropdown 1709 | $("body").on("click", ".gt2-toggle-acc-switcher-dropdown", function() { 1710 | $("body").addClass("gt2-acc-switcher-active") 1711 | $("div[data-testid=SideNav_AccountSwitcher_Button]").click() 1712 | 1713 | // change dropdown position 1714 | $(".gt2-style-acc-switcher-dropdown").remove() 1715 | let pos = $(".gt2-toggle-acc-switcher-dropdown")[0].getBoundingClientRect() 1716 | $("html").prepend(` 1717 | 1724 | `) 1725 | }) 1726 | 1727 | 1728 | // remove class on next click 1729 | $("body").on("click", ":not(.gt2-toggle-acc-switcher-dropdown), :not(div[data-testid=SideNav_AccountSwitcher_Button])", function() { 1730 | setTimeout(function () { 1731 | if (!$("a[href='/account/add']").length) { 1732 | $("body").removeClass("gt2-acc-switcher-active") 1733 | } 1734 | }, 2000) 1735 | }) 1736 | 1737 | 1738 | // expand the “What’s happening?” tweet field (minimized by default) 1739 | $("body").on("click", "div[data-testid=primaryColumn] > div > div:nth-child(2)", e => $(e.currentTarget).addClass("gt2-compose-large")) 1740 | 1741 | 1742 | // loggedOut nightmode 1743 | $("body").on("click", ".gt2-toggle-lo-nightmode", () => { 1744 | let nm = document.cookie.match(/night_mode=1/) ? 0 : 1 1745 | // delete old cookie 1746 | document.cookie = "night_mode=; Max-Age=0;" 1747 | // create new cookie 1748 | let d = new Date() 1749 | d.setDate(d.getDate() + 500) 1750 | document.cookie = `night_mode=${nm}; expires=${d.toUTCString()}; path=/; domain=.twitter.com` 1751 | window.location.reload() 1752 | }) 1753 | 1754 | 1755 | // close sidebar notice 1756 | $("body").on("click", ".gt2-sidebar-notice-close", function() { 1757 | if ($(this).parents(".gt2-sidebar-notice").hasClass("gt2-update-notice")) { 1758 | GM_setValue(`sb_notice_ack_update_${GM_info.script.version}`, true) 1759 | } 1760 | $(this).parents(".gt2-sidebar-notice").remove() 1761 | }) 1762 | 1763 | 1764 | // remove blocked profile stuff on unblock 1765 | $("body").on("click", `div[data-testid=placementTracking] div[data-testid$="-unblock"]`, () => $("[class^=gt2-blocked-profile]").remove()) 1766 | 1767 | 1768 | // [LPL] unusual activity button: make elements clickable again 1769 | $(document).on("click", `.gt2-profile-not-found [data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(2) > div[role=button]`, () => $("body").removeClass("gt2-profile-not-found")) 1770 | 1771 | 1772 | // expand t.co shortlinks (tweets) 1773 | $(document).on("mouseover", `.gt2-opt-expand-tco-shortlinks div:not([data-testid=placementTracking]) > div > article [data-testid=tweet]:not(.gt2-tco-expanded), 1774 | .gt2-opt-expand-tco-shortlinks.gt2-page-tweet [data-testid=primaryColumn] section > h1 + div > div > div:nth-child(1) article:not(.gt2-tco-expanded)`, function() { 1775 | let $tweet = $(this) 1776 | $tweet.addClass("gt2-tco-expanded") 1777 | 1778 | // exit if tweet has no links 1779 | if (!$tweet.find(`a[href^="http://t.co"], a[href^="https://t.co"], [data-testid="card.wrapper"]`).length) return 1780 | 1781 | let id = $tweet.is("article") 1782 | ? getPath().split("/")[2].split("?")[0].split("#")[0] 1783 | : $tweet.find(`time`).parent().attr("href").split("/status/")[1] 1784 | 1785 | requestTweet(id, res => { 1786 | $tweet.find(`a[href^="http://t.co"], a[href^="https://t.co"]`).each(function() { 1787 | $(this).attr("href", res.entities.urls.find(e => e.url == $(this).attr("href").split("?")[0]).expanded_url) 1788 | }) 1789 | $tweet.find(`[data-testid="card.layoutSmall.media"]`).each(function() { 1790 | console.log(res); 1791 | $(this).next().wrap(``) 1792 | }) 1793 | }) 1794 | }) 1795 | 1796 | 1797 | // expand t.co shortlinks (profile, not legacy) 1798 | $(document).on("mouseover", `.gt2-opt-expand-tco-shortlinks.gt2-page-profile:not(.gt2-opt-legacy-profile) [data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(1):not(.gt2-tco-expanded), .gt2-opt-expand-tco-shortlinks [data-testid=UserCell]`, function() { 1799 | let $profile = $(this) 1800 | $profile.addClass("gt2-tco-expanded") 1801 | // exit if profile has no links 1802 | if (!$profile.find(`a[href^="http://t.co"], a[href^="https://t.co"]`).length) return 1803 | 1804 | let screenName = $profile.is("[data-testid=UserCell]") 1805 | ? $profile.find("> div > div:nth-child(2) > div:nth-child(1) a").attr("href").slice(1) 1806 | : getPath().split("/")[0].split("?")[0].split("#")[0] 1807 | 1808 | requestUser(screenName, res => { 1809 | let ent = res.data.user.legacy.entities 1810 | let urls = [] 1811 | if (ent.description) urls.push(...ent.description.urls) 1812 | if (ent.url) urls.push(...ent.url.urls) 1813 | $profile.find(`a[href^="http://t.co"], a[href^="https://t.co"]`).each(function() { 1814 | $(this).attr("href", urls.find(e => e.url == $(this).attr("href").split("?")[0].split("#")[0]).expanded_url) 1815 | }) 1816 | }) 1817 | }) 1818 | 1819 | 1820 | // block/unblock account on holding follow button for 3 seconds 1821 | if (GM_getValue("opt_gt2").enableQuickBlock) { 1822 | let qbOffer 1823 | $("body").on("mouseover", `[data-testid$="-follow"]:not([data-gt2-qb-state])`, e => { 1824 | let $b = $(e.target).parents(`[data-testid$="-follow"]`) 1825 | $b.attr("data-gt2-qb-state", "offer-pending") 1826 | qbOffer = setTimeout(() => { 1827 | $b.attr("data-gt2-qb-state", "offer") 1828 | $b.find("> div > span").append(` 1829 | ${getLocStr("qbBlock")} 1830 | ${getLocStr("qbBlocked")} 1831 | ${getLocStr("qbUnblock")} 1832 | `) 1833 | }, 3e3) 1834 | }) 1835 | $("body").on("click", `[data-testid$="-follow"][data-gt2-qb-state=offer]`, e => { 1836 | e.stopImmediatePropagation() 1837 | let $b = $(e.target).parents(`[data-testid$="-follow"]`) 1838 | let user_id = $b.attr("data-testid").slice(0, -7) 1839 | blockUser(user_id, true, () => { 1840 | console.log(`quickblock: ${user_id}`) 1841 | $b.attr("data-gt2-qb-state", "blocked") 1842 | }) 1843 | }) 1844 | $("body").on("click", `[data-testid$="-follow"][data-gt2-qb-state=blocked]`, e => { 1845 | e.stopImmediatePropagation() 1846 | let $b = $(e.target).parents(`[data-testid$="-follow"]`) 1847 | let user_id = $b.attr("data-testid").slice(0, -7) 1848 | blockUser(user_id, false, () => { 1849 | console.log(`quickunblock: ${user_id}`) 1850 | $b.removeAttr("data-gt2-qb-state") 1851 | $b.find("[class^=gt2-qb]").remove() 1852 | }) 1853 | }) 1854 | $("body").on("mouseleave", `[data-testid$="-follow"][data-gt2-qb-state^=offer], 1855 | [data-testid$="-unfollow"][data-gt2-qb-state^=offer]`, e => { 1856 | let $b = $(e.target).parents(`[data-testid$="-follow"]`) 1857 | $b.removeAttr("data-gt2-qb-state") 1858 | $b.find("[class^=gt2-qb]").remove() 1859 | clearTimeout(qbOffer) 1860 | }) 1861 | } 1862 | 1863 | 1864 | 1865 | // ######################## 1866 | // # display settings # 1867 | // ######################## 1868 | 1869 | 1870 | // high contrast 1871 | $("body").on("click", `[data-testid="accessibilityScreen"] > div:nth-child(3) label [aria-labelledby]`, function() { 1872 | GM_setValue("opt_display_highContrast", !$(this).find("input").is("[checked]")) 1873 | updateCSS() 1874 | }) 1875 | 1876 | 1877 | // user color 1878 | waitForKeyElements(`body:not(.gt2-opt-color-override) [data-testid=SideNav_NewTweet_Button]`, e => { 1879 | let userColor = $(e).css("background-color") 1880 | if (userColor != GM_getValue("opt_display_userColor")) { 1881 | GM_setValue("opt_display_userColor", userColor) 1882 | updateCSS() 1883 | } 1884 | }) 1885 | 1886 | // background color 1887 | new MutationObserver(mut => { 1888 | mut.forEach(m => { 1889 | let bgColor = m.target[m.attributeName]["background-color"] 1890 | if (m.oldValue && bgColor != "") { 1891 | GM_setValue("opt_display_bgColor", bgColor) 1892 | updateCSS() 1893 | } 1894 | }) 1895 | }).observe($("body")[0], { 1896 | attributes: true, 1897 | attributeOldValue: true, 1898 | attributeFilter: ["style"] 1899 | }) 1900 | 1901 | 1902 | // font increment 1903 | new MutationObserver(mut => { 1904 | mut.forEach(m => { 1905 | let fs = m.target[m.attributeName]["font-size"] 1906 | let fsOld = m.oldValue.match(/font-size: (\d+px);/) 1907 | if (fsOld && fs != "" && fs != fsOld[1]) { 1908 | GM_setValue("opt_display_fontSize", fs) 1909 | updateCSS() 1910 | } 1911 | }) 1912 | }).observe($("html")[0], { 1913 | attributes: true, 1914 | attributeOldValue: true, 1915 | attributeFilter: ["style"] 1916 | }) 1917 | 1918 | 1919 | // minimize DMDrawer if hideMessageBox is set 1920 | if (GM_getValue("opt_gt2").hideMessageBox) { 1921 | waitForKeyElements(`.gt2-opt-hide-message-box [data-testid=DMDrawer] path[d^="M12 19.344l-8.72"]`, e => { 1922 | console.log("Minimized DMDrawer") 1923 | $(e).parents("[role=button]").click() 1924 | }) 1925 | } 1926 | 1927 | 1928 | // hide timeline follow suggestions 1929 | if (GM_getValue("opt_gt2").hideFollowSuggestions) { 1930 | function hideTLFS($p) { 1931 | if (!$p) return $p 1932 | if ($p.prev().length) { 1933 | $p = $p.prev() 1934 | if ($p.find("article").length) return 1935 | $p.addClass("gt2-hidden") 1936 | } else { 1937 | if (window.scrollY < 500) return 1938 | setTimeout(() => { 1939 | $p = hideTLFS($p) 1940 | }, 100) 1941 | } 1942 | return $p 1943 | } 1944 | 1945 | // small follow topic above tweets 1946 | // if ((GM_getValue("opt_gt2").hideFollowSuggestionsSel & 1) == 1) { 1947 | // waitForKeyElements(`[data-gt2-path=home] [data-testid=primaryColumn] section article > div > div > div > div:not([data-testid=tweet]) > div > div > div`, e => $(e).addClass("gt2-hidden")) 1948 | // } 1949 | 1950 | // big follow boxes 1951 | waitForKeyElements( 1952 | ["topics/picker", "connect_people", "lists/suggested"] 1953 | .filter((e, i) => (GM_getValue("opt_gt2").hideFollowSuggestionsSel & Math.pow(2, i)) == Math.pow(2, i)) 1954 | .map(e => `[data-testid=primaryColumn] section [href^="/i/${e}"]`) 1955 | .join(", "), e => { 1956 | 1957 | let $p = $(e).parent().parent().addClass("gt2-hidden") 1958 | if ($p.next().length) $p.next().addClass("gt2-hidden") 1959 | if ($p.next().next().find("div > div:empty").length) $p.next().next().addClass("gt2-hidden") 1960 | for (let i=0; i < 6; i++) { 1961 | $p = hideTLFS($p) 1962 | } 1963 | }) 1964 | } 1965 | 1966 | 1967 | // do not colorOverride these elements (reply/like/retweet/share on tweets and verified badge) 1968 | waitForKeyElements(`[data-testid=tweet] [role=group]`, e => $(e).find("[role=button] *").attr("data-gt2-color-override-ignore", "")) 1969 | waitForKeyElements(`path[d^="M22.5 12.5c0-1.58-.875"]`, e => $(e).parents("svg").attr("data-gt2-color-override-ignore", "")) 1970 | waitForKeyElements(`[data-gt2-path-modal="i/display"] div:nth-last-child(2) > div > [role=radiogroup], 1971 | [data-gt2-path="settings/display"] div:nth-last-child(2) > div > [role=radiogroup]`, e => { 1972 | let $e = $(e).parents("[aria-labelledby]") 1973 | $e.find("[name*=COLOR_PICKER]").parents("label").parent().find("*").attr("data-gt2-color-override-ignore", "") 1974 | $e.find("[dir]:nth-child(3) + div:not([dir]) > div > div > div[dir] + div *").attr("data-gt2-color-override-ignore", "") 1975 | }) 1976 | 1977 | // do not add dividers to tweet inline threads 1978 | waitForKeyElements(`[data-testid=tweet] > div:nth-child(1) > div:nth-child(2):empty`, e => $(e).parents(`[style*="position: absolute"]`).children().attr("data-gt2-divider-add-ignore", "")) 1979 | 1980 | // color notifications bell 1981 | waitForKeyElements(`path[d^="M23.61.15c-.375"]`, e => $(e).parents("[role=button]").attr("data-gt2-bell-full-color", "")) 1982 | waitForKeyElements(`path[d^="M23.24 3.26h-2.425V"]`, e => $(e).parents("[role=button]").removeAttr("data-gt2-bell-full-color", "")) 1983 | 1984 | 1985 | // ################ 1986 | // # Update CSS # 1987 | // ################ 1988 | 1989 | 1990 | // get scrollbar width (https://stackoverflow.com/q/8079187) 1991 | function getScrollbarWidth() { 1992 | if ($("html").is("[data-minimalscrollbar]")) { 1993 | return 0 1994 | } 1995 | let $t = $("
").css({ 1996 | position: "absolute", 1997 | top: "-100px", 1998 | overflowX: "hidden", 1999 | overflowY: "scroll" 2000 | }).prependTo("body") 2001 | let out = $t[0].offsetWidth - $t[0].clientWidth 2002 | $t.remove() 2003 | return out 2004 | } 2005 | 2006 | 2007 | // update inserted CSS 2008 | function updateCSS() { 2009 | // bgColor schemes 2010 | let bgColors = { 2011 | // default (white) 2012 | "rgb(255, 255, 255)": { 2013 | bg: "#e6ecf0", 2014 | elem: "rgb(255, 255, 255)", 2015 | elemSel: "rgb(247, 249, 250)", 2016 | gray: "rgb(91, 112, 131)", 2017 | grayDark: "#e6ecf0", 2018 | grayDark2: "rgb(196, 207, 214)", 2019 | grayLight: "rgb(101, 119, 134)", 2020 | navbar: "#ffffff", 2021 | text: "rgb(20, 23, 26)", 2022 | text2: "white", 2023 | shadow: "rgba(101, 119, 134, 0.15)", 2024 | backdrop: "rgba(0, 0, 0, 0.4)" 2025 | }, 2026 | // dim 2027 | "rgb(21, 32, 43)": { 2028 | bg: "#10171e", 2029 | elem: "rgb(21, 32, 43)", 2030 | elemSel: "rgb(25, 39, 52)", 2031 | gray: "rgb(101, 119, 134)", 2032 | grayDark: "#38444d", 2033 | grayDark2: "rgb(61, 84, 102)", 2034 | grayLight: "rgb(136, 153, 166)", 2035 | navbar: "#1c2938", 2036 | text: "rgb(255, 255, 255)", 2037 | text2: "white", 2038 | shadow: "rgba(136, 153, 166, 0.15)", 2039 | backdrop: "rgba(91, 112, 131, 0.4)" 2040 | }, 2041 | // lightsOut 2042 | "rgb(0, 0, 0)": { 2043 | bg: "#000000", 2044 | elem: "#000000", 2045 | elemSel: "rgb(21, 24, 28)", 2046 | gray: "#657786", 2047 | grayDark: "#38444d", 2048 | grayDark2: "rgb(47, 51, 54)", 2049 | grayLight: "rgb(110, 118, 125)", 2050 | navbar: "rgb(21, 24, 28)", 2051 | text: "rgb(217, 217, 217)", 2052 | text2: "white", 2053 | shadow: "rgba(255, 255, 255, 0.15)", 2054 | backdrop: "rgba(91, 112, 131, 0.4)" 2055 | } 2056 | } 2057 | 2058 | // high contrast color overrides 2059 | let bgColorsHC = { 2060 | // default (white) 2061 | "rgb(255, 255, 255)": { 2062 | gray: "rgb(59, 76, 92)", 2063 | grayDark: "rgb(170, 184, 194)", 2064 | grayLight: "rgb(59, 76, 92)", 2065 | text: "rgb(20, 29, 38)" 2066 | }, 2067 | // dim 2068 | "rgb(21, 32, 43)": { 2069 | elemSel: "rgb(24, 36, 48)", 2070 | gray: "rgb(184, 203, 217)", 2071 | grayDark: "rgb(56, 68, 88)", 2072 | grayLight: "rgb(184, 203, 217)", 2073 | text2: "rgb(15, 20, 25)" 2074 | }, 2075 | // lightsOut 2076 | "rgb(0, 0, 0)": { 2077 | bg: "rgb(5, 5, 5)", 2078 | elem: "rgb(5, 5, 5)", 2079 | elemSel: "rgb(14, 16, 18)", 2080 | gray: "rgb(146, 156, 166)", 2081 | grayDark: "rgb(61, 65, 69)", 2082 | grayLight: "rgb(146, 156, 166)", 2083 | text: "rgb(255, 255, 255)", 2084 | text2: "rgb(15, 20, 25)" 2085 | } 2086 | } 2087 | 2088 | let baseColors = { 2089 | // normal white hc // dim/lo hc 2090 | blue: ["29, 161, 242", "38, 74, 157", "112, 200, 255"], 2091 | green: ["23, 191, 99", "9, 102, 51", "102, 211, 151"], 2092 | red: ["224, 36, 94", "159, 12, 58", "240, 152, 179"], 2093 | redDark: ["202, 32, 85", "169, 36, 78", "216, 137, 161"], 2094 | yellow: ["255, 173, 31", "121, 80, 11", "255, 203, 112"] 2095 | } 2096 | 2097 | // initialize with the current settings 2098 | if (GM_getValue("gt2_initialized") == undefined && isLoggedIn()) { 2099 | waitForKeyElements(`h2 > a[href="/i/keyboard_shortcuts"] span`, () => { 2100 | GM_setValue("opt_display_userColor", $(`a[href="/i/keyboard_shortcuts"]`).css("color")) 2101 | GM_setValue("opt_display_bgColor", $("body").css("background-color")) 2102 | GM_setValue("opt_display_highContrast", false) 2103 | GM_setValue("opt_display_fontSize", $("html").css("font-size")) 2104 | GM_setValue("gt2_initialized", true) 2105 | window.location.reload() 2106 | }) 2107 | 2108 | } else { 2109 | // add gt2-options to body for the css to take effect 2110 | for (let [key, val] of Object.entries(GM_getValue("opt_gt2"))) { 2111 | if (val) $("body").addClass(`gt2-opt-${key.toKebab()}`) 2112 | } 2113 | 2114 | // remove unneeded classes 2115 | $("body").removeClass("gt2-acc-switcher-active") 2116 | 2117 | // delete old stylesheet 2118 | if ($(".gt2-style").length) { 2119 | $(".gt2-style").remove() 2120 | } 2121 | 2122 | let opt_display_bgColor = GM_getValue("opt_display_bgColor") 2123 | let opt_display_highContrast = GM_getValue("opt_display_highContrast") 2124 | let opt_display_fontSize = GM_getValue("opt_display_fontSize") 2125 | let opt_display_userColor = GM_getValue("opt_display_userColor") 2126 | 2127 | // options to set if not logged in 2128 | if (!isLoggedIn()) { 2129 | // get bgColor from cookie 2130 | opt_display_bgColor = document.cookie.match(/night_mode=1/) ? "rgb(21, 32, 43)" : "rgb(255, 255, 255)" 2131 | opt_display_highContrast = false 2132 | opt_display_fontSize = "15px" 2133 | opt_display_userColor = "rgb(29, 161, 242)" 2134 | } 2135 | 2136 | // highContrast lightsOut 2137 | if (opt_display_bgColor == "rgb(5, 5, 5)") opt_display_bgColor = "rgb(0, 0, 0)" 2138 | 2139 | // insert new stylesheet 2140 | $("html").prepend(` 2141 | 2162 | ` 2163 | ) 2164 | } 2165 | 2166 | // add navbar 2167 | if (!$("gt2-nav").length) { 2168 | if (isLoggedIn()) { 2169 | addNavbar() 2170 | } else { 2171 | addNavbarLoggedOut() 2172 | } 2173 | } 2174 | } 2175 | 2176 | 2177 | 2178 | // ############## 2179 | // # resizing # 2180 | // ############## 2181 | 2182 | 2183 | // things to do when resizing the window 2184 | $(window).on("resize", () => { 2185 | let w = window.innerWidth 2186 | if ((!GM_getValue("opt_gt2").smallSidebars && w <= 1350) || 2187 | ( GM_getValue("opt_gt2").smallSidebars && w <= 1230)) { 2188 | // move dash profile to right sidebar 2189 | $(".gt2-dashboard-profile") 2190 | .prependTo("div[data-testid=sidebarColumn] > div > div:nth-child(2) > div > div > div") 2191 | // remove trends 2192 | $(".gt2-trends").remove() 2193 | } else { 2194 | $(".gt2-dashboard-profile").prependTo(".gt2-left-sidebar") 2195 | } 2196 | }) 2197 | 2198 | 2199 | 2200 | // ############### 2201 | // # scrolling # 2202 | // ############### 2203 | 2204 | 2205 | // things to do when scrolling 2206 | ;(function() { 2207 | let prev = window.pageYOffset 2208 | let bannerHeight = (window.innerWidth - getScrollbarWidth()) / 3 - 15 2209 | 2210 | $(window).on("scroll", () => { 2211 | let curr = window.pageYOffset 2212 | 2213 | // prevent scroll to top 2214 | if (prev > 1500 && curr == 0) { 2215 | window.scroll(0, prev) 2216 | return 2217 | } 2218 | 2219 | if (prev < curr) { 2220 | $("body").addClass("gt2-scrolled-down") 2221 | } else { 2222 | $("body").removeClass("gt2-scrolled-down") 2223 | } 2224 | prev = curr 2225 | 2226 | // legacy profile banner parallax 2227 | if (curr > bannerHeight) { 2228 | $("body").addClass("gt2-scrolled-down-banner") 2229 | } else { 2230 | $("body").removeClass("gt2-scrolled-down-banner") 2231 | $(".gt2-legacy-profile-banner img").css("transform", `translate3d(0px, ${curr / bannerHeight * 42}%, 0px)`) 2232 | } 2233 | }) 2234 | }()) 2235 | 2236 | 2237 | 2238 | // ################ 2239 | // # URL change # 2240 | // ################ 2241 | 2242 | 2243 | function beforeUrlChange(path) { 2244 | // [LPL] reattach buttons to original position 2245 | if (!_isModal(path)) { 2246 | let $b = $("div[data-testid=primaryColumn] > div > div:nth-child(2) > div > div > div:nth-child(1) > div:nth-child(2) > div:nth-child(1)") 2247 | if (!$b.find("> div").length && $("body").attr("data-gt2-prev-path") != path) { 2248 | $(".gt2-legacy-profile-nav-right > div").appendTo($b) 2249 | } 2250 | } 2251 | } 2252 | 2253 | 2254 | // path helper functions 2255 | function _onPage(path, ...top) { 2256 | return top.some(e => e == path.split("/")[0]) 2257 | } 2258 | function _onSubPage(path, top, sub) { 2259 | return (top == null ? true : _onPage(path, top)) && path.includes("/") && sub.some(e => e == path.split("/")[1]) 2260 | } 2261 | function _isModal(path) { 2262 | return _onSubPage(path, "i", ["display", "keyboard_shortcuts", "flow"]) 2263 | || _onSubPage(path, "settings", ["trends", "profile"]) 2264 | || _onSubPage(path, "compose", ["tweet"]) 2265 | || _onSubPage(path, "account", ["add", "switch"]) 2266 | || _onPage(path, "search-advanced") 2267 | || path.match(/\/(photo|video)\/\d\/?$/) 2268 | } 2269 | 2270 | 2271 | // stuff to do when url changes 2272 | function urlChange(changeType, changePath) { 2273 | let path = () => (changePath || getPath()).split("?")[0].split("#")[0] 2274 | let onPage = (...top) => _onPage(path(), ...top) 2275 | let onSubPage = (top, sub) => _onSubPage(path(), top, sub) 2276 | let isModal = _isModal(path()) 2277 | 2278 | console.log(`[${changeType}]${isModal ? " [modal]" : ""} ${path()}`) 2279 | 2280 | 2281 | $("body").attr(`data-gt2-path${isModal ? "-modal" : ""}`, path()) 2282 | let $realPath = $("link[hreflang=default][data-rh=true]") 2283 | if ($realPath.length) $("body").attr("data-gt2-path", $realPath.attr("href")) 2284 | 2285 | // do a reload on these pages 2286 | if (onPage("login") || (!isLoggedIn() && onPage(""))) { 2287 | window.location.reload() 2288 | } 2289 | 2290 | 2291 | // update css 2292 | if (!$("body").hasClass("gt2-css-inserted")) { 2293 | updateCSS() 2294 | $("body").addClass("gt2-css-inserted") 2295 | } 2296 | 2297 | 2298 | let mainView = "main > div > div > div" 2299 | waitForKeyElements(mainView, () => { 2300 | // insert left sidebar 2301 | if (!$(".gt2-left-sidebar").length) { 2302 | $(mainView).prepend(`
`) 2303 | } 2304 | 2305 | // on error page 2306 | if ($(mainView).find("h1[data-testid=error-detail]").length && !path().startsWith("settings/gt2")) { 2307 | $("body").addClass("gt2-page-error") 2308 | } else if (!isModal) { 2309 | $("body").removeClass("gt2-page-error") 2310 | } 2311 | 2312 | if (onPage("settings")) { 2313 | waitForKeyElements(`main a[href="/settings/about"]`, addSettingsToggle) 2314 | if (path().startsWith("settings/gt2")) { 2315 | addSettings() 2316 | changeSettingsTitle() 2317 | } 2318 | } 2319 | }) 2320 | 2321 | 2322 | // add navbar 2323 | if ($("body").attr("data-gt2-prev-path") == "i/moment_maker") $(".gt2-nav").remove() 2324 | if (!$(".gt2-nav").length) { 2325 | if (isLoggedIn()) { 2326 | addNavbar() 2327 | } else { 2328 | addNavbarLoggedOut() 2329 | } 2330 | } 2331 | 2332 | // highlight current location in left bar 2333 | if (!isModal) { 2334 | $(`.gt2-nav-left > a`).removeClass("active") 2335 | $(`.gt2-nav-left > a[href^='/${path().split("/")[0]}']`).addClass("active") 2336 | } 2337 | 2338 | // hide/add search 2339 | if (onPage("search", "explore")) { 2340 | $(".gt2-search").empty() 2341 | $("body").removeClass("gt2-search-added") 2342 | } else if (!isModal) { 2343 | addSearch() 2344 | } 2345 | 2346 | if (!isLoggedIn()) { 2347 | $("body").addClass("gt2-not-logged-in") 2348 | } 2349 | 2350 | 2351 | // handle stuff in sidebars 2352 | handleTrends() 2353 | if (GM_getValue("opt_gt2").hideFollowSuggestions) { 2354 | let sel = GM_getValue("opt_gt2").hideFollowSuggestionsSel 2355 | 2356 | // topic suggestions 2357 | if ((sel & 1) == 1) waitForKeyElements(`div[data-testid=sidebarColumn] section [href^="/i/topics/"]`, e => $(e).parents("section").parent().parent().remove()) 2358 | 2359 | // user suggestions (Who to follow, You might like) 2360 | if ((sel & 2) == 2) waitForKeyElements(`div[data-testid=sidebarColumn] aside [data-testid=UserCell]`, e => $(e).parents("aside").parent().remove()) 2361 | } 2362 | 2363 | 2364 | // settings 2365 | if (onPage("settings") && !isModal) { 2366 | if (path().startsWith("settings/gt2")) { 2367 | } else { 2368 | if (window.innerWidth < 1005) { 2369 | $("main section").remove() 2370 | } 2371 | $(".gt2-settings-header, .gt2-settings").remove() 2372 | } 2373 | } else if (!isModal) { 2374 | $(".gt2-settings-header, .gt2-settings").remove() 2375 | } 2376 | 2377 | 2378 | // tweet 2379 | if (onSubPage(null, ["status"])) { 2380 | $("body").addClass("gt2-page-tweet") 2381 | // scroll up on load 2382 | waitForKeyElements("[data-testid=tweet] + div [href$=source-labels]", () => window.scroll(0, window.pageYOffset - 56.79999923706055)) 2383 | } else if (!isModal) { 2384 | $("body").removeClass("gt2-page-tweet") 2385 | } 2386 | 2387 | 2388 | // notifications 2389 | // if (onPage("notifications")) { 2390 | // $("body").on("auxclick", `[data-testid=primaryColumn] section > div > div > div:not(.gt2-handled)`, function(e) { 2391 | // e.preventDefault() 2392 | // e.stopImmediatePropagation() 2393 | // console.log(this); 2394 | // }) 2395 | // $("body").on("mouseover", `[data-testid=primaryColumn] section > div > div > div`, function(e) { 2396 | // console.log("a"); 2397 | // $(e).addClass("gt2-handled") 2398 | // $(e).off("mousedown") 2399 | // }) 2400 | // } 2401 | 2402 | 2403 | // sidebar 2404 | let sidebarContent = [] 2405 | 2406 | // update changelog 2407 | if (!GM_getValue(`sb_notice_ack_update_${GM_info.script.version}`) 2408 | && GM_getValue("opt_gt2").updateNotifications) { 2409 | sidebarContent.push(getUpdateNotice()) 2410 | } 2411 | sidebarContent.push(getDashboardProfile()) 2412 | 2413 | 2414 | // assume profile page 2415 | if (!isModal) { 2416 | if (!(onPage("", "explore", "home", "hashtag", "i", "messages", "notifications", "places", "search", "settings") 2417 | || onSubPage(null, ["followers", "followers_you_follow", "following", "lists", "moments", "status", "topics"]))) { 2418 | $("body").addClass("gt2-page-profile").removeClass("gt2-profile-not-found") 2419 | $("[class^=gt2-blocked-profile-]").remove() 2420 | $(".gt2-tco-expanded").removeClass("gt2-tco-expanded") 2421 | if (GM_getValue("opt_gt2").legacyProfile) { 2422 | if ($("body").attr("data-gt2-prev-path") != path()) { 2423 | $("a[href$='/photo'] img").data("alreadyFound", false) 2424 | } 2425 | rebuildLegacyProfile() 2426 | } 2427 | if (GM_getValue("opt_gt2").leftMedia 2428 | && ((!GM_getValue("opt_gt2").smallSidebars && window.innerWidth > 1350) 2429 | || (GM_getValue("opt_gt2").smallSidebars && window.innerWidth > 1230))) { 2430 | 2431 | waitForKeyElements("[data-testid=sidebarColumn] a:nth-child(1) [data-testid=tweetPhoto]", e => { 2432 | if ($(".gt2-profile-media").length) $(".gt2-profile-media").remove() 2433 | let $mediaContainer = $(e).parents("a[role=link]").parent().parent().parent().parent().parent() 2434 | if ($mediaContainer.parent().children().length == 1) $mediaContainer = $mediaContainer.parent() 2435 | $mediaContainer.detach().addClass("gt2-profile-media") 2436 | .appendTo(".gt2-left-sidebar") 2437 | }) 2438 | 2439 | } 2440 | } else { 2441 | $("body").removeClass("gt2-page-profile") 2442 | $(".gt2-legacy-profile-banner, .gt2-legacy-profile-nav").remove() 2443 | $(".gt2-legacy-profile-info").remove() 2444 | } 2445 | } 2446 | 2447 | 2448 | // add elements to sidebar 2449 | addToSidebar(sidebarContent) 2450 | 2451 | 2452 | // blocked profile page 2453 | waitForKeyElements(`div[data-testid=placementTracking] div[data-testid$="-unblock"], 2454 | [data-testid=emptyState] [href="https://support.twitter.com/articles/20172060"]`, displayBlockedProfileData) 2455 | 2456 | 2457 | // disableAutoRefresh 2458 | if (GM_getValue("opt_gt2").disableAutoRefresh && 2459 | (path().split("/")[0] == "home" || path().match(/^[^\/]+\/lists/)) ) { 2460 | hideTweetsOnAutoRefresh() 2461 | } 2462 | 2463 | 2464 | // force latest 2465 | if (GM_getValue("opt_gt2").forceLatest && path().split("/")[0] == "home") { 2466 | forceLatest() 2467 | } 2468 | 2469 | if (!isModal) $("body").attr("data-gt2-prev-path", path()) 2470 | } 2471 | urlChange("init") 2472 | 2473 | 2474 | // run urlChange() when history changes 2475 | // https://github.com/Bl4Cc4t/GoodTwitter2/issues/96 2476 | const exportFunc = typeof exportFunction === "function" ? exportFunction : (fn => fn) 2477 | const pageWindow = unsafeWindow.wrappedJSObject || unsafeWindow 2478 | const pageHistory = pageWindow.History.prototype 2479 | 2480 | const origPush = exportFunc(pageHistory.pushState, pageWindow) 2481 | pageHistory.pushState = exportFunc(function () { 2482 | let path = arguments[2].slice(1) 2483 | beforeUrlChange(path) 2484 | origPush.apply(this, arguments) 2485 | urlChange("push", path) 2486 | }, pageWindow) 2487 | 2488 | const origRepl = exportFunc(pageHistory.replaceState, pageWindow) 2489 | pageHistory.replaceState = exportFunc(function () { 2490 | let path = arguments[2].slice(1) 2491 | beforeUrlChange(path) 2492 | origRepl.apply(this, arguments) 2493 | urlChange("replace", path) 2494 | }, pageWindow) 2495 | 2496 | window.addEventListener("popstate", function() { 2497 | beforeUrlChange(getPath()) 2498 | urlChange("pop", getPath()) 2499 | }) 2500 | 2501 | })(jQuery, waitForKeyElements) 2502 | -------------------------------------------------------------------------------- /love.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dimdenGD/Twitter2015/bca9a31c208739de8efde60aca710c2740845423/love.png -------------------------------------------------------------------------------- /twitter2015.user.css: -------------------------------------------------------------------------------- 1 | /* ==UserStyle== 2 | @name Twitter 2015 3 | @author dimden.dev 4 | @description Returns great Twitter's style from 2015. Requires GoodTwitter2! 5 | @namespace github.com/dimdenGD/Twitter2015 6 | @version 1.0.0 7 | @license MIT 8 | ==/UserStyle== */ 9 | 10 | @-moz-document domain("twitter.com") { 11 | .r-1niwhzg::before { 12 | color: #ccd6dd; 13 | } 14 | div[data-testid="unlike"] .r-1niwhzg::before { 15 | color: rgb(224, 36, 94); 16 | } 17 | 18 | div[data-testid="unretweet"] .r-1niwhzg::before { 19 | color: rgb(23, 191, 99); 20 | } 21 | 22 | .r-xoduu5 { 23 | color: #8899a6; 24 | } 25 | 26 | div[aria-label^="Follow"], .r-1niwhzg.r-1ets6dv.r-sdzlij { 27 | background-image: linear-gradient(#fff,#f5f8fa); 28 | background-color: rgb(15, 20, 25) !important; 29 | border: 1px solid #e1e8ed; 30 | } 31 | 32 | div[aria-label^="Follow"] > div > span, .r-1niwhzg.r-1ets6dv.r-sdzlij > div > span { 33 | color: #292f33; 34 | } 35 | .r-18jsvk2 { 36 | font-weight: 400; 37 | font-size: 16px; 38 | line-height: 22px; 39 | } 40 | .r-14j79pv { 41 | color: #8899a6; 42 | font-size: 13px; 43 | } 44 | .r-14j79pv.r-1loqt21.r-1q142lx { 45 | place-self: center; 46 | } 47 | .r-1awozwy.r-18jsvk2.r-6koalj { 48 | font-weight: bold; 49 | color: #292f33; 50 | } 51 | .gt2-legacy-profile-name { 52 | font-size: 22px; 53 | font-weight: 700; 54 | line-height: 1; 55 | text-rendering: optimizeLegibility; 56 | font-family: sans-serif !important; 57 | } 58 | .gt2-legacy-profile-nav-center { 59 | color: #66757f; 60 | font-size: 11px; 61 | letter-spacing: .02em; 62 | text-transform: uppercase; 63 | } 64 | .gt2-opt-legacy-profile.gt2-page-profile .gt2-legacy-profile-nav .gt2-legacy-profile-nav-left>img { 65 | border-radius: 10px !important; 66 | } 67 | .gt2-toggle-navbar-dropdown>img { 68 | border-radius: 3px !important; 69 | } 70 | button { 71 | border-radius: 3px !important; 72 | } 73 | .r-18jsvk2.r-6koalj.r-eqz5dr, .r-14j79pv.r-6koalj.r-eqz5dr.r-37j5jr { 74 | font-size: 18px; 75 | } 76 | .r-18jsvk2.r-6koalj.r-eqz5dr { 77 | color: black; 78 | } 79 | .r-1awozwy.r-1loqt21.r-6koalj:after { 80 | background-color: transparent !important; 81 | } 82 | .r-14j79pv.r-6koalj.r-eqz5dr.r-37j5jr { 83 | color: rgb(27, 149, 224); 84 | } 85 | .r-1awozwy.r-1ro0kt6.r-18u37iz, div[aria-label^='Timeline: '] { 86 | border: 1px solid #e1e8ed; 87 | } 88 | 89 | .public-DraftStyleDefault-block { 90 | color: black; 91 | } 92 | 93 | .gt2-nav .gt2-nav-right .gt2-compose, .r-1867qdf { 94 | border-radius: 3px; 95 | } 96 | .r-1f1sjgu { 97 | padding-top: 8px; 98 | padding-bottom: 8px; 99 | } 100 | } 101 | 102 | @-moz-document url-prefix("https://twitter.com/messages") { 103 | .gt2-legacy-profile-banner, .gt2-legacy-profile-nav { 104 | display: none; 105 | } 106 | } 107 | 108 | @-moz-document domain("twitter.com") { 109 | .r-sdzlij, img.actioned-user-profile-img, .Tweet-avatar{ 110 | border-radius: 5px; 111 | } 112 | html.dark .avatar { 113 | border-radius: 5px; 114 | } 115 | .avatar, .Avatar, .nav .session .dropdown-toggle, .ProfileAvatar, .ProfileAvatar-image, .ProfileAvatar-placeholderImage, .DashboardProfileCard-avatarImage, .ProfileUserList .Avatar, .ProfileCard-avatarImage, .AdaptiveNewsHeadlineDetails-userImage, .ProfileCard-avatarLink, .ProfileCardMini-avatarImage, .MomentCapsuleItemTweet--withText .MomentUserByline-avatar, .ProfileCard-avatarLink, .nav .session .dropdown-toggle::before, .MomentCapsuleCover .MomentUserByline-avatar, .avatar--circular, .ProfileAvatarEditing, .ProfileAvatarEditing-button, .ProfileAvatarEditing-overlay, .Gallery.with-tweet.without-tweet .Gallery-media, #activity-popup-dialog:not(.reply-users-popup) .activity-popup-dialog-users .account .avatar, #session h2 img, .subject .photo, .facepile li img, .tweet-image img, .DashboardProfileCard-avatarLink, .u04__user-profile-img, #activity-popup-dialog:not(.reply-users-popup) .activity-popup-dialog-users .account .avatar, .DMAvatar{ 116 | border-radius: 5px; 117 | } 118 | .EdgeButton, .EdgeButton:visited, .RichEditor, .TwitterCard .EdgeButton, .TwitterCard .EdgeButton:visited { 119 | border-radius: 5px; 120 | } 121 | .global-nav .search-input, .trends-search-locations.trend-locations-section input, .FollowButton--edge { 122 | border-radius: 5px !important; 123 | } 124 | } 125 | 126 | @-moz-document domain("mobile.twitter.com") { 127 | .r-sdzlij{ 128 | border-radius: 5px !important; 129 | } 130 | 131 | .r-sdzlij { 132 | border-radius: 5px !important; 133 | } 134 | } 135 | --------------------------------------------------------------------------------