Extension to return old Twitter layout from 2015.
at master 120 kB view raw
1let headerGotUser = false; 2let savedSearches = [], lastSearches = []; 3let inboxData = []; 4let followRequestsData = []; 5let customSet = false; 6let menuFn; 7let isDarkModeEnabled = typeof vars !== 'undefined' ? (vars.darkMode || (vars.timeMode && isDark())) : false; 8const keysHeld = {}; 9const notificationBus = new BroadcastChannel('notification_bus'); 10notificationBus.onmessage = function (e) { 11 if(e.data.type === 'markAsRead') { 12 let notifElement = document.getElementById('notifications-count'); 13 let icon = document.getElementById('site-icon'); 14 15 notifElement.hidden = true; 16 icon.href = chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}.png`); 17 if(document.title.startsWith("(")) { 18 document.title = document.title.split(') ').slice(1).join(') '); 19 } 20 } 21}; 22const customCSSBus = new BroadcastChannel('custom_css_bus'); 23customCSSBus.onmessage = function (e) { 24 if(e.data.type === 'vars') { 25 switchDarkMode(isDarkModeEnabled); 26 } else if(e.data.type === 'css') { 27 updateCustomCSS(); 28 } else if(e.data.type === 'color') { 29 if(typeof customSet === 'undefined' || !customSet) { 30 document.querySelector(':root').style.setProperty('--link-color', e.data.color); 31 } 32 } 33}; 34 35const themeBus = new BroadcastChannel('theme_bus'); 36themeBus.onmessage = function (e) { 37 isDarkModeEnabled = e.data[0]; 38 vars.pitchBlack = e.data[1]; 39 switchDarkMode(isDarkModeEnabled); 40} 41const roundAvatarBus = new BroadcastChannel('round_avatar_bus'); 42roundAvatarBus.onmessage = function (e) { 43 switchRoundAvatars(e.data); 44} 45 46let roundAvatarsEnabled = false; 47function switchRoundAvatars(enabled) { 48 roundAvatarsEnabled = enabled; 49 if(enabled) { 50 let style = document.createElement('style'); 51 style.id = 'round-avatars'; 52 style.innerHTML = ` 53 .navbar-user-account-avatar, 54 .search-result-item-avatar, 55 .inbox-message-avatar, 56 .message-header-avatar, 57 #user-avatar, 58 .tweet-avatar, 59 .tweet-avatar-quote, 60 #profile-avatar, 61 #navbar-user-avatar, 62 .tweet-footer-favorites-img, 63 #list-avatar, 64 .message-element > a > img, 65 .notification-avatar-img, 66 #nav-profile-avatar { 67 border-radius: 50% !important; 68 } 69 `; 70 document.head.appendChild(style); 71 } else { 72 let style = document.getElementById('round-avatars'); 73 if(style) style.remove(); 74 } 75} 76 77function hideStuff() { 78 let hs = document.getElementById('hide-style'); 79 if(hs) hs.remove(); 80 let hideStyle = document.createElement('style'); 81 hideStyle.id = 'hide-style'; 82 if(vars.hideTrends) { 83 hideStyle.innerHTML += '#trends { display: none !important; }'; 84 } 85 if(vars.hideWtf) { 86 hideStyle.innerHTML += '#wtf { display: none !important; }'; 87 } 88 if(vars.hideLikes) { 89 hideStyle.innerHTML += ` 90 .tweet-interact-favorite { color: var(--background-color) !important } 91 .tweet-interact-retweet { color: var(--background-color) !important } 92 .tweet-interact-reply { color: var(--background-color) !important } 93 .tweet:hover .tweet-interact-favorite { color: var(--dark-background-color) !important } 94 .tweet:hover .tweet-interact-retweet { color: var(--dark-background-color) !important } 95 .tweet:hover .tweet-interact-reply { color: var(--dark-background-color) !important } 96 `; 97 } 98 if(vars.hideFollowers) { 99 hideStyle.innerHTML += ` 100 #user-followers-div { display: none !important; } 101 #profile-stat-followers-link { display: none !important; } 102 `; 103 } 104 if(hideStyle.innerHTML !== '') { 105 document.head.appendChild(hideStyle); 106 } 107} 108 109let userDataFunction = async user => { 110 if(headerGotUser || Object.keys(user).length === 0) return; 111 headerGotUser = true; 112 let userAvatar = document.getElementById('navbar-user-avatar'); 113 userAvatar.src = `${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`.replace('_normal.', '_bigger.'); 114 document.getElementById('navbar-user-menu-profile').href = `/${user.screen_name}`; 115 document.getElementById('navbar-user-menu-lists').href = `/${user.screen_name}/lists`; 116 let menuUserName = document.getElementById('navbar-user-menu-username'); 117 menuUserName.innerText = user.name; 118 document.getElementById('pin-profile').hidden = !vars.pinProfileOnNavbar; 119 document.getElementById('pin-bookmarks').hidden = !vars.pinBookmarksOnNavbar; 120 document.getElementById('pin-lists').hidden = !vars.pinListsOnNavbar; 121 document.getElementById('pin-profile').href = `/${user.screen_name}`; 122 document.getElementById('pin-lists').href = `/${user.screen_name}/lists`; 123 124 if(vars.enableTwemoji) twemoji.parse(menuUserName); 125 126 let root = document.querySelector(":root"); 127 128 if(!customSet && vars.linkColor) { 129 root.style.setProperty('--link-color', vars.linkColor); 130 } 131 if(vars.font) { 132 root.style.setProperty('--font', `"${vars.font}"`); 133 } 134 if(vars.tweetFont) { 135 root.style.setProperty('--tweet-font', `"${vars.tweetFont}"`); 136 } 137 if(vars.heartsNotStars) { 138 root.style.setProperty('--favorite-icon-content', '"\\f148"'); 139 root.style.setProperty('--favorite-icon-content-notif', '"\\f015"'); 140 root.style.setProperty('--favorite-icon-color', 'rgb(249, 24, 128)'); 141 } 142 143 if(vars.roundAvatars) { 144 switchRoundAvatars(true); 145 } 146 147 if(vars.disableHotkeys) { 148 document.getElementById('navbar-tweet-button').title = ''; 149 document.getElementById('search').title = ''; 150 } 151 152 // util 153 let firstTime = false; 154 async function updateUnread() { 155 let unread = await API.notifications.getUnreadCount(firstTime); 156 if(!firstTime) firstTime = true; 157 let dms = unread.dm_unread_count; 158 let notifs = unread.ntab_unread_count; 159 let total = unread.total_unread_count; 160 let dmsElement = document.getElementById('messages-count'); 161 let notifElement = document.getElementById('notifications-count'); 162 let icon = document.getElementById('site-icon'); 163 if(location.pathname.startsWith('/notifications')) { 164 notifs = 0; 165 } 166 let inboxModal = document.getElementsByClassName('inbox-modal')[0]; 167 if(inboxModal) { 168 dms = 0; 169 } 170 total = dms + notifs; 171 172 if(dms > 0) { 173 dmsElement.hidden = false; 174 dmsElement.innerText = dms; 175 } else { 176 dmsElement.hidden = true; 177 } 178 if(notifs > 0) { 179 notifElement.hidden = false; 180 notifElement.innerText = notifs; 181 } else { 182 notifElement.hidden = true; 183 } 184 icon.href = total > 0 ? chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}_notification.png`) : chrome.runtime.getURL(`images/logo32${vars.useNewIcon ? '_new' : ''}.png`); 185 if(total > 0) { 186 let newTitle = document.title; 187 if(document.title.startsWith('(')) { 188 newTitle = document.title.split(') ')[1]; 189 } 190 newTitle = `(${total}) ${newTitle}`; 191 if(document.title !== newTitle) { 192 document.title = newTitle; 193 } 194 } else { 195 if(document.title.startsWith('(')) { 196 document.title = document.title.split(') ').slice(1).join(') '); 197 } 198 } 199 } 200 async function updateAccounts() { 201 let accounts = (await API.account.getAccounts()).users; 202 let accountsElement = document.getElementById('navbar-user-accounts'); 203 accountsElement.innerHTML = ''; 204 accounts.forEach(account => { 205 let accountElement = document.createElement('div'); 206 accountElement.classList.add('navbar-user-account'); 207 accountElement.innerHTML = `<img src="${account.avatar_image_url.replace("_normal", "_bigger")}" class="navbar-user-account-avatar" width="16" height="16"> ${account.screen_name}`; 208 accountElement.addEventListener('click', async () => { 209 if(account.screen_name === user.screen_name) return alert("You're already on this account!"); 210 try { 211 await API.account.switch(account.user_id); 212 window.location.reload(); 213 } catch(e) { 214 if((typeof(e) === 'string' && e.includes('User not found.')) || e.errors[0].code === 50) { 215 window.location = 'https://twitter.com/account/switch?newtwitter=true'; 216 } else { 217 alert(e); 218 } 219 console.error(e); 220 } 221 }); 222 accountsElement.appendChild(accountElement, document.createElement('br')); 223 }); 224 document.getElementById('navbar-user-menu-logout').addEventListener('click', async () => { 225 let modal = createModal(/*html*/` 226 <h1 class="cool-header">${LOC.logout_title.message}</h1><br> 227 <span style="font-size:14px;color:var(--almost-black)">${LOC.logout_desc_1.message}<br> 228 ${LOC.logout_desc_2.message}</span> 229 <br><br> 230 <div style="display:inline-block;float: right;margin-top: 5px;"> 231 <button class="nice-button nice-red-button">${LOC.logout_button.message}</button> 232 </div> 233 `); 234 let button = modal.querySelector('button'); 235 button.addEventListener('click', async () => { 236 await API.account.logout(); 237 window.location.reload(); 238 }); 239 }); 240 } 241 async function updateInboxData() { 242 inboxData = await API.inbox.get(); 243 if(inboxData.status === "HAS_MORE" && !cursor) { 244 cursor = inboxData.min_entry_id; 245 } else { 246 cursor = undefined; 247 }; 248 249 return true; 250 } 251 252 // follow requests 253 if(user.protected) { 254 let followRequests = document.getElementById('navbar-user-menu-requests'); 255 let followRequestsCount = document.getElementById('follow-request-count'); 256 followRequests.style.display = 'flex'; 257 258 async function updateFollowRequests() { 259 let list = document.querySelector('.follow-requests-list'); 260 let newUserData = await Promise.all(followRequestsData.ids.filter(i => typeof i === 'string').map(i => API.user.get(i))); 261 for(let i = 0; i < newUserData.length; i++) { 262 followRequestsData.ids[i] = newUserData[i]; 263 } 264 for(let i in followRequestsData.ids) { 265 let u = followRequestsData.ids[i]; 266 let userElement = document.createElement('div'); 267 userElement.classList.add('follow-requests-user'); 268 userElement.innerHTML = /*html*/` 269 <div> 270 <a href="https://twitter.com/${u.screen_name}" class="following-item-link"> 271 <img src="${`${(u.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(u.id_str) % 7}_normal.png`): u.profile_image_url_https}`}" alt="${u.screen_name}" class="following-item-avatar tweet-avatar" width="48" height="48"> 272 <div class="following-item-text"> 273 <span class="tweet-header-name following-item-name ${u.verified || u.id_str === '1123203847776763904' ? 'user-verified' : ''} ${u.protected ? 'user-protected' : ''}">${escapeHTML(u.name)}</span><br> 274 <span class="tweet-header-handle">@${u.screen_name}</span> 275 </div> 276 </a> 277 </div> 278 <div> 279 <button class="request-item-btn nice-button accept">${LOC.accept.message}</button> 280 <button class="request-item-btn nice-button decline">${LOC.decline.message}</button> 281 </div> 282 `; 283 userElement.querySelector('.accept').addEventListener('click', async () => { 284 try { 285 await API.user.acceptFollowRequest(u.id_str); 286 } catch(e) { 287 console.error(e); 288 alert(e); 289 return; 290 } 291 userElement.remove(); 292 data.ids.splice(i, 1); 293 let count; 294 if(data.total_count) { 295 count = --data.total_count; 296 } else { 297 count = data.ids.length; 298 } 299 if(count > 0) { 300 followRequestsCount.hidden = false; 301 followRequestsCount.innerText = count; 302 } else { 303 followRequestsCount.hidden = true; 304 } 305 }); 306 userElement.querySelector('.decline').addEventListener('click', async () => { 307 try { 308 await API.user.declineFollowRequest(u.id_str); 309 } catch(e) { 310 console.error(e); 311 alert(e); 312 return; 313 } 314 userElement.remove(); 315 data.ids.splice(i, 1); 316 let count; 317 if(data.total_count) { 318 count = --data.total_count; 319 } else { 320 count = data.ids.length; 321 } 322 if(count > 0) { 323 followRequestsCount.hidden = false; 324 followRequestsCount.innerText = count; 325 } else { 326 followRequestsCount.hidden = true; 327 } 328 }); 329 list.appendChild(userElement); 330 } 331 } 332 followRequests.addEventListener('click', async () => { 333 let modal = createModal(/*html*/` 334 <h1 class="larger nice-header">${LOC.follow_requests.message}</h1> 335 <div class="follow-requests-list"></div> 336 <div class="requests-load-more center-text">${LOC.load_more.message}</div> 337 `, 'follow-requests-modal'); 338 let loadMoreBtn = modal.querySelector('.requests-load-more'); 339 340 if(followRequestsData.next_cursor_str !== '0') { 341 loadMoreBtn.hidden = false; 342 loadMoreBtn.addEventListener('click', async () => { 343 loadMoreBtn.innerText = LOC.loading.message; 344 loadMoreBtn.disabled = true; 345 API.user.getFollowRequests(followRequestsData.next_cursor_str).then(data => { 346 followRequestsData.ids = followRequestsData.ids.concat(data.ids); 347 followRequestsData.next_cursor_str = data.next_cursor_str; 348 updateFollowRequests(); 349 }); 350 }); 351 } else { 352 loadMoreBtn.hidden = true; 353 } 354 updateFollowRequests(); 355 }); 356 API.user.getFollowRequests().then(data => { 357 followRequestsData = data; 358 let count = data.total_count ? data.total_count : data.ids.length; 359 if(count > 0) { 360 followRequestsCount.hidden = false; 361 followRequestsCount.innerText = count; 362 } else { 363 followRequestsCount.hidden = true; 364 } 365 }); 366 } 367 368 // unfollows 369 if(user.followers_count > 0 && user.followers_count < 50000) { 370 chrome.storage.local.get(['unfollows'], async d => { 371 let res = d.unfollows; 372 if(!res) res = {}; 373 if(!res[user.id_str]) res[user.id_str] = { 374 followers: [], 375 following: [], 376 unfollowers: [], 377 unfollowings: [], 378 lastUpdate: 0 379 }; 380 381 if(Date.now() - res[user.id_str].lastUpdate > 1000 * 60 * 60) { 382 updateUnfollows(res); 383 } 384 setInterval(() => { 385 chrome.storage.local.get(['unfollows'], async d => { 386 let res = d.unfollows; 387 if(!res) res = {}; 388 if(!res[user.id_str]) res[user.id_str] = { 389 followers: [], 390 following: [], 391 unfollowers: [], 392 unfollowings: [], 393 lastUpdate: 0 394 }; 395 if(Date.now() - res[user.id_str].lastUpdate > 1000 * 60 * 60) { 396 updateUnfollows(res); 397 } 398 }); 399 }, 1000 * 60 * 10); 400 }); 401 } 402 403 // messages 404 let cursor; 405 let modal; 406 let lastConvo; 407 function compare(e, t) { 408 var i = e.length - t.length; 409 return i || (e > t ? i = 1 : e < t && (i = -1)), i; 410 }; 411 async function renderConversation(convo, convoId, newMessages = true, updateConvo = true) { 412 if(updateConvo) { 413 lastConvo = convo; 414 lastConvo.conversation_id = convoId; 415 } else { 416 if(!convo.users) convo.users = {}; 417 if(!lastConvo.users) lastConvo.users = {}; 418 lastConvo.users = Object.assign(lastConvo.users, convo.users); 419 if(!lastConvo.entries) lastConvo.entries = []; // what the fuck all of this does 420 if(!convo.entries) convo.entries = []; 421 lastConvo.entries.forEach(e => { 422 e.added = true; 423 }); 424 lastConvo.entries = lastConvo.entries.concat(convo.entries); 425 let seen = []; 426 lastConvo.entries = lastConvo.entries.filter(entry => { 427 let val = Object.values(entry)[0]; 428 if(seen.includes(val.id)) return false; 429 seen.push(val.id); 430 return true; 431 }); 432 } 433 if(inboxData) { 434 let conversations = Array.isArray(inboxData.conversations) ? inboxData.conversations : Object.values(inboxData.conversations); 435 let realConvo = conversations.find(c => c.id_str === lastConvo.id_str); 436 if(+lastConvo.max_entry_id >= +realConvo.last_read_event_id) { 437 API.inbox.markRead(lastConvo.max_entry_id); 438 realConvo.last_read_event_id = lastConvo.max_entry_id; 439 } 440 } 441 window.history.pushState(null, document.title, window.location.href) 442 let messageBox = modal.querySelector('.messages-list'); 443 if(!lastConvo.entries) { 444 modal.getElementsByClassName('messages-load-more')[0].hidden = true; 445 return; 446 } 447 let missingUserIds = []; 448 for(let j in lastConvo.entries) { 449 let m = lastConvo.entries[j].message; 450 if(!m) continue; 451 if(!lastConvo.users[m.message_data.sender_id] && !missingUserIds.includes(m.message_data.sender_id)) { 452 missingUserIds.push(m.message_data.sender_id); 453 } 454 } 455 if(missingUserIds.length > 0) { 456 let foundUsers = await API.user.lookup(missingUserIds) 457 foundUsers.forEach(user => { 458 lastConvo.users[user.id_str] = user; 459 }); 460 } 461 lastConvo.entries = lastConvo.entries.reverse(); 462 let messageElements = []; 463 for(let i in lastConvo.entries) { 464 if(lastConvo.entries[i].added) continue; 465 let m = lastConvo.entries[i].message; 466 if(!m) continue; 467 let sender = lastConvo.users[m.message_data.sender_id]; 468 469 let messageElement = document.createElement('div'); 470 messageElement.classList.add('message-element'); 471 if(sender.id_str !== user.id_str) { 472 messageElement.classList.add('message-element-other'); 473 } 474 messageElement.id = `message-${m.id}`; 475 messageElement.innerHTML = ` 476 ${sender.id_str !== user.id_str ? ` 477 <a href="https://twitter.com/${sender.screen_name}"><img src="${`${(sender.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(sender.id_str) % 7}_normal.png`): sender.profile_image_url_https}`.replace("_normal", "_bigger")}" width="26" height="26"></a> 478 <span class="message-body">${escapeHTML(m.message_data.text).replace(/((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g, '<a href="$1">$1</a>').replace(/(?<!\w)@([\w+]{1,15}\b)/g, `<a href="https://twitter.com/$1">@$1</a>`)}</span> 479 <span class="message-time" data-timestamp="${m.time}">${timeElapsed(new Date(+m.time))}</span> 480 ` : ` 481 <span class="message-menu-open"></span> 482 <div class="message-menu" hidden> 483 <span class="message-menu-delete">Delete for you</span> 484 </div> 485 <span class="message-time" data-timestamp="${m.time}">${timeElapsed(new Date(+m.time))}</span> 486 <span class="message-body">${escapeHTML(m.message_data.text).replace(/((http|https|ftp):\/\/[\w?=&.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g, '<a href="$1">$1</a>').replace(/(?<!\w)@([\w+]{1,15}\b)/g, `<a href="https://twitter.com/$1">@$1</a>`)}</span> 487 `} 488 `; 489 let menuOpen = messageElement.querySelector('.message-menu-open'); 490 if(menuOpen) { 491 let menu = messageElement.querySelector('.message-menu'); 492 let menuDelete = messageElement.querySelector('.message-menu-delete'); 493 494 menuDelete.addEventListener('click', () => { 495 API.inbox.deleteMessage(m.id); 496 messageElement.remove(); 497 }); 498 499 let clicked; 500 menuOpen.addEventListener('click', () => { 501 if(clicked) return; 502 clicked = true; 503 menu.hidden = false; 504 setTimeout(() => { 505 menuFn = () => { 506 setTimeout(() => { 507 clicked = false; 508 menu.hidden = true; 509 }, 100); 510 }; 511 document.addEventListener('click', menuFn, { once: true }) 512 }, 100); 513 }); 514 } 515 let as = Array.from(messageElement.getElementsByTagName('a')); 516 if(m.message_data.entities && m.message_data.entities.urls) { 517 m.message_data.entities.urls.forEach(url => { 518 let a = as.find(a => a.href === url.url); 519 if(!a) return; 520 let removed = false; 521 if(m.message_data.attachment) { 522 if(m.message_data.attachment.photo) { 523 if(a.href === m.message_data.attachment.photo.url) { 524 removed = true; 525 a.remove(); 526 } 527 } 528 if(m.message_data.attachment.animated_gif) { 529 if(a.href === m.message_data.attachment.animated_gif.url) { 530 removed = true; 531 a.remove(); 532 } 533 } 534 } 535 if(a && !removed) { 536 a.href = url.expanded_url; 537 a.innerText = url.display_url; 538 a.target = "_blank"; 539 } 540 }); 541 } 542 if(m.message_data.attachment) { 543 let attachment = m.message_data.attachment; 544 if(attachment.photo) { 545 let photo = attachment.photo; 546 let photoElement = document.createElement('img'); 547 photoElement.src = photo.media_url_https; 548 photoElement.classList.add('message-element-media'); 549 if(photo.original_info.width > 200) { 550 photoElement.width = 200; 551 } else { 552 photoElement.width = photo.original_info.width; 553 } 554 if(photo.original_info.height > 100) { 555 photoElement.height = 100; 556 } else { 557 photoElement.height = photo.original_info.height; 558 } 559 photoElement.addEventListener('click', e => { 560 new Viewer(photoElement, { 561 transition: false 562 }); 563 e.target.click(); 564 }) 565 messageElement.append(document.createElement('br'), photoElement); 566 } 567 if(attachment.animated_gif) { 568 let gif = attachment.animated_gif; 569 let gifElement = document.createElement('video'); 570 gifElement.src = gif.video_info.variants[0].url; 571 gifElement.muted = true; 572 gifElement.loop = true; 573 gifElement.autoplay = true; 574 if(gif.original_info.width > 200) { 575 gifElement.width = 200; 576 } else { 577 gifElement.width = gif.original_info.width; 578 } 579 if(gif.original_info.height > 100) { 580 gifElement.height = 100; 581 } else { 582 gifElement.height = gif.original_info.height; 583 } 584 gifElement.classList.add('message-element-media'); 585 messageElement.append(document.createElement('br'), gifElement); 586 } 587 } 588 let span = messageElement.getElementsByClassName('message-body')[0]; 589 if(span.innerHTML === '' || span.innerHTML === ' ') { 590 span.remove(); 591 } 592 if(vars.enableTwemoji) { 593 twemoji.parse(messageElement); 594 } 595 messageElements.push(messageElement); 596 } 597 if(!newMessages) { 598 messageElements = messageElements.reverse(); 599 for(let i in messageElements) { 600 messageBox.prepend(messageElements[i], document.createElement('br')); 601 } 602 } else { 603 for(let i in messageElements) { 604 messageBox.append(messageElements[i], document.createElement('br')); 605 } 606 } 607 if(newMessages) { 608 let modalElement = document.getElementsByClassName('modal-content')[0]; 609 modalElement.scrollTop = modalElement.scrollHeight; 610 } 611 612 const loadMoreMessages = modal.querySelector('.messages-load-more'); 613 if(lastConvo.status === "HAS_MORE") { 614 loadMoreMessages.hidden = false; 615 } else { 616 loadMoreMessages.hidden = true; 617 } 618 } 619 function renderInboxMessages(inbox, inboxList) { 620 inbox.conversations = Object.values(inbox.conversations).sort((a, b) => (+b.sort_timestamp)-(+a.sort_timestamp)); 621 for(let i in inbox.conversations) { 622 let c = inbox.conversations[i]; 623 let lastMessage = inbox.entries.find(e => (e.message && e.message.id === c.max_entry_id) || (e.trust_conversation && e.trust_conversation.id === c.max_entry_id)); 624 if(!lastMessage) { 625 continue; 626 }; 627 if(lastMessage.message) { 628 lastMessage = lastMessage.message; 629 } else if(lastMessage.trust_conversation) { 630 lastMessage = lastMessage.trust_conversation; 631 }; 632 let messageUsers = c.participants.filter(p => p.user_id !== user.id_str).map(p => inbox.users[p.user_id]); 633 let lastMessageUser = lastMessage.message_data ? messageUsers.find(user => user.id_str === lastMessage.message_data.sender_id) : messageUsers[0]; 634 let messageElement = document.createElement('div'); 635 messageElement.classList.add('inbox-message'); 636 let isUnread = false; 637 if(compare(lastMessage.id, c.last_read_event_id) < 1) {} 638 else { 639 messageElement.classList.add('inbox-message-unread'); 640 isUnread = true; 641 } 642 messageElement.innerHTML = /*html*/` 643 <img src="${messageUsers.length === 1 ? `${(messageUsers[0].default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(messageUsers[0].id_str) % 7}_normal.png`): messageUsers[0].profile_image_url_https}` : (c.avatar_image_https || chrome.runtime.getURL(`/images/group.jpg`))}" width="48" height="48" class="inbox-message-avatar"> 644 <div class="inbox-text"> 645 <b class="inbox-name">${messageUsers.length === 1 ? escapeHTML(messageUsers[0].name) : (c.name ? escapeHTML(c.name) : messageUsers.map(i => escapeHTML(i.name)).join(', ').slice(0, 128))}</b> 646 <span class="inbox-screenname">${messageUsers.length === 1 ? "@"+messageUsers[0].screen_name : ''}</span> 647 <span class="inbox-time">${timeElapsed(new Date(+lastMessage.time))}</span> 648 <br> 649 <span class="inbox-message-preview">${lastMessage.reason ? 'Accepted conversation' : lastMessage.message_data.text.startsWith('dmservice_reaction_') ? `${lastMessage.message_data.sender_id === user.id_str ? 'You reacted to message' : `${escapeHTML(lastMessageUser.name)} reacted to message`}` : escapeHTML(lastMessage.message_data.text)}</span> 650 </div> 651 `; 652 if(vars.enableTwemoji) { 653 twemoji.parse(messageElement); 654 } 655 const messageHeaderName = modal.querySelector('.message-header-name'); 656 const messageHeaderAvatar = modal.querySelector('.message-header-avatar'); 657 const messageHeaderLink = modal.querySelector('.message-header-link'); 658 messageElement.addEventListener('click', async () => { 659 let messageData = await API.inbox.getConversation(c.conversation_id); 660 modal.querySelector('.message-box').hidden = false; 661 modal.querySelector('.home-top').hidden = true; 662 modal.querySelector('.name-top').hidden = false; 663 modal.querySelector('.inbox').hidden = true; 664 modal.querySelector('.new-message-box').hidden = true; 665 messageHeaderName.innerText = messageUsers.length === 1 ? messageUsers[0].name : (c.name || messageUsers.map(i => i.name).join(', ').slice(0, 80)); 666 messageHeaderAvatar.src = messageUsers.length === 1 ? `${(messageUsers[0].default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(messageUsers[0].id_str) % 7}_normal.png`): messageUsers[0].profile_image_url_https}` : (c.avatar_image_https || chrome.runtime.getURL(`/images/group.jpg`)); 667 if(messageUsers.length === 1) messageHeaderLink.href = `https://twitter.com/${messageUsers[0].screen_name}`; 668 setTimeout(() => { 669 modal.querySelector(".message-new-input").focus(); 670 }); 671 672 renderConversation(messageData, c.conversation_id); 673 }); 674 if(isUnread) { 675 inboxList.prepend(messageElement); 676 } else { 677 inboxList.append(messageElement); 678 } 679 } 680 const messageHeaderBack = modal.querySelector('.message-header-back'); 681 messageHeaderBack.addEventListener('click', e => { 682 modal.remove(); 683 chrome.storage.local.remove(['inboxData'], () => {}); 684 setTimeout(() => { 685 document.getElementById('messages').click(); 686 }, 20); 687 }); 688 } 689 document.getElementById('messages').addEventListener('click', async e => { 690 e.preventDefault(); 691 let inbox = inboxData; 692 693 modal = createModal(/*html*/` 694 <div class="inbox"> 695 <div class="inbox-top home-top"> 696 <h1 class="larger nice-header">${LOC.direct_messages.message}</h1> 697 <div class="inbox-buttons"> 698 <button class="nice-button inbox-refresh" title="${LOC.refresh.message}"> 699 <span class="inbox-refresh-icon"></span> 700 </button> 701 <button class="nice-button inbox-readall" title="${LOC.mark_all_read.message}"> 702 <span class="inbox-readall-icon"></span> 703 </button> 704 <button class="nice-button inbox-new" title="${LOC.new_message.message}"> 705 <span class="inbox-new-icon"></span> 706 </button> 707 </div> 708 <hr> 709 </div> 710 <br><br><br> 711 <div class="inbox-list"></div> 712 <div class="center-text load-more" ${cursor ? '' : 'hidden'}>${LOC.load_more.message}</div> 713 </div> 714 <div class="message-box" hidden> 715 <div class="name-top-background"></div><!-- ugly bug fix --> 716 <div class="inbox-top name-top"> 717 <span class="message-header-back"></span> 718 <a class="message-header-link"> 719 <img class="message-header-avatar" width="32" height="32"> 720 <h1 class="larger message-header-name nice-header">${LOC.name.message}</h1> 721 </a> 722 <span class="message-leave"></span> 723 <hr> 724 </div> 725 <br><br><br><br> 726 <div class="messages-load-more center-text" style="margin-top:-18px;">${LOC.load_more.message}</div> 727 <div class="messages-list"></div> 728 <div class="message-new"> 729 <div class="message-new-media"></div> 730 <span class="message-new-media-btn"></span> 731 <span class="message-emoji-btn"></span> 732 <textarea type="text" class="message-new-input" placeholder="${LOC.type_message.message}"></textarea> 733 <button class="nice-button message-new-send">${LOC.send.message}</button> 734 </div> 735 </div> 736 <div class="new-message-box" hidden> 737 <div class="inbox-top new-name-top"> 738 <span class="message-header-back message-new-message-back"></span> 739 <h1 class="larger message-header-name nice-header" style="float: left;margin-left: 14px;">${LOC.new_message.message}</h1> 740 <button class="new-message-group nice-button" hidden>${LOC.create_new_group.message}</button> 741 <br> 742 <input type="text" class="new-message-user-search" placeholder="${LOC.search_people.message}" style="width:551px"> 743 <hr> 744 </div> 745 <br><br><br><br><br> 746 <div class="new-message-results"></div> 747 </div> 748 `, "inbox-modal", () => { 749 if(location.hash === '#dm') { 750 location.hash = ""; 751 } 752 }); 753 modal.querySelector('.modal-close').hidden = true; 754 const inboxList = modal.querySelector('.inbox-list'); 755 const readAll = modal.querySelector('.inbox-readall'); 756 const refresh = modal.querySelector('.inbox-refresh'); 757 const newInbox = modal.querySelector('.inbox-new'); 758 const newMedia = modal.querySelector('.message-new-media'); 759 const newMediaButton = modal.querySelector('.message-new-media-btn'); 760 const newMediaInput = modal.querySelector('.message-new-input'); 761 const emojiButton = modal.querySelector('.message-emoji-btn'); 762 const newSend = modal.querySelector('.message-new-send'); 763 const newInput = modal.querySelector('.message-new-input'); 764 const loadMore = modal.querySelector('.load-more'); 765 const loadMoreMessages = modal.querySelector('.messages-load-more'); 766 const userSearch = modal.querySelector('.new-message-user-search'); 767 const newMessageResults = modal.querySelector('.new-message-results'); 768 const leaveConvo = modal.querySelector('.message-leave'); 769 770 newInbox.addEventListener('click', () => { 771 modal.querySelector('.inbox').hidden = true; 772 modal.querySelector('.new-message-box').hidden = false; 773 modal.querySelector('.name-top').hidden = true; 774 modal.querySelector('.home-top').hidden = true; 775 modal.querySelector('.message-box').hidden = true; 776 }); 777 modal.getElementsByClassName('message-new-message-back')[0].addEventListener('click', () => { 778 modal.remove(); 779 document.getElementById('messages').click(); 780 }); 781 leaveConvo.addEventListener('click', async () => { 782 if(!lastConvo || !lastConvo.conversation_id) return; 783 let c = confirm('Are you sure you want to leave/remove this conversation?'); 784 if(c) { 785 await API.inbox.deleteConversation(lastConvo.conversation_id); 786 modal.remove(); 787 chrome.storage.local.remove(['inboxData'], () => {}); 788 await updateInboxData(); 789 } 790 }); 791 userSearch.addEventListener('keyup', async () => { 792 let q = userSearch.value; 793 let res = await API.search.typeahead(q); 794 newMessageResults.innerHTML = ''; 795 res.users.slice(0, 5).forEach(u => { 796 let userElement = document.createElement('div'); 797 userElement.classList.add('new-message-user'); 798 userElement.innerHTML = ` 799 <img class="new-message-user-avatar" src="${`${(u.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(u.id_str) % 7}_normal.png`): u.profile_image_url_https}`.replace("_normal", "_bigger")}" width="48" height="48"> 800 <div class="new-message-user-text"> 801 <b class="new-message-user-name">${escapeHTML(u.name)}</b> 802 <span class="new-message-user-screenname">@${u.screen_name}</span> 803 </div> 804 `; 805 userElement.addEventListener('click', async () => { 806 const messageHeaderName = modal.querySelector('.message-header-name'); 807 const messageHeaderAvatar = modal.querySelector('.message-header-avatar'); 808 const messageHeaderLink = modal.querySelector('.message-header-link'); 809 let messageData = await API.inbox.getConversation(`${user.id_str}-${u.id_str}`); 810 modal.querySelector('.message-box').hidden = false; 811 modal.querySelector('.home-top').hidden = true; 812 modal.querySelector('.name-top').hidden = false; 813 modal.querySelector('.inbox').hidden = true; 814 modal.querySelector('.new-message-box').hidden = true; 815 messageHeaderName.innerText = u.name; 816 messageHeaderAvatar.src = `${(u.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(u.id_str) % 7}_normal.png`): u.profile_image_url_https}`; 817 messageHeaderLink.href = `https://twitter.com/${u.screen_name}`; 818 setTimeout(() => { 819 modal.querySelector(".message-new-input").focus(); 820 }); 821 822 renderConversation(messageData, `${user.id_str}-${u.id_str}`); 823 }); 824 newMessageResults.appendChild(userElement); 825 }); 826 }); 827 828 let mediaToUpload = []; 829 newMediaButton.addEventListener('click', () => { 830 getDMMedia(mediaToUpload, newMedia, document.querySelector('.modal-content')); 831 }); 832 newInput.addEventListener('paste', event => { 833 let items = (event.clipboardData || event.originalEvent.clipboardData).items; 834 for (index in items) { 835 let item = items[index]; 836 if (item.kind === 'file') { 837 let file = item.getAsFile(); 838 handleFiles([file], mediaToUpload, newMedia); 839 } 840 } 841 }); 842 newInput.addEventListener('keypress', e => { 843 if(e.key === "Enter" && !e.shiftKey) { 844 e.preventDefault(); 845 newSend.click(); 846 } 847 }); 848 newSend.addEventListener('click', async () => { 849 let message = newMediaInput.value; 850 if (message.length === 0 && mediaToUpload.length === 0) return; 851 newSend.disabled = true; 852 newInput.value = ""; 853 let uploadedMedia = []; 854 for (let i in mediaToUpload) { 855 let media = mediaToUpload[i]; 856 try { 857 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = false; 858 let mediaId = await API.uploadMedia({ 859 media_type: media.type, 860 media: media.data, 861 cw: media.cw, 862 loadCallback: data => { 863 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].innerText = `${data.text} (${data.progress}%)`; 864 } 865 }); 866 uploadedMedia.push(mediaId); 867 } catch (e) { 868 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = true; 869 console.error(e); 870 alert(e); 871 } 872 } 873 let obj = { 874 text: message, 875 conversation_id: lastConvo.conversation_id 876 }; 877 if (uploadedMedia.length > 0) { 878 obj.media_id = uploadedMedia.join(','); 879 } 880 try { 881 let sentMessage = await API.inbox.send(obj); 882 newSend.disabled = false; 883 mediaToUpload = []; 884 newMedia.innerHTML = ""; 885 sentMessage.conversation_id = lastConvo.conversation_id; 886 renderConversation(sentMessage, lastConvo.conversation_id, true, false); 887 } catch (e) { 888 console.error(e); 889 if(String(e).includes('You cannot send messages to this user.')) { 890 let messageList = modal.querySelector('.messages-list'); 891 messageList.innerHTML = LOC.cant_send.message; 892 return; 893 } 894 newSend.disabled = false; 895 } 896 }); 897 emojiButton.addEventListener('click', () => { 898 let rect = emojiButton.getBoundingClientRect(); 899 createEmojiPicker(document.body, newInput, { 900 left: rect.x + 'px', 901 top: rect.y-300 + 'px' 902 }); 903 }); 904 905 906 loadMore.addEventListener('click', async () => { 907 let moreInbox = await API.inbox.get(cursor); 908 if(moreInbox.status === "HAS_MORE") { 909 cursor = moreInbox.min_entry_id; 910 } else { 911 cursor = undefined; 912 } 913 renderInboxMessages(moreInbox, inboxList); 914 }); 915 loadMoreMessages.addEventListener('click', async () => { 916 let moreMessages = await API.inbox.getConversation(lastConvo.conversation_id, lastConvo.min_entry_id); 917 renderConversation(moreMessages, lastConvo.conversation_id, false); 918 }); 919 920 readAll.addEventListener('click', async () => { 921 await API.inbox.markRead(inbox.last_seen_event_id); 922 let unreadMessages = Array.from(document.getElementsByClassName('inbox-message-unread')); 923 unreadMessages.forEach(message => { 924 message.classList.remove('inbox-message-unread'); 925 }); 926 chrome.storage.local.remove(['inboxData'], () => {}); 927 await updateInboxData(); 928 modal.remove(); 929 document.getElementById('messages').click(); 930 }); 931 refresh.addEventListener('click', async () => { 932 chrome.storage.local.remove(['inboxData'], () => {}); 933 await updateInboxData(); 934 modal.remove(); 935 document.getElementById('messages').click(); 936 }); 937 938 renderInboxMessages(inbox, inboxList); 939 }); 940 setInterval(() => { 941 let times = Array.from(document.getElementsByClassName('message-time')); 942 times.forEach(time => { 943 time.innerText = timeElapsed(+time.dataset.timestamp); 944 }); 945 }, 10000); 946 let updateCursor; 947 setInterval(async () => { 948 let updates = await API.inbox.getUpdates(updateCursor); 949 updateCursor = Object.values(updates)[0].cursor; 950 if(updates.user_events && updates.user_events.conversations && lastConvo) { 951 for(let i in updates.user_events.conversations) { 952 let c = updates.user_events.conversations[i]; 953 if(c.conversation_id === lastConvo.conversation_id) { 954 updates.user_events.entries.forEach(e => { 955 if(e.message_delete && e.message_delete.conversation_id === lastConvo.conversation_id) { 956 let messages = e.message_delete.messages; 957 for(let j in messages) { 958 let message = messages[j]; 959 let messageElement = document.getElementById(`message-${message.message_id}`); 960 if(messageElement) { 961 messageElement.remove(); 962 } 963 } 964 } 965 }); 966 updates.user_events.entries = updates.user_events.entries.filter(m => m.message && m.message.conversation_id === lastConvo.conversation_id); 967 renderConversation(updates.user_events, lastConvo.conversation_id, true, false); 968 } 969 } 970 } 971 }, 5000); 972 973 // tweet 974 document.getElementById('navbar-tweet-button').addEventListener('click', () => { 975 let modal = createModal(/*html*/` 976 <div class="navbar-new-tweet-container"> 977 <div class="navbar-new-tweet"> 978 <img width="35" height="35" class="navbar-new-tweet-avatar"> 979 <span class="navbar-new-tweet-char">0/280</span> 980 <textarea maxlength="1000" class="navbar-new-tweet-text" placeholder="${LOC.whats_happening.message}"></textarea> 981 <div class="navbar-new-tweet-user-search box" hidden></div> 982 <div class="navbar-new-tweet-media-div"> 983 <span class="navbar-new-tweet-media"></span> 984 </div> 985 <div class="navbar-new-tweet-focused"> 986 <span id="navbar-new-tweet-poll-btn"></span> 987 <span id="navbar-new-tweet-emoji-btn"></span> 988 <div id="navbar-new-tweet-poll" hidden></div> 989 <div class="navbar-new-tweet-media-cc"><div class="navbar-new-tweet-media-c"></div></div> 990 <button class="navbar-new-tweet-button nice-button">${LOC.tweet.message}</button> 991 <br><br> 992 </div> 993 </div> 994 </div> 995 `); 996 const newTweet = modal.getElementsByClassName('navbar-new-tweet-container')[0]; 997 const newTweetText = modal.getElementsByClassName('navbar-new-tweet-text')[0]; 998 const newTweetChar = modal.getElementsByClassName('navbar-new-tweet-char')[0]; 999 const newTweetMedia = modal.getElementsByClassName('navbar-new-tweet-media')[0]; 1000 const newTweetMediaDiv = modal.getElementsByClassName('navbar-new-tweet-media-c')[0]; 1001 const newTweetButton = modal.getElementsByClassName('navbar-new-tweet-button')[0]; 1002 const newTweetUserSearch = modal.getElementsByClassName('navbar-new-tweet-user-search')[0]; 1003 const newTweetPoll = document.getElementById('navbar-new-tweet-poll'); 1004 const newTweetEmojiBtn = document.getElementById('navbar-new-tweet-emoji-btn'); 1005 1006 newTweetText.focus(); 1007 1008 newTweetEmojiBtn.addEventListener('click', () => { 1009 let rect = newTweetEmojiBtn.getBoundingClientRect(); 1010 createEmojiPicker(document.body, newTweetText, { 1011 left: rect.x - 320 + 'px', 1012 top: rect.y + 'px' 1013 }); 1014 }); 1015 1016 let selectedIndex = 0; 1017 let pollToUpload = undefined; 1018 1019 document.getElementById('navbar-new-tweet-poll-btn').addEventListener('click', () => { 1020 if(newTweetPoll.hidden) { 1021 mediaToUpload = []; 1022 newTweetMediaDiv.innerHTML = ''; 1023 newTweetPoll.hidden = false; 1024 newTweetPoll.style.width = "364px"; 1025 document.getElementById('navbar-new-tweet-poll').innerHTML = ` 1026 <input class="navbar-poll-question" data-variant="1" placeholder="${LOC.variant.message} 1"><br> 1027 <input class="navbar-poll-question" data-variant="2" placeholder="${LOC.variant.message} 2"><br> 1028 <input class="navbar-poll-question" data-variant="3" placeholder="${LOC.variant.message} 3 ${LOC.optional.message}"><br> 1029 <input class="navbar-poll-question" data-variant="4" placeholder="${LOC.variant.message} 4 ${LOC.optional.message}"><br> 1030 <hr> 1031 ${LOC.days.message}: <input class="navbar-poll-date" id="navbar-poll-days" type="number" min="0" max="7" value="1"> 1032 ${LOC.hours.message}: <input class="navbar-poll-date" id="navbar-poll-hours" type="number" min="0" max="23" value="0"> 1033 ${LOC.minutes.message}: <input class="navbar-poll-date" id="navbar-poll-minutes" type="number" min="0" max="59" value="0"> 1034 <hr> 1035 <button class="nice-button" id="navbar-poll-remove">${LOC.remove_poll.message}</button> 1036 `; 1037 let pollVariants = Array.from(document.getElementsByClassName('navbar-poll-question')); 1038 pollToUpload = { 1039 duration_minutes: 1440, 1040 variants: ['', '', '', ''] 1041 } 1042 let pollDates = Array.from(document.getElementsByClassName('navbar-poll-date')); 1043 pollDates.forEach(pollDate => { 1044 pollDate.addEventListener('change', () => { 1045 let days = parseInt(document.getElementById('navbar-poll-days').value); 1046 let hours = parseInt(document.getElementById('navbar-poll-hours').value); 1047 let minutes = parseInt(document.getElementById('navbar-poll-minutes').value); 1048 if(days === 0 && hours === 0 && minutes === 0) { 1049 days = 1; 1050 document.getElementById('navbar-poll-days').value = 1; 1051 } 1052 pollToUpload.duration_minutes = days * 1440 + hours * 60 + minutes; 1053 }, { passive: true }); 1054 }); 1055 pollVariants.forEach(pollVariant => { 1056 pollVariant.addEventListener('change', () => { 1057 pollToUpload.variants[(+pollVariant.dataset.variant) - 1] = pollVariant.value; 1058 }, { passive: true }); 1059 }); 1060 document.getElementById('navbar-poll-remove').addEventListener('click', () => { 1061 newTweetPoll.hidden = true; 1062 newTweetPoll.innerHTML = ''; 1063 newTweetPoll.style.width = "0"; 1064 pollToUpload = undefined; 1065 }); 1066 } else { 1067 newTweetPoll.innerHTML = ''; 1068 newTweetPoll.hidden = true; 1069 newTweetPoll.style.width = "0"; 1070 pollToUpload = undefined; 1071 } 1072 }); 1073 1074 modal.getElementsByClassName('navbar-new-tweet-avatar')[0].src = `${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`.replace("_normal", "_bigger"); 1075 newTweetText.addEventListener('focus', async e => { 1076 setTimeout(() => { 1077 if(/(?<!\w)@([\w+]{1,15}\b)$/.test(e.target.value)) { 1078 newTweetUserSearch.hidden = false; 1079 } else { 1080 newTweetUserSearch.hidden = true; 1081 newTweetUserSearch.innerHTML = ''; 1082 } 1083 }, 10); 1084 }); 1085 newTweetText.addEventListener('blur', async e => { 1086 setTimeout(() => { 1087 newTweetUserSearch.hidden = true; 1088 }, 100); 1089 }); 1090 newTweetText.addEventListener('keydown', e => { 1091 if(e.key === "Enter" && e.ctrlKey) { 1092 newTweetButton.click(); 1093 } 1094 }); 1095 newTweetText.addEventListener('input', e => { 1096 let charElement = newTweetChar; 1097 let text = e.target.value.replace(linkRegex, ' https://t.co/xxxxxxxxxx').trim(); 1098 charElement.innerText = `${text.length}/280`; 1099 if (text.length > 265) { 1100 charElement.style.color = "#c26363"; 1101 } else { 1102 charElement.style.color = ""; 1103 } 1104 if (text.length > 280) { 1105 charElement.style.color = "red"; 1106 newTweetButton.disabled = true; 1107 } else { 1108 newTweetButton.disabled = false; 1109 } 1110 }); 1111 newTweetText.addEventListener('keypress', async e => { 1112 if ((e.key === 'Enter' || e.key === 'Tab') && !newTweetUserSearch.hidden) { 1113 let activeSearch = newTweetUserSearch.querySelector('.search-result-item-active'); 1114 if(!e.ctrlKey) { 1115 e.preventDefault(); 1116 e.stopPropagation(); 1117 newTweetText.value = newTweetText.value.split("@").slice(0, -1).join('@').split(" ").slice(0, -1).join(" ") + ` @${activeSearch.querySelector('.search-result-item-screen-name').innerText.slice(1)} `; 1118 if(newTweetText.value.startsWith(" ")) newTweetText.value = newTweetText.value.slice(1); 1119 newTweetUserSearch.innerHTML = ''; 1120 newTweetUserSearch.hidden = true; 1121 } 1122 } 1123 }); 1124 newTweetText.addEventListener('keydown', async e => { 1125 if(e.key === 'ArrowDown') { 1126 if(selectedIndex < newTweetUserSearch.children.length - 1) { 1127 selectedIndex++; 1128 newTweetUserSearch.children[selectedIndex].classList.add('search-result-item-active'); 1129 newTweetUserSearch.children[selectedIndex - 1].classList.remove('search-result-item-active'); 1130 } else { 1131 selectedIndex = 0; 1132 newTweetUserSearch.children[selectedIndex].classList.add('search-result-item-active'); 1133 newTweetUserSearch.children[newTweetUserSearch.children.length - 1].classList.remove('search-result-item-active'); 1134 } 1135 return; 1136 } 1137 if(e.key === 'ArrowUp') { 1138 if(selectedIndex > 0) { 1139 selectedIndex--; 1140 newTweetUserSearch.children[selectedIndex].classList.add('search-result-item-active'); 1141 newTweetUserSearch.children[selectedIndex + 1].classList.remove('search-result-item-active'); 1142 } else { 1143 selectedIndex = newTweetUserSearch.children.length - 1; 1144 newTweetUserSearch.children[selectedIndex].classList.add('search-result-item-active'); 1145 newTweetUserSearch.children[0].classList.remove('search-result-item-active'); 1146 } 1147 return; 1148 } 1149 if(/(?<!\w)@([\w+]{1,15}\b)$/.test(e.target.value)) { 1150 newTweetUserSearch.hidden = false; 1151 selectedIndex = 0; 1152 let users = (await API.search.typeahead(e.target.value.match(/@([\w+]{1,15}\b)$/)[1])).users; 1153 newTweetUserSearch.innerHTML = ''; 1154 users.forEach((user, index) => { 1155 let userElement = document.createElement('span'); 1156 userElement.className = 'search-result-item'; 1157 if(index === 0) userElement.classList.add('search-result-item-active'); 1158 userElement.innerHTML = ` 1159 <img width="16" height="16" class="search-result-item-avatar" src="${`${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`}"> 1160 <span class="search-result-item-name ${user.verified || user.id_str === '1123203847776763904' ? 'search-result-item-verified' : ''}">${escapeHTML(user.name)}</span> 1161 <span class="search-result-item-screen-name">@${user.screen_name}</span> 1162 `; 1163 userElement.addEventListener('click', () => { 1164 newTweetText.value = newTweetText.value.split("@").slice(0, -1).join('@').split(" ").slice(0, -1).join(" ") + ` @${user.screen_name} `; 1165 if(newTweetText.value.startsWith(" ")) newTweetText.value = newTweetText.value.slice(1); 1166 newTweetText.focus(); 1167 newTweetUserSearch.innerHTML = ''; 1168 newTweetUserSearch.hidden = true; 1169 }); 1170 newTweetUserSearch.appendChild(userElement); 1171 }); 1172 } else { 1173 newTweetUserSearch.innerHTML = ''; 1174 newTweetUserSearch.hidden = true; 1175 } 1176 }); 1177 let mediaToUpload = []; 1178 newTweet.addEventListener('drop', e => { 1179 document.getElementById('new-tweet').click(); 1180 newTweetPoll.innerHTML = ''; 1181 newTweetPoll.hidden = true; 1182 newTweetPoll.style.width = "0"; 1183 pollToUpload = undefined; 1184 handleDrop(e, mediaToUpload, newTweetMediaDiv); 1185 }); 1186 newTweet.addEventListener('paste', event => { 1187 let items = (event.clipboardData || event.originalEvent.clipboardData).items; 1188 for (index in items) { 1189 let item = items[index]; 1190 if (item.kind === 'file') { 1191 let file = item.getAsFile(); 1192 handleFiles([file], mediaToUpload, newTweetMediaDiv); 1193 } 1194 } 1195 }); 1196 newTweetMedia.addEventListener('click', () => { 1197 newTweetPoll.innerHTML = ''; 1198 newTweetPoll.hidden = true; 1199 newTweetPoll.style.width = "0"; 1200 pollToUpload = undefined; 1201 getMedia(mediaToUpload, newTweetMediaDiv); 1202 newTweetText.focus(); 1203 }); 1204 newTweetButton.addEventListener('click', async () => { 1205 let tweet = newTweetText.value; 1206 if (tweet.length === 0 && mediaToUpload.length === 0) return; 1207 newTweetButton.disabled = true; 1208 if(!pollToUpload) { 1209 let uploadedMedia = []; 1210 for (let i in mediaToUpload) { 1211 let media = mediaToUpload[i]; 1212 try { 1213 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = false; 1214 let mediaId = await API.uploadMedia({ 1215 media_type: media.type, 1216 media_category: media.category, 1217 media: media.data, 1218 alt: media.alt, 1219 cw: media.cw, 1220 loadCallback: data => { 1221 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].innerText = `${data.text} (${data.progress}%)`; 1222 } 1223 }); 1224 uploadedMedia.push(mediaId); 1225 } catch (e) { 1226 media.div.getElementsByClassName('new-tweet-media-img-progress')[0].hidden = true; 1227 console.error(e); 1228 alert(e); 1229 } 1230 } 1231 try { 1232 let tweetObject = await API.tweet.postV2({ 1233 text: tweet, 1234 media: uploadedMedia 1235 }); 1236 tweetObject._ARTIFICIAL = true; 1237 const event = new CustomEvent('newTweet', { detail: tweetObject }); 1238 document.dispatchEvent(event); 1239 } catch (e) { 1240 newTweetButton.disabled = false; 1241 console.error(e); 1242 alert(e); 1243 } 1244 } else { 1245 let pollVariants = pollToUpload.variants.filter(i => i); 1246 if(pollVariants.length < 2) { 1247 newTweetButton.disabled = false; 1248 return alert('You must have at least 2 poll variants'); 1249 } 1250 let cardObject = { 1251 "twitter:card": `poll${pollVariants.length}choice_text_only`, 1252 "twitter:api:api:endpoint": "1", 1253 "twitter:long:duration_minutes": pollToUpload.duration_minutes, 1254 "twitter:string:choice1_label": pollVariants[0], 1255 "twitter:string:choice2_label": pollVariants[1] 1256 } 1257 if(pollVariants[2]) { 1258 cardObject["twitter:string:choice3_label"] = pollVariants[2]; 1259 } 1260 if(pollVariants[3]) { 1261 cardObject["twitter:string:choice4_label"] = pollVariants[3]; 1262 } 1263 try { 1264 let card = await API.tweet.createCard(cardObject); 1265 let tweetObject = await API.tweet.postV2({ 1266 text: tweet, 1267 card_uri: card.card_uri, 1268 }); 1269 tweetObject._ARTIFICIAL = true; 1270 const event = new CustomEvent('newTweet', { detail: tweetObject }); 1271 document.dispatchEvent(event); 1272 } catch (e) { 1273 newTweetButton.disabled = false; 1274 console.error(e); 1275 alert(e); 1276 } 1277 } 1278 modal.remove(); 1279 modal.removeModal(); 1280 }); 1281 }); 1282 1283 // search 1284 let searchInput = document.getElementById('search-input'); 1285 let searchResults = document.getElementById('search-results'); 1286 let searchIcon = document.getElementById('search-icon'); 1287 1288 let selectedIndex = -1; 1289 1290 async function loadDefaultSearches() { 1291 searchResults.innerHTML = ''; 1292 1293 await new Promise(resolve => chrome.storage.local.get(['lastSearches'], data => { 1294 lastSearches = data.lastSearches; 1295 if(!lastSearches) lastSearches = []; 1296 else lastSearches = lastSearches.filter(i => i); 1297 resolve(1); 1298 })); 1299 if(savedSearches.length === 0) { 1300 try { 1301 savedSearches = await API.search.getSaved(); 1302 } catch(e) {} 1303 } 1304 if(lastSearches.length > 0) { 1305 let span = document.createElement('span'); 1306 span.innerText = LOC.last_searches.message; 1307 span.className = 'search-results-title'; 1308 searchResults.append(span); 1309 for(let i in lastSearches) { 1310 let topic = lastSearches[i]; 1311 let topicElement = document.createElement('a'); 1312 topicElement.href = `/search?q=${topic}`; 1313 topicElement.className = 'search-result-item'; 1314 topicElement.innerText = topic; 1315 if(location.pathname.startsWith('/search')) topicElement.addEventListener('click', e => { 1316 e.preventDefault(); 1317 searchResults.hidden = true; 1318 searchInput.value = topic; 1319 let event = new Event('newSearch'); 1320 document.dispatchEvent(event); 1321 }); 1322 let removeTopic = document.createElement('span'); 1323 removeTopic.innerText = '×'; 1324 removeTopic.className = 'search-result-item-remove'; 1325 removeTopic.addEventListener('click', () => { 1326 lastSearches.splice(i, 1); 1327 chrome.storage.local.set({lastSearches: lastSearches}); 1328 topicElement.remove(); 1329 removeTopic.remove(); 1330 }); 1331 searchResults.append(topicElement, removeTopic); 1332 } 1333 } 1334 if(savedSearches.length > 0) { 1335 let span = document.createElement('span'); 1336 span.innerText = LOC.saved_searches.message; 1337 span.className = 'search-results-title'; 1338 searchResults.append(span); 1339 for(let i in savedSearches) { 1340 let topic = savedSearches[i].query; 1341 let topicId = savedSearches[i].id_str; 1342 let topicElement = document.createElement('a'); 1343 topicElement.href = `/search?q=${topic}`; 1344 topicElement.className = 'search-result-item'; 1345 topicElement.innerText = topic; 1346 if(location.pathname.startsWith('/search')) topicElement.addEventListener('click', e => { 1347 e.preventDefault(); 1348 searchResults.hidden = true; 1349 searchInput.value = topic; 1350 let event = new Event('newSearch'); 1351 document.dispatchEvent(event); 1352 }); 1353 let removeTopic = document.createElement('span'); 1354 removeTopic.innerText = '×'; 1355 removeTopic.className = 'search-result-item-remove'; 1356 removeTopic.addEventListener('click',async () => { 1357 await API.search.deleteSaved(topicId); 1358 savedSearches.splice(i, 1); 1359 topicElement.remove(); 1360 removeTopic.remove(); 1361 }); 1362 searchResults.append(topicElement, removeTopic); 1363 } 1364 } 1365 } 1366 1367 searchInput.addEventListener('focus', () => { 1368 searchResults.hidden = false; 1369 if(searchInput.value.length === 0) { 1370 loadDefaultSearches(); 1371 } 1372 }); 1373 searchInput.addEventListener('blur', () => { 1374 setTimeout(() => { 1375 searchResults.hidden = true; 1376 }, 150); 1377 }); 1378 searchInput.addEventListener('keyup', async (e) => { 1379 let query = searchInput.value; 1380 let searchElements = Array.from(searchResults.children).filter(e => e.tagName === "A"); 1381 let activeSearch = searchElements[selectedIndex]; 1382 if(e.key === "Enter") { 1383 if(activeSearch) { 1384 activeSearch.click(); 1385 } else { 1386 searchIcon.click(); 1387 } 1388 return; 1389 } 1390 if(activeSearch) activeSearch.classList.remove('search-result-item-active'); 1391 if(e.key === 'ArrowDown') { 1392 if(selectedIndex < searchElements.length - 1) { 1393 selectedIndex++; 1394 searchElements[selectedIndex].classList.add('search-result-item-active'); 1395 } else { 1396 selectedIndex = -1; 1397 } 1398 return; 1399 } 1400 if(e.key === 'ArrowUp') { 1401 if(selectedIndex > -1) { 1402 selectedIndex--; 1403 if(searchElements[selectedIndex]) searchElements[selectedIndex].classList.add('search-result-item-active'); 1404 } else { 1405 selectedIndex = searchElements.length - 1; 1406 searchElements[selectedIndex].classList.add('search-result-item-active'); 1407 } 1408 return; 1409 } 1410 if(query.length === 0) { 1411 return loadDefaultSearches(); 1412 } 1413 let search = await API.search.typeahead(query); 1414 searchResults.innerHTML = ''; 1415 search.topics.forEach(({topic}) => { 1416 let topicElement = document.createElement('a'); 1417 topicElement.href = `/search?q=${topic}`; 1418 topicElement.className = 'search-result-item'; 1419 topicElement.innerText = topic; 1420 if(location.pathname.startsWith('/search')) topicElement.addEventListener('click', e => { 1421 e.preventDefault(); 1422 searchResults.hidden = true; 1423 searchInput.value = topic; 1424 let event = new Event('newSearch'); 1425 document.dispatchEvent(event); 1426 }); 1427 searchResults.appendChild(topicElement); 1428 }); 1429 search.users.forEach((user) => { 1430 let userElement = document.createElement('a'); 1431 userElement.href = `/${user.screen_name}`; 1432 userElement.className = 'search-result-item'; 1433 userElement.innerHTML = ` 1434 <img width="16" height="16" class="search-result-item-avatar" src="${`${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`}"> 1435 <span class="search-result-item-name ${user.verified || user.id_str === '1123203847776763904' ? 'search-result-item-verified' : ''}">${user.name}</span> 1436 <span class="search-result-item-screen-name">@${user.screen_name}</span> 1437 `; 1438 searchResults.appendChild(userElement); 1439 }); 1440 }); 1441 searchIcon.addEventListener('click', () => { 1442 if(!searchInput.value) { 1443 return searchInput.focus(); 1444 } 1445 lastSearches.push(searchInput.value); 1446 if(lastSearches.length > 5) { 1447 lastSearches.shift(); 1448 } 1449 lastSearches = [...new Set(lastSearches)]; 1450 chrome.storage.local.set({ 1451 lastSearches 1452 }, () => { 1453 if(location.pathname.startsWith('/search')) { 1454 searchResults.hidden = true; 1455 let event = new Event('newSearch'); 1456 document.dispatchEvent(event); 1457 } else { 1458 location.href = `/search?q=${encodeURIComponent(searchInput.value)}`; 1459 } 1460 }); 1461 }); 1462 searchIcon.addEventListener('mousedown', e => { 1463 if(e.button === 1) { 1464 e.preventDefault(); 1465 lastSearches.push(searchInput.value); 1466 if(lastSearches.length > 5) { 1467 lastSearches.shift(); 1468 } 1469 lastSearches = [...new Set(lastSearches)]; 1470 chrome.storage.local.set({ 1471 lastSearches 1472 }, () => { 1473 openInNewTab(`/search?q=${encodeURIComponent(searchInput.value)}`); 1474 }); 1475 } 1476 }); 1477 1478 // user previews 1479 let userPreviewTimeouts = []; 1480 let leavePreviewTimeout; 1481 document.addEventListener('mouseover', e => { 1482 for(let timeout of userPreviewTimeouts) { 1483 clearTimeout(timeout); 1484 } 1485 userPreviewTimeouts = []; 1486 let el = e.target; 1487 if(el.closest('.user-preview')) { 1488 clearTimeout(leavePreviewTimeout); 1489 leavePreviewTimeout = null; 1490 } 1491 if(document.getElementsByClassName('user-preview').length > 0) return; 1492 el = el.closest('a'); 1493 if(!el || !el.href) return; 1494 let url; 1495 try { url = new URL(el.href.split('?')[0].split('#')[0]) } catch(e) { return }; 1496 if((!isProfilePath(url.pathname) && !url.pathname.startsWith('/i/user/')) || url.host !== 'twitter.com') return; 1497 let username, id; 1498 let path = url.pathname; 1499 if(path.endsWith('/')) path = path.slice(0, -1); 1500 1501 if(url.pathname.startsWith('/i/user/')) { 1502 id = path.split('/').pop(); 1503 } else { 1504 username = path.slice(1); 1505 }; 1506 1507 if(location.pathname.slice(1) === username) return; 1508 if(username === user.screen_name) return; 1509 if(typeof pageUser !== 'undefined') { 1510 if(username === pageUser.screen_name) return; 1511 } 1512 userPreviewTimeouts.push(setTimeout(async () => { 1513 let userPreview = document.createElement('div'); 1514 let shadow = userPreview.attachShadow({mode: 'closed'}); 1515 userPreview.className = 'user-preview'; 1516 1517 let stopLoad = false; 1518 1519 let leaveFunction = () => { 1520 leavePreviewTimeout = setTimeout(() => { 1521 stopLoad = true; 1522 userPreview.remove(); 1523 el.removeEventListener('mouseleave', leaveFunction); 1524 }, 500); 1525 } 1526 el.addEventListener('mouseleave', leaveFunction); 1527 1528 let user = await API.user.get(id ? id : username, !!id); 1529 if(stopLoad) return; 1530 let div = document.createElement('div'); 1531 div.innerHTML = /*html*/` 1532 <style> 1533 :host{font-weight:initial;line-height:initial;text-align:initial;word-spacing:initial;white-space:initial} 1534 .follows-you-label{font-size:11px;letter-spacing:.02em;text-transform:uppercase;color:var(--darker-gray);background:rgba(0,0,0,0.08);width:fit-content;padding:3px 7px;border-radius:5px;margin-bottom:5px;margin-top:5px;display:block} 1535 .preview-user-banner {border-top-left-radius: 5px;border-top-right-radius: 5px;object-fit: cover;} 1536 .preview-user-info {left: 10px;position: relative;text-decoration: none !important;} 1537 .preview-user-stats {display: inline-flex;padding-bottom: 7px;} 1538 .preview-user-avatar {border: 4px solid var(--background-color);border-radius: 7px;margin-left: 7px;margin-top: -32px;} 1539 .preview-user-name {color: var(--almost-black);font-size: 20px;margin: 0;position: relative;width: 180px;overflow-x: hidden;} 1540 .preview-user-handle {color: var(--lil-darker-gray);font-size: 14px;font-weight: 100;margin: 0;position: relative;width: fit-content;} 1541 .preview-user-follow {float: right;bottom: 68px;position: relative;right: 7px;} 1542 .preview-user-description {width: 280px;color: var(--darker-gray);font-size: 15px;left: 10px;position: relative;display: block;margin-top: 5px;overflow: hidden;} 1543 .user-stat-div>h2 {color: var(--lil-darker-gray);font-size: 14px;font-weight: 100;margin: 0 10px;text-transform: uppercase;white-space: nowrap;} 1544 .user-stat-div>h1 {color: var(--link-color);font-size: 20px;margin: 0 10px } 1545 .user-stat-div {text-decoration: none !important;} 1546 .nice-button {color: var(--almost-black);background-color: var(--darker-background-color);background-image: linear-gradient(var(--background-color),var(--darker-background-color));background-repeat: no-repeat;border: 1px solid var(--border);border-radius: 4px;color: var(--darker-gray);cursor: pointer;font-size: 14px;font-weight: bold;line-height: normal;padding: 8px 16px;} 1547 .nice-button:hover:not([disabled]) {color: var(--almost-black);text-decoration: none;background-color: var(--border);background-image: linear-gradient(var(--background-color),var(--border));border-color: var(--border);} 1548 .nice-button:disabled {color: lightgray !important;cursor: not-allowed;} 1549 .nice-button:disabled:before {color: lightgray !important;} 1550 .emoji {height: 16px;margin-left: 2px;margin-right: 2px;vertical-align: text-top;width: 16px;} 1551 a {color: var(--link-color);text-decoration: none } 1552 a:hover {text-decoration: underline;} 1553 .profile-additional-thing{font-size:14px;color: var(--darker-gray);font-weight:400;line-height:20px;left: 10px;position: relative;display: block;overflow: hidden;} 1554 .profile-additional-thing::before{margin-right:5px;vertical-align:sub;color:var(--light-gray);display:inline-block;width:20px;text-align:center;font: 18px 'RosettaIcons'} 1555 .profile-additional-location::before{content:"\\f031"} 1556 .profile-additional-joined::before{content:"\\f177"} 1557 .profile-additional-birth::before{content:"\\f033"} 1558 .profile-additional-professional::before{content:"\\f204"} 1559 .profile-additional-url::before{content:"\\f098"} 1560 .preview-user-additional-info{margin-top:10px} 1561 ${roundAvatarsEnabled ? '.preview-user-avatar {border-radius: 50%!important;}' : ''} 1562 </style> 1563 <img class="preview-user-banner" height="100" width="300" src="${user.profile_banner_url ? user.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png'}"> 1564 <div class="preview-user-data"> 1565 <a class="preview-user-avatar-link" href="https://twitter.com/${user.screen_name}"> 1566 <img class="preview-user-avatar" width="50" height="50" src="${`${(user.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(user.id_str) % 7}_normal.png`): user.profile_image_url_https}`.replace('_normal.', '_400x400.')}"> 1567 </a> 1568 <br> 1569 <a class="preview-user-info" href="https://twitter.com/${user.screen_name}"> 1570 <h1 class="preview-user-name">${escapeHTML(user.name)}</h1> 1571 <h2 class="preview-user-handle">@${user.screen_name}</h2> 1572 ${user.followed_by ? /*html*/`<span class="follows-you-label">${LOC.follows_you.message}</span>` : ''} 1573 </a> 1574 <button class="nice-button preview-user-follow ${user.following ? 'following' : 'follow'}">${user.following ? LOC.following_btn.message : LOC.follow.message}</button> 1575 <span class="preview-user-description">${escapeHTML(user.description).replace(/\n/g, '<br>').replace(/((http|https):\/\/[\w?=.\/-;#~%-]+(?![\w\s?&.\/;#~%"=-]*>))/g, '<a href="$1">$1</a>').replace(/(?<!\w)@([\w+]{1,15}\b)/g, `<a href="https://twitter.com/$1">@$1</a>`).replace(/(?<!\w)#([\w+]+\b)/g, `<a href="https://twitter.com/hashtag/$1">#$1</a>`)}</span> 1576 <div class="preview-user-additional-info"> 1577 <span class="profile-additional-thing profile-additional-joined">${LOC.joined.message} ${new Date(user.created_at).toLocaleDateString(LANGUAGE.replace("_", "-"), {month: 'long', year: 'numeric', day: 'numeric'})}</span> 1578 </div> 1579 <br> 1580 <div class="preview-user-stats"> 1581 <a class="user-stat-div" href="https://twitter.com/${user.screen_name}/following"> 1582 <h2>${LOC.following.message}</h2> 1583 <h1 class="preview-user-following">${Number(user.friends_count).toLocaleString().replace(/\s/g, ',')}</h1> 1584 </a> 1585 <a class="user-stat-div" href="https://twitter.com/${user.screen_name}/followers"> 1586 <h2>${LOC.followers.message}</h2> 1587 <h1 class="preview-user-followers">${Number(user.followers_count).toLocaleString().replace(/\s/g, ',')}</h1> 1588 </a> 1589 </div> 1590 </div> 1591 `; 1592 let additionalInfoElement = div.querySelector('.preview-user-additional-info'); 1593 if(user.location) { 1594 let location = document.createElement('span'); 1595 location.classList.add('profile-additional-thing', 'profile-additional-location'); 1596 location.innerText = user.location.replace(/\n\n\n\n/g, "\n"); 1597 additionalInfoElement.prepend(location); 1598 if(vars.enableTwemoji) twemoji.parse(location); 1599 } 1600 if(user.professional && user.professional.category && user.professional.category[0]) { 1601 let prof = document.createElement('span'); 1602 prof.classList.add('profile-additional-thing', 'profile-additional-professional'); 1603 prof.innerText = user.professional.category[0].name; 1604 additionalInfoElement.prepend(prof); 1605 if(vars.enableTwemoji) twemoji.parse(prof); 1606 } 1607 if(user.url) { 1608 let url = document.createElement('a'); 1609 url.classList.add('profile-additional-thing', 'profile-additional-url'); 1610 let realUrl = user.entities.url.urls[0]; 1611 url.innerText = realUrl.display_url; 1612 url.href = realUrl.expanded_url; 1613 if(!url.href.startsWith('https://twitter.com/')) url.target = "_blank"; 1614 additionalInfoElement.prepend(url); 1615 } 1616 div.addEventListener('mouseleave', leaveFunction); 1617 let links = Array.from(div.querySelector('.preview-user-description').querySelectorAll('a')); 1618 links.forEach(link => { 1619 let realLink = user.entities.description.urls.find(u => u.url === link.href); 1620 if (realLink) { 1621 link.href = realLink.expanded_url; 1622 if(!link.href.startsWith('https://twitter.com/')) link.target = '_blank'; 1623 link.innerText = realLink.display_url; 1624 } 1625 }); 1626 const followBtn = div.querySelector('.preview-user-follow'); 1627 followBtn.addEventListener('click', async () => { 1628 if (followBtn.className.includes('following')) { 1629 await API.user.unfollow(user.screen_name); 1630 followBtn.classList.remove('following'); 1631 followBtn.classList.add('follow'); 1632 followBtn.innerText = LOC.follow.message; 1633 user.following = false; 1634 let wtfFollow = document.querySelector(`.wtf-user > .tweet-avatar-link[href="https://twitter.com/${user.screen_name}"]`); 1635 if(!wtfFollow) return; 1636 wtfFollow = wtfFollow.parentElement.getElementsByClassName('discover-follow-btn')[0]; 1637 wtfFollow.classList.remove('following'); 1638 wtfFollow.classList.add('follow'); 1639 wtfFollow.innerText = LOC.follow.message; 1640 } else { 1641 await API.user.follow(user.screen_name); 1642 followBtn.classList.add('following'); 1643 followBtn.classList.remove('follow'); 1644 followBtn.innerText = LOC.following_btn.message; 1645 user.following = true; 1646 let wtfFollow = document.querySelector(`.wtf-user > .tweet-avatar-link[href="https://twitter.com/${user.screen_name}"]`); 1647 if(!wtfFollow) return; 1648 wtfFollow = wtfFollow.parentElement.getElementsByClassName('discover-follow-btn')[0]; 1649 wtfFollow.classList.add('following'); 1650 wtfFollow.classList.remove('follow'); 1651 wtfFollow.innerText = LOC.following_btn.message; 1652 } 1653 }); 1654 shadow.appendChild(div); 1655 1656 if(isSticky(el)) { 1657 el.parentElement.append(userPreview); 1658 } else { 1659 let rects = el.getBoundingClientRect(); 1660 userPreview.style.top = `${rects.top + window.scrollY+ 20}px`; 1661 userPreview.style.left = `${rects.left + window.scrollX}px`; 1662 let closestTweet = el.closest('.tweet'); 1663 if(closestTweet) { 1664 let linkColor = closestTweet.style.getPropertyValue('--link-color'); 1665 if(linkColor) { 1666 div.style.setProperty('--link-color', linkColor); 1667 } 1668 } 1669 document.body.append(userPreview); 1670 } 1671 if(vars.enableTwemoji) twemoji.parse(shadow); 1672 }, 700)); 1673 }, { passive: true }); 1674 1675 document.addEventListener('messageUser', e => { 1676 document.getElementById('messages').click(); 1677 setTimeout(async () => { 1678 let convo_id = e.detail.id; 1679 let u = e.detail.user; 1680 const messageHeaderName = modal.querySelector('.message-header-name'); 1681 const messageHeaderAvatar = modal.querySelector('.message-header-avatar'); 1682 const messageHeaderLink = modal.querySelector('.message-header-link'); 1683 let messageData = await API.inbox.getConversation(convo_id); 1684 modal.querySelector('.message-box').hidden = false; 1685 modal.querySelector('.home-top').hidden = true; 1686 modal.querySelector('.name-top').hidden = false; 1687 modal.querySelector('.inbox').hidden = true; 1688 modal.querySelector('.new-message-box').hidden = true; 1689 messageHeaderName.innerText = u.name; 1690 messageHeaderAvatar.src = `${(u.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(u.id_str) % 7}_normal.png`): u.profile_image_url_https}`; 1691 messageHeaderLink.href = `https://twitter.com/${u.screen_name}`; 1692 setTimeout(() => { 1693 modal.querySelector(".message-new-input").focus(); 1694 }); 1695 1696 renderConversation(messageData, convo_id); 1697 }, 50); 1698 }); 1699 document.addEventListener('tweetAction', e => { 1700 if(typeof timeline === 'undefined') return; 1701 let data = e.detail; 1702 let tweet = data.tweet; 1703 let tweetData = timeline.data.find(i => i.id_str === tweet.id_str); 1704 switch(data.action) { 1705 case 'favorite': { 1706 if(tweetData && tweetData.renderFavoritesUp) { 1707 tweetData.renderFavoritesUp(); 1708 } 1709 break; 1710 } 1711 case 'unfavorite': { 1712 if(tweetData && tweetData.renderFavoritesDown) { 1713 tweetData.renderFavoritesDown(); 1714 } 1715 break; 1716 } 1717 case 'retweet': { 1718 if(tweetData && tweetData.renderRetweetsUp) { 1719 tweetData.renderRetweetsUp(data.tweetData); 1720 } 1721 break; 1722 } 1723 case 'unretweet': { 1724 if(tweetData && tweetData.renderRetweetsDown) { 1725 tweetData.renderRetweetsDown(); 1726 } 1727 break; 1728 } 1729 case 'follow': { 1730 let user = tweet.user.id_str; 1731 let tweetsDataByUser = timeline.data.filter(i => i.user.id_str === user); 1732 let tweetsElementsByUser = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.userId === user); 1733 let wtfElement = Array.from(document.getElementsByClassName('wtf-user')).filter(i => i.dataset.userId === user)[0]; 1734 if(wtfElement) { 1735 wtfElement.remove(); 1736 } 1737 tweetsDataByUser.forEach(tweetData => { 1738 tweetData.user.following = true; 1739 }); 1740 tweetsElementsByUser.forEach(tweetElement => { 1741 let followButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-follow')[0]; 1742 if(followButton) { 1743 if(LOC.unfollow_user.message.includes('$SCREEN_NAME$')) { 1744 followButton.innerText = `${LOC.unfollow_user.message.replace('$SCREEN_NAME$', tweet.user.screen_name)}`; 1745 } else { 1746 followButton.innerText = `${LOC.unfollow_user.message} @${tweet.user.screen_name}`; 1747 } 1748 } 1749 }); 1750 let controlFollow = document.getElementById('control-follow'); 1751 if(controlFollow) { 1752 controlFollow.classList.remove('follow'); 1753 controlFollow.classList.add('following'); 1754 controlFollow.innerText = LOC.following.message; 1755 pageUser.following = true; 1756 } 1757 break; 1758 } 1759 case 'unfollow': { 1760 let user = tweet.user.id_str; 1761 let tweetsDataByUser = timeline.data.filter(i => i.user.id_str === user); 1762 let tweetsElementsByUser = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.userId === user); 1763 tweetsDataByUser.forEach(tweetData => { 1764 tweetData.user.following = false; 1765 }); 1766 let controlFollow = document.getElementById('control-follow'); 1767 if(controlFollow) { 1768 controlFollow.classList.remove('following'); 1769 controlFollow.classList.add('follow'); 1770 controlFollow.innerText = LOC.follow.message; 1771 pageUser.following = false; 1772 } 1773 tweetsElementsByUser.forEach(tweetElement => { 1774 let followButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-follow')[0]; 1775 if(followButton) { 1776 if(LOC.follow_user.message.includes('$SCREEN_NAME$')) { 1777 followButton.innerText = `${LOC.follow_user.message.replace('$SCREEN_NAME$', tweet.user.screen_name)}`; 1778 } else { 1779 followButton.innerText = `${LOC.follow_user.message} @${tweet.user.screen_name}`; 1780 } 1781 } 1782 }); 1783 break; 1784 } 1785 case 'block': { 1786 let user = tweet.user.id_str; 1787 let tweetsDataByUser = timeline.data.filter(i => i.user.id_str === user); 1788 let tweetsElementsByUser = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.userId === user); 1789 let wtfElement = Array.from(document.getElementsByClassName('wtf-user')).filter(i => i.dataset.userId === user)[0]; 1790 if(wtfElement) { 1791 wtfElement.remove(); 1792 } 1793 tweetsDataByUser.forEach(tweetData => { 1794 tweetData.user.blocking = true; 1795 }); 1796 tweetsElementsByUser.forEach(tweetElement => { 1797 let blockButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-block')[0]; 1798 if(blockButton) { 1799 if(LOC.unblock_user.message.includes('$SCREEN_NAME$')) { 1800 blockButton.innerText = `${LOC.unblock_user.message.replace('$SCREEN_NAME$', tweet.user.screen_name)}`; 1801 } else { 1802 blockButton.innerText = `${LOC.unblock_user.message} @${tweet.user.screen_name}`; 1803 } 1804 } 1805 }); 1806 break; 1807 } 1808 case 'unblock': { 1809 let user = tweet.user.id_str; 1810 let tweetsDataByUser = timeline.data.filter(i => i.user.id_str === user); 1811 let tweetsElementsByUser = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.userId === user); 1812 tweetsDataByUser.forEach(tweetData => { 1813 tweetData.user.blocking = false; 1814 }); 1815 tweetsElementsByUser.forEach(tweetElement => { 1816 let blockButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-block')[0]; 1817 if(blockButton) { 1818 if(LOC.block_user.message.includes('$SCREEN_NAME$')) { 1819 blockButton.innerText = `${LOC.block_user.message.replace('$SCREEN_NAME$', tweet.user.screen_name)}`; 1820 } else { 1821 blockButton.innerText = `${LOC.block_user.message} @${tweet.user.screen_name}`; 1822 } 1823 } 1824 }); 1825 break; 1826 } 1827 case 'mute': { 1828 tweet.conversation_muted = true; 1829 let tweetElement = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.tweetId === tweet.id_str)[0]; 1830 if(tweetElement) { 1831 let muteButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-mute')[0]; 1832 if(muteButton) muteButton.innerText = LOC.unmute_convo.message; 1833 } 1834 break; 1835 } 1836 case 'unmute': { 1837 tweet.conversation_muted = false; 1838 let tweetElement = Array.from(document.getElementsByClassName('tweet')).filter(i => i.dataset.tweetId === tweet.id_str)[0]; 1839 if(tweetElement) { 1840 let muteButton = tweetElement.getElementsByClassName('tweet-interact-more-menu-mute')[0]; 1841 if(muteButton) muteButton.innerText = LOC.mute_convo.message; 1842 } 1843 break; 1844 } 1845 } 1846 }); 1847 window.addEventListener("popstate", e => { 1848 if(document.querySelector('.message-leave')) { 1849 e.preventDefault(); 1850 e.stopImmediatePropagation(); 1851 e.stopPropagation(); 1852 document.querySelector(".message-header-back").click(); 1853 } 1854 }); 1855 1856 // menu 1857 let userMenu = document.getElementById('navbar-user-menu'); 1858 userAvatar.addEventListener('click', () => { 1859 if(!userMenu.hidden) { 1860 return userMenu.hidden = true; 1861 } 1862 userMenu.hidden = false; 1863 setTimeout(() => { 1864 document.body.addEventListener('click', e => { 1865 setTimeout(() => { 1866 userMenu.hidden = true; 1867 }, 70); 1868 }, { once: true }); 1869 }, 70); 1870 }); 1871 updateUnread(); 1872 updateAccounts(); 1873 updateInboxData(); 1874 setInterval(updateAccounts, 60000*5); 1875 setInterval(updateUnread, 20000); 1876 setInterval(updateInboxData, 20000); 1877} 1878 1879setInterval(() => { 1880 if(!vars.timeMode) return; 1881 let dark = isDark(); 1882 if(dark !== isDarkModeEnabled) { 1883 isDarkModeEnabled = dark; 1884 switchDarkMode(dark); 1885 } 1886}, 60000); 1887 1888(async () => { 1889 if(!vars) { 1890 await varsPromise; 1891 } 1892 if(vars.darkMode || (vars.timeMode && isDark())) { 1893 let bg = getComputedStyle(document.querySelector(':root')).getPropertyValue('--background-color').trim(); 1894 if(bg === '') { 1895 while(bg !== 'white' && bg !== '#1b2836') { 1896 await sleep(50); 1897 bg = getComputedStyle(document.querySelector(':root')).getPropertyValue('--background-color').trim(); 1898 if(bg === 'white') { 1899 isDarkModeEnabled = true; 1900 switchDarkMode(true); 1901 } 1902 } 1903 } 1904 } 1905 1906 setTimeout(() => { 1907 if(!headerGotUser) { 1908 API.account.verifyCredentials().then(async u => { 1909 userDataFunction(u); 1910 }); 1911 } 1912 }, 1750); 1913 setTimeout(() => { 1914 let version = document.getElementById('oldtwitter-version'); 1915 if(version) { 1916 fetch(`https://raw.githubusercontent.com/dimdenGD/OldTwitter/master/manifest.json?t=${Date.now()}`).then(res => res.json()).then(res => { 1917 version.innerText += ` (${LOC.last_version.message}: ${res.version})`; 1918 if(TRANSLATORS[LANGUAGE]) { 1919 let translated_by = document.createElement('span'); 1920 if(typeof TRANSLATORS[LANGUAGE][0] === 'object') { 1921 let as = []; 1922 for(let translator of TRANSLATORS[LANGUAGE]) { 1923 as.push(`<a${translator[1] ? ` target="_blank" href="${translator[1]}"` : ''}>${translator[0]}</a>`); 1924 } 1925 translated_by.innerHTML = ` ${LOC.translated_by.message.replace("$TRANSLATOR$", as.join(', '))}<br>`; 1926 } else { 1927 translated_by.innerHTML = ` ${LOC.translated_by.message.replace("$TRANSLATOR$", `<a${TRANSLATORS[LANGUAGE][1] ? ` target="_blank" href="${TRANSLATORS[LANGUAGE][1]}"` : ''}>${TRANSLATORS[LANGUAGE][0]}</a>`)}<br>`; 1928 } 1929 document.getElementById('about').children[0].append(translated_by); 1930 } else { 1931 document.getElementById('about').children[0].append(document.createElement('br')); 1932 } 1933 }); 1934 } 1935 let about = document.getElementById('about'); 1936 if(about && !location.pathname.startsWith('/old/') && !location.pathname.startsWith('/i/timeline')) { 1937 let a = document.createElement('a'); 1938 let hrefUrl = new URL(location.href); 1939 let searchParams = new URLSearchParams(hrefUrl.search); 1940 searchParams.set('newtwitter', 'true'); 1941 hrefUrl.search = searchParams.toString(); 1942 a.href = hrefUrl.toString(); 1943 setInterval(() => { 1944 let hrefUrl = new URL(location.href); 1945 let searchParams = new URLSearchParams(hrefUrl.search); 1946 searchParams.set('newtwitter', 'true'); 1947 hrefUrl.search = searchParams.toString(); 1948 a.href = hrefUrl.toString(); 1949 }, 500); 1950 a.innerText = `[${LOC.open_newtwitter.message}]`; 1951 a.addEventListener('click', e => { 1952 e.stopImmediatePropagation(); 1953 }); 1954 a.style.color = 'var(--light-gray)'; 1955 about.appendChild(a); 1956 } 1957 if(Math.random() > 0.99) { 1958 document.getElementById('donate-button').innerHTML += ' <span style="vertical-align: middle;">🥺</span>'; 1959 } 1960 }, 500); 1961 1962 let root = document.querySelector(":root"); 1963 document.addEventListener('updatePageUserData', e => { 1964 let pageUser = e.detail; 1965 if(pageUser.profile_link_color && pageUser.profile_link_color !== '1DA1F2') { 1966 customSet = true; 1967 root.style.setProperty('--link-color', pageUser.profile_link_color); 1968 } 1969 }); 1970 1971 hideStuff(); 1972 setTimeout(hideStuff, 1000); // weird issue on firefox 1973 1974 // custom css 1975 document.addEventListener('customCSS', updateCustomCSS); 1976 document.addEventListener('customCSSVariables', () => switchDarkMode(isDarkModeEnabled)); 1977 document.addEventListener('roundAvatars', e => switchRoundAvatars(e.detail)); 1978 1979 // hotkeys 1980 if(!vars.disableHotkeys) { 1981 function processHotkeys() { 1982 if (keysHeld['Alt'] && keysHeld['Control'] && keysHeld['KeyO']) { 1983 let url = new URL(location.href); 1984 url.searchParams.set('newtwitter', 'true'); 1985 location.replace(url.href); 1986 } else if(keysHeld['Alt'] && keysHeld['Control'] && keysHeld['KeyD']) { 1987 if(vars.developerMode) chrome.storage.sync.get('extensiveLogging', res => { 1988 chrome.storage.sync.set({ extensiveLogging: !res.extensiveLogging }, () => { 1989 if(!res.extensiveLogging) { 1990 toast.success('Extensive logging enabled', 3000); 1991 } else { 1992 toast.error('Extensive logging disabled', 3000); 1993 } 1994 vars.extensiveLogging = !res.extensiveLogging; 1995 }); 1996 }); 1997 } else if(keysHeld['Alt'] && keysHeld['Control'] && keysHeld['KeyM']) { 1998 if(vars.developerMode) { 1999 let pass = prompt('Enter password'); 2000 fetch(`https://dimden.dev/services/twitter_link_colors/v2/admin/verify`, { 2001 method: 'POST', 2002 headers: { 2003 'Content-Type': 'application/json' 2004 }, 2005 body: JSON.stringify({ 2006 password: pass 2007 }) 2008 }).then(res => res.text()).then(res => { 2009 if(res === "ok") { 2010 chrome.storage.local.set({ adminpass: pass }, () => { 2011 toast.success('Password set', 3000); 2012 }); 2013 } else { 2014 toast.error('Wrong password', 3000); 2015 } 2016 }); 2017 } 2018 } else if(keysHeld['KeyG'] && keysHeld['KeyH']) { 2019 location.href = '/'; 2020 } else if(keysHeld['KeyG'] && keysHeld['KeyN']) { 2021 location.href = '/notifications'; 2022 } else if(keysHeld['KeyG'] && keysHeld['KeyR']) { 2023 location.href = '/notifications/mentions'; 2024 } else if(keysHeld['KeyG'] && keysHeld['KeyP']) { 2025 location.href = `/${user.screen_name}`; 2026 } else if(keysHeld['KeyG'] && keysHeld['KeyL']) { 2027 location.href = `/${user.screen_name}/likes`; 2028 } else if(keysHeld['KeyG'] && keysHeld['KeyI']) { 2029 location.href = `/${user.screen_name}/lists`; 2030 } else if(keysHeld['KeyG'] && keysHeld['KeyM']) { 2031 document.getElementById("messages").click(); 2032 } else if(keysHeld['KeyG'] && keysHeld['KeyS']) { 2033 location.href = `/old/settings`; 2034 } else if(keysHeld['KeyG'] && keysHeld['KeyB']) { 2035 location.href = `/i/bookmarks`; 2036 } else if(keysHeld['KeyG'] && keysHeld['KeyU']) { 2037 location.href = `/unfollows/followers`; 2038 } 2039 } 2040 window.addEventListener('keydown', (ev) => { 2041 let key = ev.code; 2042 if(key === 'AltLeft' || key === 'AltRight') key = 'Alt'; 2043 if(key === 'ControlLeft' || key === 'ControlRight') key = 'Control'; 2044 if(key === 'ShiftLeft' || key === 'ShiftRight') key = 'Shift'; 2045 if(ev.target.tagName === 'INPUT' || ev.target.tagName === 'TEXTAREA') { 2046 if(keysHeld['KeyG']) { 2047 processHotkeys(); 2048 } 2049 } else { 2050 keysHeld[key] = true; 2051 processHotkeys(); 2052 } 2053 }); 2054 2055 window.addEventListener('keyup', (ev) => { 2056 let key = ev.code; 2057 if(key === 'AltLeft' || key === 'AltRight') key = 'Alt'; 2058 if(key === 'ControlLeft' || key === 'ControlRight') key = 'Control'; 2059 if(key === 'ShiftLeft' || key === 'ShiftRight') key = 'Shift'; 2060 2061 if(ev.target.tagName === 'INPUT' || ev.target.tagName === 'TEXTAREA') { 2062 if(keysHeld['KeyG']) { 2063 keysHeld[key] = true; 2064 processHotkeys(); 2065 } 2066 } else { 2067 delete keysHeld[key]; 2068 } 2069 }); 2070 2071 let tle = document.getElementById('timeline'); 2072 if(!tle) tle = document.getElementById('list-tweets'); 2073 document.addEventListener('keydown', async e => { 2074 if(e.ctrlKey || keysHeld['KeyG']) return; 2075 // reply box 2076 if(e.target.className === 'tweet-reply-text') { 2077 if(e.altKey) { 2078 if(e.keyCode === 82) { // ALT+R 2079 // hide reply box 2080 e.target.blur(); 2081 activeTweet.getElementsByClassName('tweet-interact-reply')[0].click(); 2082 } else if(e.keyCode === 77) { // ALT+M 2083 // upload media 2084 let tweetReplyUpload = activeTweet.getElementsByClassName('tweet-reply-upload')[0]; 2085 tweetReplyUpload.click(); 2086 } else if(e.keyCode === 70) { // ALT+F 2087 // remove first media 2088 e.preventDefault(); 2089 e.stopImmediatePropagation(); 2090 let tweetReplyMediaElement = activeTweet.getElementsByClassName('tweet-reply-media')[0].children[0]; 2091 if(!tweetReplyMediaElement) return; 2092 let removeBtn = tweetReplyMediaElement.getElementsByClassName('new-tweet-media-img-remove')[0]; 2093 removeBtn.click(); 2094 } 2095 } 2096 } 2097 if(e.target.className === 'tweet-quote-text') { 2098 if(e.altKey) { 2099 if(e.keyCode === 81) { // ALT+Q 2100 // hide quote box 2101 e.target.blur(); 2102 activeTweet.getElementsByClassName('tweet-interact-retweet')[0].click(); 2103 } else if(e.keyCode === 77) { // ALT+M 2104 // upload media 2105 let tweetQuoteUpload = activeTweet.getElementsByClassName('tweet-quote-upload')[0]; 2106 tweetQuoteUpload.click(); 2107 } else if(e.keyCode === 70) { // ALT+F 2108 // remove first media 2109 e.preventDefault(); 2110 e.stopImmediatePropagation(); 2111 let tweetQuoteMediaElement = activeTweet.getElementsByClassName('tweet-quote-media')[0].children[0]; 2112 if(!tweetQuoteMediaElement) return; 2113 let removeBtn = tweetQuoteMediaElement.getElementsByClassName('new-tweet-media-img-remove')[0]; 2114 removeBtn.click(); 2115 } 2116 } 2117 } 2118 if(e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'EMOJI-PICKER') return; 2119 if(e.keyCode === 83) { // S 2120 // next tweet 2121 let index = [...tle.children].indexOf(activeTweet); 2122 if(index === -1) return; 2123 let nextTweet = tle.children[index + 1]; 2124 if(!nextTweet) return; 2125 nextTweet.focus(); 2126 nextTweet.scrollIntoView({ block: 'center' }); 2127 } else if(e.keyCode === 87) { // W 2128 // previous tweet 2129 let index = [...tle.children].indexOf(activeTweet); 2130 if(index === -1) return; 2131 let nextTweet = tle.children[index - 1]; 2132 if(!nextTweet) return; 2133 nextTweet.focus(); 2134 nextTweet.scrollIntoView({ block: 'center' }); 2135 } else if(e.keyCode === 76) { // L 2136 // like tweet 2137 if(!activeTweet) return; 2138 let tweetFavoriteButton = activeTweet.querySelector('.tweet-interact-favorite'); 2139 tweetFavoriteButton.click(); 2140 } else if(e.keyCode === 66) { // B 2141 // bookmark tweet 2142 if(!activeTweet) return; 2143 let tweetFavoriteButton = activeTweet.querySelector('.tweet-interact-more-menu-bookmark'); 2144 tweetFavoriteButton.click(); 2145 } else if(e.keyCode === 84) { // T 2146 // retweet 2147 if(!activeTweet) return; 2148 let hasRetweetedWithHotkeyBefore = await new Promise(resolve => { 2149 chrome.storage.local.get(['hasRetweetedWithHotkey'], data => { 2150 resolve(data.hasRetweetedWithHotkey); 2151 }); 2152 }); 2153 if(!hasRetweetedWithHotkeyBefore) { 2154 let c = confirm(LOC.retweet_hotkey_warn.message); 2155 if(c) { 2156 chrome.storage.local.set({hasRetweetedWithHotkey: true}, () => {}); 2157 } else { 2158 return; 2159 } 2160 } 2161 let tweetRetweetButton = activeTweet.querySelector('.tweet-interact-retweet-menu-retweet'); 2162 tweetRetweetButton.click(); 2163 } else if(e.keyCode === 82) { // R 2164 // open reply box 2165 if(!activeTweet) return; 2166 e.preventDefault(); 2167 e.stopImmediatePropagation(); 2168 let tweetReply = activeTweet.getElementsByClassName('tweet-reply')[0]; 2169 let tweetQuote = activeTweet.getElementsByClassName('tweet-quote')[0]; 2170 let tweetReplyText = activeTweet.getElementsByClassName('tweet-reply-text')[0]; 2171 2172 tweetReply.hidden = false; 2173 tweetQuote.hidden = true; 2174 tweetReplyText.focus(); 2175 } else if(e.keyCode === 81) { // Q 2176 // open quote box 2177 if(!activeTweet) return; 2178 e.preventDefault(); 2179 e.stopImmediatePropagation(); 2180 let tweetReply = activeTweet.getElementsByClassName('tweet-reply')[0]; 2181 let tweetQuote = activeTweet.getElementsByClassName('tweet-quote')[0]; 2182 let tweetQuoteText = activeTweet.getElementsByClassName('tweet-quote-text')[0]; 2183 2184 tweetReply.hidden = true; 2185 tweetQuote.hidden = false; 2186 tweetQuoteText.focus(); 2187 } else if(e.keyCode === 32) { // Space 2188 // toggle tweet media 2189 if(!activeTweet) return; 2190 e.preventDefault(); 2191 e.stopImmediatePropagation(); 2192 let tweetMedia = activeTweet.getElementsByClassName('tweet-media')[0].children[0]; 2193 if(!tweetMedia) return; 2194 if(tweetMedia.tagName === "VIDEO") { 2195 tweetMedia.paused ? tweetMedia.play() : tweetMedia.pause(); 2196 } else { 2197 tweetMedia.click(); 2198 tweetMedia.click(); 2199 } 2200 } else if(e.keyCode === 13) { // Enter 2201 // open tweet 2202 if(e.target.className.includes('tweet tweet-id-')) { 2203 if(!activeTweet) return; 2204 e.preventDefault(); 2205 e.stopImmediatePropagation(); 2206 activeTweet.click(); 2207 } else if(e.target.className === "tweet-interact-more") { 2208 e.target.click(); 2209 activeTweet.getElementsByClassName('tweet-interact-more-menu-copy')[0].focus(); 2210 } 2211 } else if(e.keyCode === 67 && !e.ctrlKey && !e.altKey) { // C 2212 // copy image 2213 if(e.target.className.includes('tweet tweet-id-')) { 2214 if(!activeTweet) return; 2215 let media = activeTweet.getElementsByClassName('tweet-media')[0]; 2216 if(!media) return; 2217 media = media.children[0]; 2218 if(!media) return; 2219 if(media.tagName === "IMG") { 2220 let img = media; 2221 let canvas = document.createElement('canvas'); 2222 canvas.width = img.width; 2223 canvas.height = img.height; 2224 let ctx = canvas.getContext('2d'); 2225 ctx.drawImage(img, 0, 0, img.width, img.height); 2226 canvas.toBlob((blob) => { 2227 navigator.clipboard.write([ 2228 new ClipboardItem({ "image/png": blob }) 2229 ]); 2230 }, "image/png"); 2231 } 2232 } 2233 } else if(e.keyCode === 68 && !e.ctrlKey && !e.altKey) { // D 2234 // download media 2235 if(activeTweet.className.includes('tweet tweet-id-')) { 2236 activeTweet.getElementsByClassName('tweet-interact-more-menu-download')[0].click(); 2237 } 2238 } 2239 }); 2240 let searchInput = document.getElementById('search-input'); 2241 document.addEventListener('keydown', e => { 2242 if(document.activeElement === searchInput && e.altKey && e.keyCode === 70) { // Alt+F 2243 // blur search bar 2244 e.preventDefault(); 2245 e.stopImmediatePropagation(); 2246 searchInput.blur(); 2247 } 2248 if(e.target.className === 'navbar-new-tweet-text' && e.altKey) { 2249 let m = document.getElementsByClassName('navbar-new-tweet-container')[0]; 2250 if(e.keyCode === 77) { // ALT+M 2251 // upload media 2252 let tweetUpload = m.getElementsByClassName('navbar-new-tweet-media')[0]; 2253 tweetUpload.click(); 2254 } else if(e.keyCode === 70) { // ALT+F 2255 // remove first media 2256 e.preventDefault(); 2257 e.stopImmediatePropagation(); 2258 let tweetMediaElement = m.getElementsByClassName('navbar-new-tweet-media-c')[0].children[0]; 2259 if(!tweetMediaElement) return; 2260 let removeBtn = tweetMediaElement.getElementsByClassName('new-tweet-media-img-remove')[0]; 2261 removeBtn.click(); 2262 } 2263 } 2264 if(e.target.id === 'new-tweet-text' && e.altKey) { 2265 if(e.keyCode === 77) { // ALT+M 2266 // upload media 2267 let tweetUpload = document.getElementById('new-tweet-media'); 2268 tweetUpload.click(); 2269 } else if(e.keyCode === 70) { // ALT+F 2270 // remove first media 2271 e.preventDefault(); 2272 e.stopImmediatePropagation(); 2273 let tweetMediaElement = document.getElementById('new-tweet-media-c').children[0]; 2274 if(!tweetMediaElement) return; 2275 let removeBtn = tweetMediaElement.getElementsByClassName('new-tweet-media-img-remove')[0]; 2276 removeBtn.click(); 2277 } else if(e.keyCode === 78) { // ALT+N 2278 // unfocus new tweet 2279 e.target.blur(); 2280 let f = document.getElementById('timeline').firstChild; 2281 f.scrollIntoView(); 2282 f.focus(); 2283 e.preventDefault(); 2284 e.stopImmediatePropagation(); 2285 } 2286 } 2287 2288 if(e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.tagName === 'EMOJI-PICKER') return; 2289 2290 if(!e.altKey && !e.ctrlKey && e.keyCode === 70) { // F 2291 // focus search bar 2292 searchInput.focus(); 2293 e.preventDefault(); 2294 e.stopImmediatePropagation(); 2295 } 2296 if(!e.altKey && !e.ctrlKey && e.keyCode === 78) { // N 2297 // new tweet 2298 e.preventDefault(); 2299 e.stopImmediatePropagation(); 2300 let event = new CustomEvent('clearActiveTweet'); 2301 document.dispatchEvent(event); 2302 if(scrollY < 400 && (location.pathname === '/' || location.pathname === '/home')) { 2303 let newTweetText = document.getElementById('new-tweet-text'); 2304 document.getElementById('new-tweet').click(); 2305 newTweetText.focus(); 2306 document.scrollingElement.scrollTop = 0; 2307 } else { 2308 document.getElementById('navbar-tweet-button').click(); 2309 } 2310 } 2311 if(!e.altKey && !e.ctrlKey && e.keyCode === 77) { // M 2312 document.getElementById('navbar-user-avatar').click(); 2313 if(!document.getElementById('navbar-user-menu').hidden) { 2314 document.getElementById('navbar-user-menu-profile').focus(); 2315 } else { 2316 document.activeElement.blur(); 2317 document.removeEventListener('click', menuFn); 2318 menuFn(); 2319 menuFn = undefined; 2320 } 2321 } 2322 }); 2323 } else { 2324 let style = document.createElement('style'); 2325 style.innerHTML = `.tweet-interact::after { content: '' !important; }`; 2326 document.head.appendChild(style); 2327 } 2328 2329 function fullscreenEvent(fullscreen) { 2330 if(fullscreen) { 2331 let style = document.createElement('style'); 2332 style.innerHTML = `.tweet-media-element-quote { object-fit: contain !important; }`; 2333 style.id = 'fullscreen-style'; 2334 document.head.appendChild(style); 2335 } else { 2336 let style = document.getElementById('fullscreen-style'); 2337 if(style) style.remove(); 2338 } 2339 } 2340 2341 document.getElementById('notifications').addEventListener('click', e => { 2342 if(vars.openNotifsAsModal) { 2343 e.preventDefault(); 2344 e.stopImmediatePropagation(); 2345 createModal(` 2346 <iframe src="/notifications?nonavbar=1" id="notifications-iframe"></iframe> 2347 `, 'notifications-modal'); 2348 } 2349 }); 2350 2351 switchDarkMode(vars.darkMode || (vars.timeMode && isDark())); 2352 updateCustomCSS(); 2353 2354 window.addEventListener('resize', () => { 2355 if (window.matchMedia('(display-mode: fullscreen)').matches || window.document.fullscreenElement) { 2356 fullscreenEvent(true); 2357 } else { 2358 fullscreenEvent(false); 2359 } 2360 }, { passive: true }); 2361 setTimeout(() => { 2362 document.getElementById('navbar-user-avatar').addEventListener('click', () => { 2363 if(headerGotUser) return; 2364 API.account.verifyCredentials().then(async u => { 2365 userDataFunction({ detail: u }); 2366 }); 2367 }); 2368 }, 1000); 2369})();