Extension to return old Twitter layout from 2015.
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})();