Extension to return old Twitter layout from 2015.
1let user = {};
2let pageUser = {};
3let timeline = {
4 data: [],
5 dataToUpdate: [],
6 toBeUpdated: 0
7}
8let seenThreads = [];
9let averageLikeCount = 1;
10let pinnedTweet, followersYouFollow;
11let previousLastTweet, stopLoad = false;
12let tweetsCursor, favoritesCursor, followingCursor, followersCursor, followersYouKnowCursor, mediaCursor;
13let cssEligibleAuto = false, cssEligible = false;
14
15// Util
16
17let subpage;
18let user_handle = location.pathname.slice(1).split("?")[0].split('#')[0];
19user_handle = user_handle.split('/')[0];
20let user_protected = false;
21let user_blocked_by = false;
22let user_blocking = false;
23function updateSubpage() {
24 previousLastTweet = undefined; stopLoad = false;
25 averageLikeCount = 1;
26 user_handle = location.pathname.slice(1).split("?")[0].split('#')[0];
27 if(user_handle.split('/').length === 1) {
28 subpage = 'profile';
29 } else {
30 if(user_handle.endsWith('/with_replies')) {
31 subpage = 'replies';
32 } else if(user_handle.endsWith('/media')) {
33 subpage = 'media';
34 } else if(user_handle.endsWith('/likes')) {
35 subpage = 'likes';
36 } else if(user_handle.endsWith('/following')) {
37 subpage = 'following';
38 } else if(user_handle.endsWith('/followers')) {
39 subpage = 'followers';
40 } else if(user_handle.endsWith('/followers_you_follow')) {
41 subpage = 'followers_you_follow';
42 } else if(user_handle.endsWith('/lists')) {
43 subpage = 'lists';
44 }
45 }
46 user_handle = user_handle.split('/')[0];
47}
48
49function updateSelection() {
50 document.getElementById('style-hide-retweets').innerHTML = '';
51 document.getElementById('tweet-nav-more-menu-hr').checked = false;
52 document.getElementById('tweet-nav-more-menu-hnr').checked = false;
53
54 let activeStats = Array.from(document.getElementsByClassName('profile-stat-active'));
55 for(let i in activeStats) {
56 if(activeStats[i].classList.contains('profile-stat-active')) {
57 activeStats[i].classList.remove('profile-stat-active');
58 }
59 }
60 let activeNavs = Array.from(document.getElementsByClassName('tweet-nav-active'));
61 for(let i in activeNavs) {
62 if(activeNavs[i].classList.contains('tweet-nav-active')) {
63 activeNavs[i].classList.remove('tweet-nav-active');
64 }
65 }
66
67 if(subpage === "profile") {
68 document.getElementById('tweet-nav').hidden = false;
69 document.getElementById('timeline').hidden = false;
70 document.getElementById('following-list').hidden = true;
71 document.getElementById('followers-list').hidden = true;
72 document.getElementById('following-more').hidden = true;
73 document.getElementById('followers-more').hidden = true;
74 document.getElementById('followers_you_follow-list').hidden = true;
75 document.getElementById('followers_you_follow-more').hidden = true;
76 document.getElementById('lists-list').hidden = true;
77
78 document.getElementById('profile-stat-tweets-link').classList.add('profile-stat-active');
79 document.getElementById('tweet-nav-tweets').classList.add('tweet-nav-active');
80 } else if(subpage === "likes") {
81 document.getElementById('tweet-nav').hidden = false;
82 document.getElementById('timeline').hidden = false;
83 document.getElementById('following-list').hidden = true;
84 document.getElementById('followers-list').hidden = true;
85 document.getElementById('following-more').hidden = true;
86 document.getElementById('followers-more').hidden = true;
87 document.getElementById('followers_you_follow-list').hidden = true;
88 document.getElementById('followers_you_follow-more').hidden = true;
89 document.getElementById('lists-list').hidden = true;
90
91 document.getElementById('profile-stat-favorites-link').classList.add('profile-stat-active');
92 } else if(subpage === "replies") {
93 document.getElementById('tweet-nav').hidden = false;
94 document.getElementById('timeline').hidden = false;
95 document.getElementById('following-list').hidden = true;
96 document.getElementById('followers-list').hidden = true;
97 document.getElementById('following-more').hidden = true;
98 document.getElementById('followers-more').hidden = true;
99 document.getElementById('followers_you_follow-list').hidden = true;
100 document.getElementById('followers_you_follow-more').hidden = true;
101 document.getElementById('lists-list').hidden = true;
102
103 document.getElementById('profile-stat-tweets-link').classList.add('profile-stat-active');
104 document.getElementById('tweet-nav-replies').classList.add('tweet-nav-active');
105 } else if(subpage === "media") {
106 document.getElementById('tweet-nav').hidden = false;
107 document.getElementById('timeline').hidden = false;
108 document.getElementById('following-list').hidden = true;
109 document.getElementById('followers-list').hidden = true;
110 document.getElementById('following-more').hidden = true;
111 document.getElementById('followers-more').hidden = true;
112 document.getElementById('followers_you_follow-list').hidden = true;
113 document.getElementById('followers_you_follow-more').hidden = true;
114 document.getElementById('lists-list').hidden = true;
115
116 document.getElementById('profile-stat-media-link').classList.add('profile-stat-active');
117 document.getElementById('tweet-nav-media').classList.add('tweet-nav-active');
118 } else if(subpage === "following") {
119 document.getElementById('tweet-nav').hidden = true;
120 document.getElementById('timeline').hidden = true;
121 document.getElementById('following-list').hidden = false;
122 document.getElementById('followers-list').hidden = true;
123 document.getElementById('following-more').hidden = false;
124 document.getElementById('followers-more').hidden = true;
125 document.getElementById('followers_you_follow-list').hidden = true;
126 document.getElementById('followers_you_follow-more').hidden = true;
127 document.getElementById('lists-list').hidden = true;
128
129 document.getElementById('profile-stat-following-link').classList.add('profile-stat-active');
130 } else if(subpage === "followers") {
131 document.getElementById('tweet-nav').hidden = true;
132 document.getElementById('timeline').hidden = true;
133 document.getElementById('following-list').hidden = true;
134 document.getElementById('followers-list').hidden = false;
135 document.getElementById('following-more').hidden = true;
136 document.getElementById('followers-more').hidden = false;
137 document.getElementById('followers_you_follow-list').hidden = true;
138 document.getElementById('followers_you_follow-more').hidden = true;
139 document.getElementById('lists-list').hidden = true;
140
141 document.getElementById('profile-stat-followers-link').classList.add('profile-stat-active');
142 } else if(subpage === "followers_you_follow") {
143 document.getElementById('tweet-nav').hidden = true;
144 document.getElementById('timeline').hidden = true;
145 document.getElementById('following-list').hidden = true;
146 document.getElementById('followers-list').hidden = true;
147 document.getElementById('following-more').hidden = true;
148 document.getElementById('followers-more').hidden = true;
149 document.getElementById('followers_you_follow-list').hidden = false;
150 document.getElementById('followers_you_follow-more').hidden = false;
151 document.getElementById('lists-list').hidden = true;
152
153 document.getElementById('profile-stat-followers-link').classList.add('profile-stat-active');
154 } else if(subpage === "lists") {
155 document.getElementById('tweet-nav').hidden = true;
156 document.getElementById('timeline').hidden = true;
157 document.getElementById('following-list').hidden = true;
158 document.getElementById('followers-list').hidden = true;
159 document.getElementById('following-more').hidden = true;
160 document.getElementById('followers-more').hidden = true;
161 document.getElementById('followers_you_follow-list').hidden = true;
162 document.getElementById('followers_you_follow-more').hidden = true;
163 document.getElementById('lists-list').hidden = false;
164 }
165 document.getElementById('profile-stat-tweets-link').href = `https://twitter.com/${pageUser.screen_name}`;
166 document.getElementById('profile-stat-following-link').href = `https://twitter.com/${pageUser.screen_name}/following`;
167 document.getElementById('profile-stat-followers-link').href = `https://twitter.com/${pageUser.screen_name}/followers`;
168 document.getElementById('profile-stat-favorites-link').href = `https://twitter.com/${pageUser.screen_name}/likes`;
169 document.getElementById('profile-stat-media-link').href = `https://twitter.com/${pageUser.screen_name}/media`;
170 document.getElementById('tweet-nav-tweets').href = `https://twitter.com/${pageUser.screen_name}`;
171 document.getElementById('tweet-nav-replies').href = `https://twitter.com/${pageUser.screen_name}/with_replies`;
172 document.getElementById('tweet-nav-media').href = `https://twitter.com/${pageUser.screen_name}/media`;
173
174 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies')) {
175 document.getElementById('trends').hidden = true;
176 document.getElementById('no-tweets').hidden = false;
177 document.getElementById('no-tweets').innerHTML = `
178 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
179 <p>${LOC.when_theyll_tweet.message}</p>
180 `;
181 } else {
182 document.getElementById('trends').hidden = false;
183 document.getElementById('no-tweets').hidden = true;
184 document.getElementById('no-tweets').innerHTML = ``;
185 }
186}
187
188function updateUserData() {
189 return new Promise(async (resolve, reject) => {
190 document.getElementsByTagName('title')[0].innerText = `${user_handle} - ` + LOC.twitter.message;
191 let [pageUserData, followersYouFollowData, oldUser, u] = await Promise.allSettled([
192 API.user.getV2(user_handle),
193 API.user.friendsFollowing(user_handle, false),
194 API.user.get(user_handle, false),
195 API.account.verifyCredentials()
196 ]).catch(e => {
197 document.getElementById('loading-box').hidden = false;
198 if(String(e).includes('User has been suspended.')) {
199 return document.getElementById('loading-box-error').innerHTML = `${LOC.user_was_suspended.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
200 }
201 if(String(e).includes("reading 'result'")) {
202 return document.getElementById('loading-box-error').innerHTML = `${LOC.user_was_not_found.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
203 }
204 return document.getElementById('loading-box-error').innerHTML = `${String(e)}.<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
205 });
206 if(oldUser.reason) {
207 let e = oldUser.reason;
208 if(String(e).includes('User has been suspended.')) {
209 document.getElementById('loading-box').hidden = false;
210 return document.getElementById('loading-box-error').innerHTML = `${LOC.user_was_suspended.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
211 }
212 }
213 if(pageUserData.reason) {
214 let e = pageUserData.reason;
215 document.getElementById('loading-box').hidden = false;
216 if(String(e).includes("reading 'result'")) {
217 return document.getElementById('loading-box-error').innerHTML = `${LOC.user_was_not_found.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
218 }
219 return document.getElementById('loading-box-error').innerHTML = `${String(e)}.<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`;
220 }
221 followersYouFollowData = followersYouFollowData.value;
222 oldUser = oldUser.value;
223 u = u.value;
224 user = u;
225 pageUserData = pageUserData.value;
226 //default value
227 user_blocked_by = false;
228 user_blocking = false;
229 user_protected = false;
230 if (pageUserData.blocked_by) {
231 user_blocked_by = true;
232 }
233 if (pageUserData.blocking) {
234 user_blocking = true;
235 }
236 if (pageUserData.protected && !pageUserData.following && pageUserData.id_str !== user.id_str) {
237 user_protected = true;
238 }
239 userDataFunction(u);
240 const event2 = new CustomEvent('updatePageUserData', { detail: oldUser });
241 document.dispatchEvent(event2);
242 pageUser = pageUserData;
243 pageUser.protected = oldUser.protected;
244 let r = document.querySelector(':root');
245 let usedProfileColor = vars && vars.linkColor ? vars.linkColor : '#4595B5';
246 r.style.setProperty('--link-color', usedProfileColor);
247 let sc = makeSeeableColor(oldUser.profile_link_color);
248 if(oldUser.profile_link_color && oldUser.profile_link_color !== '1DA1F2') {
249 customSet = true;
250 r.style.setProperty('--link-color', sc);
251 usedProfileColor = oldUser.profile_link_color;
252 document.getElementById('color-years-ago').hidden = false;
253 } else {
254 document.getElementById('color-years-ago').hidden = true;
255 }
256
257 const profileLinkColor = document.getElementById('profile-link-color');
258 const colorPreviewLight = document.getElementById('color-preview-light');
259 const colorPreviewDark = document.getElementById('color-preview-dark');
260 const colorPreviewBlack = document.getElementById('color-preview-black');
261 const darkModeVars = document.getElementById('dark-mode-vars');
262 const lightModeVars = document.getElementById('light-mode-vars');
263 const cssTextArea = document.getElementById('profile-css-textarea');
264
265 profileLinkColor.value = `#${usedProfileColor}`;
266 profileLinkColor.addEventListener('input', () => {
267 let color = profileLinkColor.value;
268 if(color.startsWith('#')) color = color.slice(1);
269 let sc = makeSeeableColor(color);
270 customSet = true;
271 r.style.setProperty('--link-color', sc);
272
273 colorPreviewLight.style.color = makeSeeableColor(`#${color}`, "#ffffff");
274 colorPreviewDark.style.color = makeSeeableColor(`#${color}`, "#1b2836");
275 colorPreviewBlack.style.color = makeSeeableColor(`#${color}`, "#000000");
276 });
277
278 colorPreviewLight.style.color = makeSeeableColor(`#${usedProfileColor}`, "#ffffff");
279 colorPreviewDark.style.color = makeSeeableColor(`#${usedProfileColor}`, "#1b2836");
280 colorPreviewBlack.style.color = makeSeeableColor(`#${usedProfileColor}`, "#000000");
281
282 let profileCustomCSSData = {};
283 let pccss = await new Promise(resolve => {
284 chrome.storage.local.get(["profileCustomCSS"], async data => {
285 if(!data.profileCustomCSS) {
286 data.profileCustomCSS = {};
287 }
288 profileCustomCSSData = data.profileCustomCSS;
289 if(data.profileCustomCSS[pageUser.id_str]) {
290 resolve(data.profileCustomCSS[pageUser.id_str]);
291 } else {
292 resolve({});
293 }
294 });
295 });
296 if(pccss.darkModeVars && isDarkModeEnabled) {
297 let vars = parseVariables(pccss.darkModeVars);
298 for(let i in vars) {
299 r.style.setProperty(i, vars[i]);
300 }
301 } else if(pccss.lightModeVars && !isDarkModeEnabled) {
302 let vars = parseVariables(pccss.lightModeVars);
303 for(let i in vars) {
304 r.style.setProperty(i, vars[i]);
305 }
306 } else {
307 await switchDarkMode(isDarkModeEnabled);
308 }
309 if(pccss.css) {
310 if(!customCSS) {
311 customCSS = document.createElement('style');
312 customCSS.id = 'oldtwitter-custom-css';
313 document.head.appendChild(customCSS);
314 }
315 customCSS.innerHTML = pccss.css;
316 } else {
317 updateCustomCSS();
318 }
319
320 getLinkColors(pageUserData.id_str).then(data => {
321 let color = data[0];
322 if(color) color = color.color;
323
324 if(color && color !== 'none') {
325 let sc = makeSeeableColor(color);
326 customSet = true;
327 r.style.setProperty('--link-color', sc);
328 usedProfileColor = color;
329 }
330 fetch("https://dimden.dev/services/twitter_link_colors/v2/get_data/"+pageUserData.id_str).then(r => r.json()).then(async data => {
331 if(data.color !== 'none' && data.color !== '4595b5') {
332 if(data.color !== color) {
333 chrome.storage.local.get(["linkColors"], async lc => {
334 let linkColors = lc.linkColors || {};
335 linkColors[pageUserData.id_str] = data.color;
336 chrome.storage.local.set({ linkColors });
337 });
338 let sc = makeSeeableColor(data.color);
339 customSet = true;
340 usedProfileColor = data.color;
341 r.style.setProperty('--link-color', sc);
342 }
343 let pc = `#${data.color}`;
344
345 profileLinkColor.value = pc;
346
347 colorPreviewLight.style.color = makeSeeableColor(pc, "#ffffff");
348 colorPreviewDark.style.color = makeSeeableColor(pc, "#1b2836");
349 colorPreviewBlack.style.color = makeSeeableColor(pc, "#000000");
350 }
351 chrome.storage.local.get(["adminpass"], async a => {
352 if(a.adminpass) {
353 let adminControls = document.getElementById('admin-controls');
354 if(adminControls) adminControls.remove();
355 adminControls = document.createElement('div');
356 adminControls.id = 'admin-controls';
357 console.log(data.css_eligible);
358 adminControls.innerHTML = `
359 <br>
360 Eligible for custom profile CSS: <span id="admin-css-eligible">${data.css_eligible ? 'yes' : 'no'}</span><br>
361 Can get access automatically: <span id="admin-css-eligible-auto">${data.css_eligible_auto ? 'yes' : 'no'}</span><br>
362 Has custom profile CSS: ${data.css || data.css_vars_dark || data.css_vars_light ? 'yes' : 'no'}<br>
363 Has custom color: ${data.color !== 'none' ? 'yes' : 'no'}<br><br>
364 <button id="admin-controls-switch" style="background-color: var(--menu-bg);border: 1px solid var(--border);color: var(--almost-black);cursor: pointer;">Switch</button>
365 <br><br>
366 `;
367 document.getElementById('about').appendChild(adminControls);
368 document.getElementById('admin-controls-switch').addEventListener('click', () => {
369 fetch(`https://dimden.dev/services/twitter_link_colors/v2/admin/switch_access`, {
370 method: 'POST',
371 headers: {
372 'Content-Type': 'application/json'
373 },
374 body: JSON.stringify({
375 password: a.adminpass,
376 user: pageUserData.id_str
377 })
378 }).then(r => r.json()).then(data => {
379 if(data.error) {
380 return alert(data.error);
381 }
382 if(data.eligible) {
383 document.getElementById('admin-css-eligible').innerText = 'yes';
384 document.getElementById('admin-css-eligible-auto').innerText = 'no';
385 } else {
386 document.getElementById('admin-css-eligible').innerText = 'no';
387 document.getElementById('admin-css-eligible-auto').innerText = 'no';
388 }
389 });
390 });
391 }
392 });
393 if(data.css_eligible && !vars.disableProfileCustomizations) {
394 if(pccss.css !== data.css || pccss.darkModeVars !== data.css_vars_dark || pccss.lightModeVars !== data.css_vars_light) {
395 pccss.css = data.css;
396 pccss.darkModeVars = data.css_vars_dark;
397 pccss.lightModeVars = data.css_vars_light;
398 profileCustomCSSData[pageUser.id_str] = pccss;
399 chrome.storage.local.set({ profileCustomCSS: profileCustomCSSData });
400 }
401 let styled = false;
402 if(data.css) {
403 styled = true;
404 profileCSS = true;
405 if(!customCSS) {
406 customCSS = document.createElement('style');
407 customCSS.id = 'oldtwitter-custom-css';
408 document.head.appendChild(customCSS);
409 }
410 customCSS.innerHTML = data.css;
411 } else {
412 profileCSS = false;
413 updateCustomCSS();
414 }
415 if(data.css_vars_dark && isDarkModeEnabled) {
416 styled = true;
417 let vars = parseVariables(data.css_vars_dark);
418 for(let i in vars) {
419 r.style.setProperty(i, vars[i]);
420 }
421 } else if(data.css_vars_light && !isDarkModeEnabled) {
422 styled = true;
423 let vars = parseVariables(data.css_vars_light);
424 for(let i in vars) {
425 r.style.setProperty(i, vars[i]);
426 }
427 } else {
428 await switchDarkMode(isDarkModeEnabled);
429 }
430 if(pageUser.id_str !== user.id_str && styled) {
431 let additionalThing = document.createElement("span");
432 additionalThing.innerHTML = `<a href="https://dimden.dev/ot/custom-css/" target="_blank">${LOC.styled_profile.message}</a>`;
433 additionalThing.className = "profile-additional-thing profile-additional-styled";
434 document.getElementById("profile-additional").appendChild(additionalThing);
435 }
436 } else {
437 profileCSS = false;
438 if(profileCustomCSSData[pageUser.id_str]) {
439 delete profileCustomCSSData[pageUser.id_str];
440 chrome.storage.local.set({ profileCustomCSS: profileCustomCSSData });
441 }
442 updateCustomCSS();
443 await switchDarkMode(isDarkModeEnabled);
444 }
445 if(pageUserData.id_str === user.id_str) {
446 darkModeVars.value = data.css_vars_dark || '';
447 lightModeVars.value = data.css_vars_light || '';
448 cssTextArea.value = data.css || '';
449 cssEligibleAuto = data.css_eligible_auto;
450 cssEligible = data.css_eligible;
451 if(data.css_eligible || (data.css_eligible_auto && user.followers_count >= 5000)) {
452 document.getElementById('custom-css-eligible').hidden = false;
453 document.getElementById('custom-css-not-eligible').hidden = true;
454 if(!vars.acknowledgedCssAccess && !data.css && !data.css_vars_dark && !data.css_vars_light) {
455 let modal = createModal(`
456 <div style="color:var(--almost-black);max-width:500px">
457 <h2 class="nice-header">${LOC.profile_custom_css.message}</h2><br>
458 <span>${LOC.pccss_congrats.message}</span>
459 <br><br>
460 <div style="display:inline-block;float: right;">
461 <button class="nice-button">${LOC.yay.message}</button>
462 </div>
463 </div>
464 `, 'css-congrats-modal', () => {
465 chrome.storage.sync.set({ acknowledgedCssAccess: true });
466 }, () => false);
467 modal.querySelector('button').addEventListener('click', () => {
468 modal.removeModal();
469 });
470 }
471 } else {
472 document.getElementById('custom-css-eligible').hidden = true;
473 document.getElementById('custom-css-not-eligible').hidden = false;
474 }
475 } else {
476 document.getElementById('custom-css-eligible').hidden = true;
477 document.getElementById('custom-css-not-eligible').hidden = true;
478 }
479 });
480 });
481
482 if(pageUser.id_str !== user.id_str) {
483 followersYouFollow = followersYouFollowData;
484 document.getElementById('profile-friends-text').style.display = 'unset';
485 } else {
486 followersYouFollow = undefined;
487 document.getElementById('profile-friends-text').style.display = 'none';
488 }
489 renderProfile();
490 resolve(u);
491 }).catch(e => {
492 if (e === "Not logged in") {
493 window.location.href = "https://twitter.com/i/flow/login?newtwitter=true";
494 }
495 console.error(e);
496 reject(e);
497 });
498}
499
500async function updateTimeline() {
501 seenThreads = [];
502 if (timeline.data.length === 0) document.getElementById('timeline').innerHTML = `
503 <div class="loading-data" id="tweets-loader">
504 <img src="${chrome.runtime.getURL(`images/loading.svg`)}" width="64" height="64">
505 </div>`;
506 let tl;
507 if(subpage === "likes") {
508 let data = await API.user.getFavorites(pageUser.id_str);
509 tl = data.tl;
510 favoritesCursor = data.cursor;
511 } else {
512 try {
513 if (!user_protected && !user_blocked_by) {
514 if (subpage === "media") {
515 tl = await API.user.getMediaTweets(pageUser.id_str);
516 mediaCursor = tl.cursor;
517 tl = tl.tweets;
518 } else {
519 tl = await API.user.getTweetsV2(
520 pageUser.id_str,
521 undefined,
522 subpage !== "profile"
523 );
524 pinnedTweet = tl.pinnedTweet;
525 tweetsCursor = tl.cursor;
526 tl = tl.tweets;
527 }
528 } else if(user_blocked_by) {
529 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.blocked_by_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.why_you_cant_see_block_user.message.replaceAll("$SCREEN_NAME$",pageUser.screen_name)}</p></div>`;
530 return;
531 }else if(user_protected) {
532 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.user_protected.message}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.follow_to_see.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p></div>`;
533 return;
534 }/*else if(user_blocking) {
535 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.you_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.do_you_want_see_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p><button class="nice-button" id="see-tweet-btn">${LOC.I_want_see_blocked_user.message}</button> </div>`;
536 return;
537 }*/
538 } catch(e) {
539 console.error(e);
540 document.getElementById('timeline').innerHTML = `<div style="padding: 100px;color: var(--darker-gray);">${escapeHTML(String(e))}</div>`;
541 return;
542 }
543 // if(subpage === 'media') {
544 // tl = tl.filter(t => t.extended_entities && t.extended_entities.media && t.extended_entities.media.length > 0 && !t.retweeted_status);
545 // }
546 }
547 if(tl.error === "Not authorized.") {
548 document.getElementById('tweet-nav').hidden = true;
549 document.getElementById('loading-box').hidden = true;
550 document.getElementById('timeline').innerHTML = pageUser.statuses_count === 0 ? '' : `<div style="padding: 100px;color: var(--darker-gray);">${LOC.timeline_not_authorized.message}</div>`;
551 return;
552 }
553 tl.forEach(t => {
554 let oldTweet = timeline.data.find(tweet => tweet.id_str === t.id_str);
555 let tweetElement = document.getElementById(`tweet-${t.id_str}`);
556 if (oldTweet) {
557 oldTweet.favorite_count = t.favorite_count;
558 oldTweet.retweet_count = t.retweet_count;
559 oldTweet.reply_count = t.reply_count;
560 oldTweet.favorited = t.favorited;
561 oldTweet.retweeted = t.retweeted;
562 }
563 if (tweetElement) {
564 tweetElement.querySelector('.tweet-interact-favorite ').innerText = t.favorite_count;
565 tweetElement.querySelector('.tweet-interact-retweet').innerText = t.retweet_count;
566 tweetElement.querySelector('.tweet-interact-reply').innerText = t.reply_count;
567 tweetElement.querySelector('.tweet-interact-favorite').classList.toggle('tweet-interact-favorited', t.favorited);
568 tweetElement.querySelector('.tweet-interact-retweet').classList.toggle('tweet-interact-retweeted', t.retweeted);
569 }
570 });
571 // first update
572 timeline.data = tl;
573 averageLikeCount = timeline.data.filter(t => !t.retweeted_status).map(t => t.favorite_count).sort((a, b) => a - b)[Math.floor(timeline.data.length/2)];
574 renderTimeline();
575 previousLastTweet = timeline.data[timeline.data.length - 1];
576}
577
578async function renderFollowing(clear = true, cursor) {
579 loadingFollowing = true;
580 let userList = document.getElementById('following-list');
581 if(clear) {
582 if(pageUser.id_str === user.id_str) {
583 userList.innerHTML = `
584 <h1 class="nice-header">${LOC.following.message}</h1>
585 <a href="/old/unfollows/following" style="float: right;font-size: 14px;">${LOC.unfollowings.message}</a>
586 `;
587 } else {
588 userList.innerHTML = `<h1 class="nice-header">${LOC.following.message}</h1>`;
589 }
590 }
591 let following;
592 try {
593 following = await API.user.getFollowing(pageUser.id_str, cursor);
594 } catch(e) {
595 loadingFollowing = false;
596 followingMoreBtn.innerText = LOC.load_more.message;
597 console.error(e);
598 return;
599 }
600 followingCursor = following.cursor;
601 following = following.list;
602 if(following.length === 0) {
603 followingMoreBtn.hidden = true;
604 } else {
605 followingMoreBtn.hidden = false;
606 }
607 following.forEach(u => {
608 appendUser(u, userList);
609 });
610 document.getElementById('loading-box').hidden = true;
611 loadingFollowing = false;
612 followingMoreBtn.innerText = LOC.load_more.message;
613}
614async function renderFollowers(clear = true, cursor) {
615 loadingFollowers = true;
616 let userList = document.getElementById('followers-list');
617 if(clear) {
618 if(pageUser.id_str === user.id_str) {
619 userList.innerHTML = `
620 <h1 class="nice-header">${LOC.followers.message}</h1>
621 <a href="/old/unfollows/followers" style="float: right;font-size: 14px;">${LOC.unfollowers.message}</a>
622 `;
623 } else {
624 userList.innerHTML = `<h1 class="nice-header">${LOC.followers.message}</h1>`;
625 }
626 }
627 let following;
628 try {
629 following = await API.user.getFollowers(pageUser.id_str, cursor)
630 } catch(e) {
631 loadingFollowers = false;
632 followersMoreBtn.innerText = LOC.load_more.message;
633 console.error(e);
634 return;
635 }
636 followersCursor = following.cursor;
637 following = following.list;
638 if(following.length === 0) {
639 followersMoreBtn.hidden = true;
640 } else {
641 followersMoreBtn.hidden = false;
642 }
643 following.forEach(u => {
644 appendUser(u, userList);
645 });
646 document.getElementById('loading-box').hidden = true;
647 loadingFollowers = false;
648 followersMoreBtn.innerText = LOC.load_more.message;
649}
650async function renderFollowersYouFollow(clear = true, cursor) {
651 loadingFollowersYouKnow = true;
652 let userList = document.getElementById('followers_you_follow-list');
653 if(clear) {
654 if(LOC.followers_you_know.message.includes("$NUMBER$")) {
655 userList.innerHTML = `<h1 class="nice-header">${LOC.followers_you_know.message.replace("$NUMBER$", followersYouFollow.total_count)}</h1>`;
656 } else {
657 userList.innerHTML = `<h1 class="nice-header">${followersYouFollow.total_count} ${LOC.followers_you_know.message}</h1>`;
658 }
659 }
660 let following;
661 try {
662 following = await API.user.getFollowersYouFollow(pageUser.id_str, cursor);
663 } catch(e) {
664 console.error(e);
665 loadingFollowersYouKnow = false;
666 followersYouFollowMoreBtn.innerText = LOC.load_more.message;
667 return;
668 }
669 followersYouKnowCursor = following.cursor;
670 following = following.list;
671 if(following.length === 0) {
672 followersYouFollowMoreBtn.hidden = true;
673 } else {
674 followersYouFollowMoreBtn.hidden = false;
675 }
676 following.forEach(u => {
677 appendUser(u, userList);
678 });
679 document.getElementById('loading-box').hidden = true;
680 loadingFollowersYouKnow = false;
681 followersYouFollowMoreBtn.innerText = LOC.load_more.message;
682}
683async function renderLists() {
684 let lists = pageUser.id_str === user.id_str ? await API.list.getMyLists() : await API.user.getLists(pageUser.id_str);
685 let listsList = document.getElementById('lists-list');
686 listsList.innerHTML = `<h1 class="nice-header">${LOC.lists.message}</h1>`;
687 if(pageUser.id_str === user.id_str) {
688 listsList.innerHTML += `<h1 class="nice-header" style="float:right;cursor:pointer" id="create-list">${LOC.create_btn.message}</h1>`;
689 document.getElementById('create-list').addEventListener('click', () => {
690 let modal = createModal(`
691 <div id="list-creator">
692 <h1 class="cool-header">${LOC.create_list.message}</h1><br>
693 <span id="list-editor-error" style="color:red"></span><br>
694 ${LOC.name.message}:<br><input maxlength="25" type="text" id="list-name-input"><br><br>
695 ${LOC.description.message}:<br><textarea maxlength="100" type="text" id="list-description-input"></textarea><br>
696 <br>
697 ${LOC.is_private.message}: <input type="checkbox" style="width: 15px;" id="list-private-input"><br>
698 <br>
699 <button class="nice-button" id="list-btn-create">${LOC.create.message}</button>
700 </div>
701 `, 'list-creator-modal');
702 document.getElementById('list-btn-create').addEventListener('click', async () => {
703 let list;
704 try {
705 list = await API.list.create(document.getElementById('list-name-input').value, document.getElementById('list-description-input').value, document.getElementById('list-private-input').checked);
706 } catch(e) {
707 return document.getElementById('list-editor-error').innerText = e && e.message ? e.message : e;
708 }
709 location.href = `https://twitter.com/i/lists/${list.id_str}`;
710 });
711 });
712 }
713 for(let i in lists) {
714 let l = lists[i];
715 if(!l) continue;
716 let listElement = document.createElement('div');
717 listElement.classList.add('list-item');
718 listElement.innerHTML = `
719 <div>
720 <a href="https://twitter.com/i/lists/${l.id_str}" class="following-item-link">
721 <img style="object-fit: cover;" src="${l.custom_banner_media ? l.custom_banner_media.media_info.original_img_url : l.default_banner_media.media_info.original_img_url}" alt="${l.name}" class="following-item-avatar tweet-avatar" width="48" height="48">
722 <div class="following-item-text" style="position: relative;bottom: 12px;">
723 <span class="tweet-header-name following-item-name${l.mode === 'Private' ? ' user-protected' : ''}" style="font-size: 18px;">${escapeHTML(l.name)}</span><br>
724 <span style="color:var(--darker-gray);font-size:14px;margin-top:2px">${l.description ? escapeHTML(l.description).slice(0, 52) : LOC.no_description.message}</span>
725 </div>
726 </a>
727 </div>
728 `;
729 listsList.appendChild(listElement);
730 }
731 document.getElementById('loading-box').hidden = true;
732}
733
734let months = [];
735let everAddedAdditional = false;
736let toAutotranslate = false;
737async function renderProfile() {
738 document.getElementById('profile-banner').src = pageUser.profile_banner_url ? pageUser.profile_banner_url : 'https://abs.twimg.com/images/themes/theme1/bg.png';
739 let attempts = 0;
740 document.getElementById('profile-avatar').addEventListener('error', () => {
741 if(attempts > 3) return document.getElementById('profile-avatar').src = `${vars.useOldDefaultProfileImage ? chrome.runtime.getURL(`images/default_profile_images/default_profile_400x400.png`) : 'https://abs.twimg.com/sticky/default_profile_images/default_profile_400x400.png'}`;
742 attempts++;
743 setTimeout(() => {
744 document.getElementById('profile-avatar').src = `${(pageUser.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(pageUser.id_str) % 7}_normal.png`): pageUser.profile_image_url_https}`.replace('_normal.', '_400x400.');
745 }, 500);
746 });
747 let autotranslateProfiles = await new Promise(resolve => {
748 chrome.storage.sync.get(['autotranslateProfiles'], data => {
749 resolve(data.autotranslateProfiles);
750 });
751 });
752 if(!autotranslateProfiles) {
753 autotranslateProfiles = [];
754 }
755 toAutotranslate = autotranslateProfiles.includes(pageUser.id_str);
756 document.getElementById('profile-avatar').src = `${(pageUser.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(pageUser.id_str) % 7}_normal.png`): pageUser.profile_image_url_https}`.replace('_normal.', '_400x400.');
757 document.getElementById('nav-profile-avatar').src = `${(pageUser.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(pageUser.id_str) % 7}_normal.png`): pageUser.profile_image_url_https}`.replace('_normal.', '_bigger.');
758 document.getElementById('profile-name').innerText = pageUser.name.replace(/\n/g, ' ');
759 document.getElementById('nav-profile-name').innerText = pageUser.name.replace(/\n/g, ' ');
760 document.getElementById('profile-avatar-link').href = `${(pageUser.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(pageUser.id_str) % 7}_normal.png`): pageUser.profile_image_url_https}`.replace('_normal.', '_400x400.');
761 if(LOC.tweet_to.message.includes("$SCREEN_NAME$")) {
762 document.getElementById('tweet-to').innerText = LOC.tweet_to.message.replace("$SCREEN_NAME$", pageUser.screen_name.replace(/\n/g, ' '));
763 } else {
764 document.getElementById('tweet-to').innerText = `${LOC.tweet_to.message} ${pageUser.name.replace(/\n/g, ' ')}`;
765 }
766 if(vars.heartsNotStars) {
767 document.getElementById('profile-stat-text-favorites').innerText = LOC.likes.message;
768 }
769 let stats = Array.from(document.getElementsByClassName('profile-stat'));
770 stats.forEach(s => {
771 s.classList.toggle('profile-stat-disabled', (pageUser.protected && !pageUser.following) && pageUser.id_str !== user.id_str); //BUG:pageUser.blocked_by works strangly only here...
772 });
773
774 document.getElementById('profile-name').className = "";
775 if(pageUser.verified || pageUser.verified_type || pageUser.id_str === '1123203847776763904') {
776 if(!(!vars.twitterBlueCheckmarks && pageUser.verified_type === "Blue")) document.getElementById('profile-name').classList.add('user-verified');
777 if(pageUser.id_str === '1123203847776763904') document.getElementById('profile-name').classList.add('user-verified-green');
778 if(vars.twitterBlueCheckmarks && pageUser.verified_type === "Blue") document.getElementById('profile-name').classList.add('user-verified-blue');
779 if(pageUser.verified_type === "Government") document.getElementById('profile-name').classList.add('user-verified-gray');
780 if(pageUser.verified_type === "Business") document.getElementById('profile-name').classList.add('user-verified-yellow');
781 }
782 if(pageUser.protected) {
783 document.getElementById('profile-name').classList.add('user-protected');
784 }
785 if(pageUser.muting) {
786 document.getElementById('profile-name').classList.add('user-muted');
787 }
788 document.getElementById('profile-username').innerText = `@${pageUser.screen_name}`;
789 document.getElementById('nav-profile-username').innerText = `@${pageUser.screen_name}`;
790 document.getElementById('profile-media-text').href = `https://twitter.com/${pageUser.screen_name}/media`;
791
792 updateSelection();
793
794 document.getElementById('profile-bio').innerHTML = escapeHTML(pageUser.description).replace(/\n\n\n\n/g, "\n").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>`).replace(hashtagRegex, `<a href="https://twitter.com/hashtag/$2">#$2</a>`).replace(/\n/g, '<br>');
795 let strippedDownText = pageUser.description
796 .replace(/(?:https?|ftp):\/\/[\n\S]+/g, '') //links
797 .replace(/(?<!\w)@([\w+]{1,15}\b)/g, '') //mentions
798 .replace(/[\p{Extended_Pictographic}]/gu, '') //emojis (including ones that arent colored)
799 .replace(/[\u200B-\u200D\uFE0E\uFE0F]/g, '') //sometimes emojis leave these behind
800 .replace(/\d+/g, '') //numbers
801 .trim();
802 let detectedLanguage = strippedDownText.length < 1 ? {languages:[{language:LANGUAGE, percentage:100}]} : await chrome.i18n.detectLanguage(strippedDownText);
803 if(!detectedLanguage.languages[0]) detectedLanguage = {languages:[{language:LANGUAGE, percentage:100}]};
804 let isEnglish = detectedLanguage.languages[0] && detectedLanguage.languages[0].percentage > 60 && detectedLanguage.languages[0].language.startsWith(LANGUAGE);
805 let at = false;
806 if(!isEnglish) {
807 let translateBtn = document.createElement('span');
808 translateBtn.className = "translate-bio";
809 translateBtn.addEventListener('click', async () => {
810 if(at) return;
811 at = true;
812 let translated = await API.user.translateBio(pageUser.id_str);
813 let span = document.createElement('span');
814 let translatedMessage;
815 if(LOC.translated_from.message.includes("$LANGUAGE$")) {
816 translatedMessage = LOC.translated_from.message.replace("$LANGUAGE$", `[${translated.localizedSourceLanguage}]`);
817 } else {
818 translatedMessage = `${LOC.translated_from.message} [${translated.localizedSourceLanguage}]`;
819 }
820 span.innerHTML = `
821 <br>
822 <span class='piu-a'>${translatedMessage}:</span>
823 <span>${escapeHTML(translated.translation).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>`).replace(hashtagRegex, `<a href="https://twitter.com/hashtag/$2">#$2</a>`).replace(/\n/g, '<br>')}</span>
824 `;
825 translateBtn.hidden = true;
826 document.getElementById('profile-bio').append(span);
827 let links = Array.from(span.getElementsByTagName('a'));
828 links.forEach(link => {
829 let realLink = pageUser.entities.description.urls.find(u => u.url === link.href);
830 if (realLink) {
831 link.href = realLink.expanded_url;
832 if(!link.href.startsWith('https://twitter.com/')) link.target = '_blank';
833 link.innerText = realLink.display_url;
834 }
835 });
836 if(vars.enableTwemoji) twemoji.parse(span);
837 });
838 translateBtn.innerText = LOC.translate_bio.message;
839 document.getElementById('profile-bio').append(document.createElement('br'), translateBtn);
840 }
841
842 if(vars.enableTwemoji) twemoji.parse(document.getElementById('profile-name'));
843
844 document.getElementById('profile-stat-tweets-value').innerText = Number(pageUser.statuses_count).toLocaleString().replace(/\s/g, ',');
845 document.getElementById('profile-stat-following-value').innerText = Number(pageUser.friends_count).toLocaleString().replace(/\s/g, ',');
846 document.getElementById('profile-stat-followers-value').innerText = Number(pageUser.followers_count).toLocaleString().replace(/\s/g, ',');
847 document.getElementById('profile-stat-favorites-value').innerText = Number(pageUser.favourites_count).toLocaleString().replace(/\s/g, ',');
848 document.getElementById('profile-stat-media-value').innerText = Number(pageUser.media_count).toLocaleString().replace(/\s/g, ',');
849
850 document.getElementById('tweet-nav').hidden = pageUser.statuses_count === 0 || user_blocked_by || user_protected || !(subpage === 'profile' || subpage === 'replies' || subpage === 'media');
851 document.getElementById('profile-stat-tweets-link').hidden = pageUser.statuses_count === 0;
852 document.getElementById('profile-stat-following-link').hidden = pageUser.friends_count === 0;
853 document.getElementById('profile-stat-followers-link').hidden = pageUser.followers_count === 0;
854 document.getElementById('profile-stat-favorites-link').hidden = pageUser.favourites_count === 0;
855 document.getElementById('profile-stat-media-link').hidden = pageUser.media_count === 0 || !vars.showMediaCount;
856
857 if((pageUser.statuses_count === 0 && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) || ((pageUser.protected || pageUser.blocked_by) && !pageUser.following && pageUser.id_str !== user.id_str)) {
858 document.getElementById('trends').hidden = true;
859 setTimeout(() => {
860 let list = document.getElementById('wtf-list');
861 while(list.childElementCount > 3) list.removeChild(list.lastChild);
862 }, 500);
863 } else {
864 document.getElementById('trends').hidden = false;
865 }
866 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) {
867 document.getElementById('no-tweets').hidden = false;
868 document.getElementById('no-tweets').innerHTML = `
869 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
870 <p>${LOC.when_theyll_tweet.message}</p>
871 `;
872 } else {
873 document.getElementById('no-tweets').hidden = true;
874 document.getElementById('no-tweets').innerHTML = ``;
875 }
876 if(pageUser.blocking && !pageUser.blocked_by) {
877 document.getElementById('no-tweets').hidden = false;
878 document.getElementById('no-tweets').innerHTML = `<div dir="auto" style="color: var(--darker-gray); font-size: 20px;"><h2>${LOC.you_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.do_you_want_see_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p><button class="nice-button" id="see-tweet-btn">${LOC.I_want_see_blocked_user.message}</button> </div>`;
879 document.getElementById('timeline').hidden = true;
880 document.getElementById('tweet-nav').hidden = true;
881 document.getElementById('see-tweet-btn').addEventListener('click', async () => {
882 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) {
883 document.getElementById('trends').hidden = true;
884 document.getElementById('no-tweets').hidden = false;
885 document.getElementById('no-tweets').innerHTML = `
886 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
887 <p>${LOC.when_theyll_tweet.message}</p>
888 `;
889 }
890 else {
891 document.getElementById('no-tweets').hidden = true;
892 document.getElementById('no-tweets').innerHTML = ``;
893 document.getElementById('timeline').hidden = false;
894 if(!pageUser.protected){
895 document.getElementById('trends').hidden = false;
896 document.getElementById('tweet-nav').hidden = false;
897 }
898 }
899
900 });
901 }
902
903 if(pageUser.followed_by) {
904 document.getElementById('follows-you').hidden = false;
905 } else {
906 document.getElementById('follows-you').hidden = true;
907 }
908
909 if(followersYouFollow && followersYouFollow.total_count > 0) {
910 let friendsFollowing = document.getElementById('profile-friends-following');
911 let friendsFollowingList = document.getElementById('profile-friends-div');
912 let friendsFollowingText = document.getElementById('profile-friends-text');
913 if(LOC.followers_you_know.message.includes("$NUMBER$")) {
914 friendsFollowingText.innerText = LOC.followers_you_know.message.replace("$NUMBER$", followersYouFollow.total_count);
915 } else {
916 friendsFollowingText.innerText = `${followersYouFollow.total_count} ${LOC.followers_you_know.message}`;
917 }
918 friendsFollowingText.href = `https://twitter.com/${pageUser.screen_name}/followers_you_follow`;
919 friendsFollowingText
920 followersYouFollow.users.forEach(u => {
921 let a = document.createElement('a');
922 a.href = `/${u.screen_name}`;
923 let avatar = document.createElement('img');
924 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');
925 avatar.width = 45;
926 avatar.height = 45;
927 avatar.title = u.name + ' (@' + u.screen_name + ')';
928 avatar.classList.add('profile-friends-avatar');
929 a.append(avatar);
930 friendsFollowingList.append(a);
931 });
932 friendsFollowing.hidden = false;
933 } else {
934 let friendsFollowing = document.getElementById('profile-friends-following');
935 friendsFollowing.hidden = true;
936 }
937
938 let buttonsElement = document.getElementById('profile-nav-buttons');
939 document.getElementById('pin-profile').classList.toggle('menu-active', pageUser.id_str === user.id_str && !location.pathname.includes('/lists'));
940 document.getElementById('pin-lists').classList.toggle('menu-active', location.pathname.startsWith(`/${pageUser.screen_name}/lists`));
941 let styling = document.getElementById('styling');
942 if(pageUser.id_str === user.id_str) {
943 buttonsElement.innerHTML = /*html*/`
944 <a class="nice-button" id="edit-profile" target="_blank" href="https://twitter.com/settings/profile?newtwitter=true">${LOC.edit_profile.message}</a>
945 <button class="profile-additional-thing nice-button" id="profile-style"></button>
946 `;
947 let profileStyleActive = false;
948 let profileStyle = document.getElementById('profile-style');
949 profileStyle.addEventListener('click', () => {
950 profileStyle.classList.toggle('profile-style-active');
951 profileStyleActive = !profileStyleActive;
952 styling.hidden = !profileStyleActive;
953 });
954 chrome.storage.local.get(['otPrivateTokens'], data => {
955 document.getElementById('private-profile-warn').hidden = !user.protected || (data.otPrivateTokens && data.otPrivateTokens[user.id_str]);
956 });
957 } else {
958 document.getElementById('private-profile-warn').hidden = true;
959 styling.hidden = true;
960 document.getElementById('tweet-to-bg').hidden = false;
961 buttonsElement.innerHTML = /*html*/`
962 <button ${(pageUser.blocking || pageUser.blocked_by) ? 'hidden' : ''} class="nice-button ${pageUser.following || pageUser.follow_request_sent ? 'following' : 'follow'} control-btn" id="control-follow">${pageUser.following || (pageUser.protected && pageUser.follow_request_sent) ? ((pageUser.protected && pageUser.follow_request_sent) ? LOC.follow_request_sent.message : LOC.following_btn.message) : LOC.follow.message}</button>
963 <button class="nice-button control-btn" id="control-unblock" ${pageUser.blocking ? '' : 'hidden'}>${LOC.unblock.message}</button>
964 <a ${pageUser.can_dm && !pageUser.blocking && !pageUser.blocked_by ? '' : 'hidden'} class="nice-button" id="message-user"></a>
965 `;
966 if(!pageUser.following) {
967 pageUser.want_retweets = true;
968 }
969 let blockUserText, unblockUserText;
970 if(LOC.block_user.message.includes('$SCREEN_NAME$') && LOC.unblock_user.message.includes('$SCREEN_NAME$')) {
971 blockUserText = `${LOC.block_user.message.replace('$SCREEN_NAME$', pageUser.screen_name)}`;
972 unblockUserText = `${LOC.unblock_user.message.replace('$SCREEN_NAME$', pageUser.screen_name)}`;
973 } else {
974 blockUserText = `${LOC.block_user.message} @${pageUser.screen_name}`;
975 unblockUserText = `${LOC.unblock_user.message} @${pageUser.screen_name}`;
976 }
977 buttonsElement.innerHTML += /*html*/`
978 <span class="profile-additional-thing" id="profile-settings"></span>
979 <div id="profile-settings-div" class="dropdown-menu" hidden>
980 <span ${!pageUser.following || pageUser.blocking ? 'hidden' : ''} id="profile-settings-notifications" class="${pageUser.notifications ? 'profile-settings-offnotifications' : 'profile-settings-notifications'}">${pageUser.notifications ? LOC.stop_notifications.message : LOC.receive_notifications.message}</span>
981 <span id="profile-settings-block" class="${pageUser.blocking ? 'profile-settings-unblock' : 'profile-settings-block'}">${pageUser.blocking ? unblockUserText : blockUserText}</span>
982 <span ${pageUser.blocking || ((pageUser.protected || pageUser.blocked_by) && !pageUser.following) ? 'hidden' : ''} id="profile-settings-mute" class="${pageUser.muting ? 'profile-settings-unmute' : 'profile-settings-mute'}">${pageUser.muting ? LOC.unmute.message : LOC.mute.message}</span>
983 ${pageUser.followed_by ? /*html*/`<span id="profile-settings-removefollowing">${LOC.remove_from_followers.message}</span>` : ''}
984 <span id="profile-settings-lists-action" ${pageUser.blocking || ((pageUser.protected || pageUser.blocked_by) && !pageUser.following) ? 'hidden' : ''}>${LOC.from_list.message}</span>
985 <span id="profile-settings-autotranslate">${toAutotranslate ? LOC.dont_autotranslate.message : LOC.autotranslate_tweets.message}</span>
986 <span id="profile-settings-retweets" ${pageUser.following ? '' : 'hidden'}>${pageUser.want_retweets ? LOC.turn_off_retweets.message : LOC.turn_on_retweets.message}</span>
987 <hr>
988 <span id="profile-settings-lists" ${(pageUser.protected || pageUser.blocked_by) && !pageUser.following ? 'hidden' : ''}>${LOC.see_lists.message}</span>
989 <span id="profile-settings-share">${LOC.share_user.message}</span>
990 <span id="profile-settings-copy">${LOC.copy_profile_link.message}</span>
991 ${vars.developerMode ? /*html*/`<span id="profile-settings-copy-id">${LOC.copy_user_id.message}</span>` : ''}
992 </div>
993 `;
994 let messageUser = document.getElementById('message-user');
995 messageUser.addEventListener('click', () => {
996 let event = new CustomEvent('messageUser', { detail: { id: `${user.id_str}-${pageUser.id_str}`, user: pageUser } });
997 document.dispatchEvent(event);
998 });
999 let clicked = false;
1000 let controlFollow = document.getElementById('control-follow');
1001 controlFollow.addEventListener('click', async () => {
1002 if (controlFollow.className.includes('following')) {
1003 try {
1004 pageUser.protected && pageUser.follow_request_sent ? await API.user.cancelFollowRequest(pageUser.screen_name) : await API.user.unfollow(pageUser.screen_name);
1005 } catch(e) {
1006 console.error(e);
1007 alert(e);
1008 return;
1009 }
1010 controlFollow.classList.remove('following');
1011 controlFollow.classList.add('follow');
1012 controlFollow.innerText = LOC.follow.message;
1013 pageUser.following = false;
1014 document.getElementById("profile-settings-retweets").hidden = true;
1015 document.getElementById('profile-stat-followers-value').innerText = Number(parseInt(document.getElementById('profile-stat-followers-value').innerText.replace(/\s/g, '').replace(/,/g, '')) - 1).toLocaleString().replace(/\s/g, ',');
1016 document.getElementById('profile-settings-notifications').hidden = true;
1017 } else {
1018 try {
1019 await API.user.follow(pageUser.screen_name);
1020 } catch(e) {
1021 console.error(e);
1022 alert(e);
1023 return;
1024 }
1025 controlFollow.classList.add('following');
1026 controlFollow.classList.remove('follow');
1027 controlFollow.innerText = pageUser.protected ? LOC.follow_request_sent.message : LOC.following_btn.message;
1028 pageUser.following = true;
1029 if(!pageUser.protected) {
1030 document.getElementById('profile-settings-notifications').hidden = false;
1031 document.getElementById("profile-settings-retweets").hidden = false;
1032 document.getElementById('profile-stat-followers-value').innerText = Number(parseInt(document.getElementById('profile-stat-followers-value').innerText.replace(/\s/g, '').replace(/,/g, '')) + 1).toLocaleString().replace(/\s/g, ',');
1033 }
1034 }
1035 });
1036 document.getElementById('profile-settings-retweets').addEventListener('click', async e => {
1037 if(pageUser.want_retweets) {
1038 await API.user.switchRetweetsVisibility(pageUser.id_str, false);
1039 pageUser.want_retweets = false;
1040 e.target.innerText = LOC.turn_on_retweets.message;
1041 } else {
1042 await API.user.switchRetweetsVisibility(pageUser.id_str, true);
1043 pageUser.want_retweets = true;
1044 e.target.innerText = LOC.turn_off_retweets.message;
1045 }
1046 });
1047 document.getElementById('profile-settings').addEventListener('click', () => {
1048 document.getElementById('profile-settings-div').hidden = false;
1049 setTimeout(() => {
1050 if(clicked) return;
1051 clicked = true;
1052 document.addEventListener('click', () => {
1053 setTimeout(() => {
1054 clicked = false;
1055 document.getElementById('profile-settings-div').hidden = true;
1056 }, 100);
1057 }, { once: true });
1058 }, 100);
1059 });
1060 document.getElementById('profile-settings-notifications').addEventListener('click', async () => {
1061 if(!pageUser.notifications) {
1062 await API.user.receiveNotifications(pageUser.id_str, true);
1063 pageUser.notifications = true;
1064 document.getElementById('profile-settings-notifications').classList.remove('profile-settings-notifications');
1065 document.getElementById('profile-settings-notifications').classList.add('profile-settings-offnotifications');
1066 document.getElementById('profile-settings-notifications').innerText = LOC.stop_notifications.message;
1067 } else {
1068 await API.user.receiveNotifications(pageUser.id_str, false);
1069 pageUser.notifications = false;
1070 document.getElementById('profile-settings-notifications').classList.remove('profile-settings-offnotifications');
1071 document.getElementById('profile-settings-notifications').classList.add('profile-settings-notifications');
1072 document.getElementById('profile-settings-notifications').innerText = LOC.stop_notifications.message;
1073 }
1074 });
1075
1076
1077 document.getElementById('profile-settings-block').addEventListener('click', async () => {
1078 if(pageUser.blocking) {
1079 await API.user.unblock(pageUser.id_str);
1080 pageUser.blocking = false;
1081 document.getElementById('profile-settings-block').classList.remove('profile-settings-unblock');
1082 document.getElementById('profile-settings-block').classList.add('profile-settings-block');
1083 if(LOC.block_user.message.includes("$SCREEN_NAME$")) {
1084 document.getElementById('profile-settings-block').innerText = LOC.block_user.message.replace("$SCREEN_NAME$", pageUser.screen_name);
1085 } else {
1086 document.getElementById('profile-settings-block').innerText = `${LOC.block_user.message} @${pageUser.screen_name}`;
1087 }
1088 document.getElementById('control-unblock').hidden = true;
1089 if(!pageUser.blocked_by) {
1090 document.getElementById('control-follow').hidden = false;
1091 document.getElementById("profile-settings-notifications").hidden = false;
1092 document.getElementById("profile-settings-mute").hidden = false;
1093 document.getElementById('message-user').hidden = !pageUser.can_dm;
1094 //enable timeline
1095 //recycle no-tweets
1096 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) {
1097 document.getElementById('trends').hidden = true;
1098 document.getElementById('no-tweets').hidden = false;
1099 document.getElementById('no-tweets').innerHTML = `
1100 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
1101 <p>${LOC.when_theyll_tweet.message}</p>
1102 `;
1103 }
1104 else {
1105 document.getElementById('trends').hidden = false;
1106 document.getElementById('no-tweets').hidden = true;
1107 document.getElementById('no-tweets').innerHTML = ``;
1108 document.getElementById('timeline').hidden = false;
1109 if(!pageUser.protected)
1110 document.getElementById('tweet-nav').hidden = false;
1111 }
1112 }
1113 } else {
1114 let blockMessage;
1115 if(LOC.block_sure.message.includes("$SCREEN_NAME$")) {
1116 blockMessage = LOC.block_sure.message.replace("$SCREEN_NAME$", pageUser.screen_name);
1117 } else {
1118 blockMessage = `${LOC.block_sure.message} @${pageUser.screen_name}?`;
1119 }
1120 let blockMessageDesc;
1121 if(LOC.block_sure_desc.message.includes("$SCREEN_NAME$")) {
1122 blockMessageDesc = LOC.block_sure_desc.message.replace("$SCREEN_NAME$", pageUser.screen_name);
1123 } else {
1124 blockMessageDesc = `${LOC.block_sure_desc.message} @${pageUser.screen_name}?`;
1125 }
1126 let modal = createModal(`
1127 <h1 class="cool-header">${blockMessage}</span>
1128 <br><br>
1129 <span style='font-size:14px;color:var(--almost-black)'>${blockMessageDesc}</h1>
1130 <br>
1131 <div style="display:inline-block;float: right;margin-top: 5px;">
1132 <button class="nice-button nice-red-button">${LOC.block.message}</button>
1133 </div>
1134 `)
1135 modal.getElementsByClassName('nice-button')[0].addEventListener('click', async () => {
1136 await API.user.block(pageUser.id_str);
1137 pageUser.blocking = true;
1138 document.getElementById('profile-settings-block').classList.add('profile-settings-unblock');
1139 document.getElementById('profile-settings-block').classList.remove('profile-settings-block');
1140 if(LOC.unblock_user.message.includes("$SCREEN_NAME$")) {
1141 document.getElementById('profile-settings-block').innerText = LOC.unblock_user.message.replace("$SCREEN_NAME$", pageUser.screen_name);
1142 } else {
1143 document.getElementById('profile-settings-block').innerText = `${LOC.unblock_user.message} @${pageUser.screen_name}`;
1144 }
1145 document.getElementById('control-unblock').hidden = false;
1146 document.getElementById('control-follow').hidden = true;
1147 document.getElementById('message-user').hidden = true;
1148 document.getElementById("profile-settings-notifications").hidden = true;
1149 document.getElementById("profile-settings-mute").hidden = true;
1150 if(!pageUser.blocked_by) {
1151 //disable timeline
1152 //recycle no-tweets
1153 document.getElementById('no-tweets').hidden = false;
1154 document.getElementById('no-tweets').innerHTML = `<div dir="auto" style="color: var(--darker-gray); font-size: 20px;"><h2>${LOC.you_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.do_you_want_see_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p><button class="nice-button" id="see-tweet-btn">${LOC.I_want_see_blocked_user.message}</button> </div>`;
1155 document.getElementById('timeline').hidden = true;
1156 document.getElementById('tweet-nav').hidden = true;
1157 document.getElementById('see-tweet-btn').addEventListener('click', async () => {
1158 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) {
1159 document.getElementById('trends').hidden = true;
1160 document.getElementById('no-tweets').hidden = false;
1161 document.getElementById('no-tweets').innerHTML = `
1162 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
1163 <p>${LOC.when_theyll_tweet.message}</p>
1164 `;
1165 }
1166 else {
1167 document.getElementById('no-tweets').hidden = true;
1168 document.getElementById('no-tweets').innerHTML = ``;
1169 document.getElementById('timeline').hidden = false;
1170 if(!pageUser.protected){
1171 document.getElementById('trends').hidden = false;
1172 document.getElementById('tweet-nav').hidden = false;
1173 }
1174 }
1175
1176 });
1177 }
1178 modal.removeModal();
1179 });
1180 }
1181 });
1182 document.getElementById('control-unblock').addEventListener('click', async () => {
1183 if(pageUser.blocking) {
1184 await API.user.unblock(pageUser.id_str);
1185 pageUser.blocking = false;
1186 document.getElementById('profile-settings-block').classList.remove('profile-settings-unblock');
1187 document.getElementById('profile-settings-block').classList.add('profile-settings-block');
1188 if(LOC.block_user.message.includes("$SCREEN_NAME$")) {
1189 document.getElementById('profile-settings-block').innerText = LOC.block_user.message.replace("$SCREEN_NAME$", pageUser.screen_name);
1190 } else {
1191 document.getElementById('profile-settings-block').innerText = `${LOC.block_user.message} @${pageUser.screen_name}`;
1192 }
1193 document.getElementById('control-unblock').hidden = true;
1194 if(!pageUser.blocked_by) {
1195 document.getElementById('control-follow').hidden = false;
1196 document.getElementById("profile-settings-notifications").hidden = false;
1197 document.getElementById("profile-settings-mute").hidden = false;
1198 document.getElementById('message-user').hidden = !pageUser.can_dm;
1199 //enable timeline
1200 //recycle no-tweets
1201 if(pageUser.statuses_count === 0 && !( pageUser.blocked_by || pageUser.blocking || pageUser.protected ) && (subpage === 'profile' || subpage === 'replies' || subpage === 'media')) {
1202 document.getElementById('trends').hidden = true;
1203 document.getElementById('no-tweets').hidden = false;
1204 document.getElementById('no-tweets').innerHTML = `
1205 <h3>${LOC.hasnt_tweeted.message.replace('$SCREEN_NAME$', `<span>${pageUser.screen_name}</span>`)}</h3>
1206 <p>${LOC.when_theyll_tweet.message}</p>
1207 `;
1208 }
1209 else {
1210 document.getElementById('trends').hidden = false;
1211 document.getElementById('no-tweets').hidden = true;
1212 document.getElementById('no-tweets').innerHTML = ``;
1213 document.getElementById('timeline').hidden = false;
1214 if(!pageUser.protected)
1215 document.getElementById('tweet-nav').hidden = false;
1216 }
1217 }
1218 } else {
1219 }
1220
1221
1222 });
1223 document.getElementById('profile-settings-autotranslate').addEventListener('click', async () => {
1224 let autotranslateProfiles = await new Promise(resolve => {
1225 chrome.storage.sync.get(['autotranslateProfiles'], data => {
1226 resolve(data.autotranslateProfiles);
1227 });
1228 });
1229 if(!autotranslateProfiles) {
1230 autotranslateProfiles = [];
1231 }
1232 if(autotranslateProfiles.includes(pageUser.id_str)) {
1233 autotranslateProfiles.splice(autotranslateProfiles.indexOf(pageUser.id_str), 1);
1234 document.getElementById('profile-settings-autotranslate').innerText = LOC.dont_autotranslate.message;
1235 toAutotranslate = false;
1236 } else {
1237 autotranslateProfiles.push(pageUser.id_str);
1238 document.getElementById('profile-settings-autotranslate').innerText = LOC.autotranslate_tweets.message;
1239 toAutotranslate = true;
1240 }
1241 chrome.storage.sync.set({ autotranslateProfiles });
1242 setTimeout(() => {
1243 location.reload();
1244 }, 100)
1245 });
1246 document.getElementById('profile-settings-mute').addEventListener('click', async () => {
1247 if(pageUser.muting) {
1248 await API.user.unmute(pageUser.id_str);
1249 pageUser.muting = false;
1250 document.getElementById('profile-settings-mute').classList.remove('profile-settings-unmute');
1251 document.getElementById('profile-settings-mute').classList.add('profile-settings-mute');
1252 document.getElementById('profile-settings-mute').innerText = LOC.mute.message;
1253 document.getElementById('profile-name').classList.remove('user-muted');
1254 } else {
1255 await API.user.mute(pageUser.id_str);
1256 pageUser.muting = true;
1257 document.getElementById('profile-settings-mute').classList.add('profile-settings-unmute');
1258 document.getElementById('profile-settings-mute').classList.remove('profile-settings-mute');
1259 document.getElementById('profile-settings-mute').innerText = LOC.unmute.message;
1260 document.getElementById('profile-name').classList.add('user-muted');
1261 }
1262 });
1263 if(document.getElementById('profile-settings-removefollowing')) document.getElementById('profile-settings-removefollowing').addEventListener('click', async () => {
1264 let modal = createModal(`
1265 <h1 class="cool-header">${LOC.remove_from_followers_sure.message}</h1><br>
1266 <span style='font-size:14px;color:var(--almost-black)'>
1267 ${LOC.able_in_future.message}
1268 <br><br>
1269 ${LOC.remove_from_followers_warn.message}
1270 </span>
1271 <br><br>
1272 <div style="display:inline-block;float: right;margin-top: 5px;">
1273 <button class="nice-button nice-red-button">${LOC.remove_from_followers_button.message}</button>
1274 </div>
1275 `.replace('$SCREEN_NAME$', pageUser.screen_name));
1276 modal.getElementsByClassName('nice-button')[0].addEventListener('click', async () => {
1277 await API.user.removeFollower(pageUser.id_str);
1278 pageUser.followed_by = false;
1279 document.getElementById('profile-settings-removefollowing').hidden = true;
1280 document.getElementById('follows-you').hidden = true;
1281 modal.removeModal();
1282 });
1283 });
1284 document.getElementById('profile-settings-lists-action').addEventListener('click', async () => {
1285 let lists = await API.list.getOwnerships(user.id_str, pageUser.id_str);
1286 let modal = createModal(`
1287 <h1 class="cool-header">${LOC.from_list.message}</h1>
1288 <div id="modal-lists"></div>
1289 `);
1290 let container = document.getElementById('modal-lists');
1291 for(let i in lists) {
1292 let l = lists[i];
1293 let listElement = document.createElement('div');
1294 listElement.classList.add('list-item');
1295 listElement.innerHTML = `
1296 <div style="display:inline-block;">
1297 <a href="https://twitter.com/i/lists/${l.id_str}" class="following-item-link">
1298 <img style="object-fit: cover;" src="${l.custom_banner_media ? l.custom_banner_media.media_info.original_img_url : l.default_banner_media.media_info.original_img_url}" alt="${l.name}" class="following-item-avatar tweet-avatar" width="48" height="48">
1299 <div class="following-item-text" style="position: relative;bottom: 12px;">
1300 <span class="tweet-header-name following-item-name" style="font-size: 18px;">${escapeHTML(l.name)}</span><br>
1301 <span style="color:var(--darker-gray);font-size:14px;margin-top:2px">${l.description ? escapeHTML(l.description).slice(0, 52) : LOC.no_description.message}</span>
1302 </div>
1303 </a>
1304 </div>
1305 <div style="display:inline-block;float: right;margin-top: 5px;">
1306 <button class="nice-button">${l.is_member ? LOC.remove.message : LOC.add.message}</button>
1307 </div>
1308 `;
1309 container.appendChild(listElement);
1310 listElement.getElementsByClassName('nice-button')[0].addEventListener('click', async () => {
1311 if(l.is_member) {
1312 await API.list.removeMember(l.id_str, pageUser.id_str);
1313 l.is_member = false;
1314 listElement.getElementsByClassName('nice-button')[0].innerText = LOC.add.message;
1315 } else {
1316 await API.list.addMember(l.id_str, pageUser.id_str);
1317 l.is_member = true;
1318 listElement.getElementsByClassName('nice-button')[0].innerText = LOC.remove.message;
1319 }
1320 l.is_member = !l.is_member;
1321 });
1322 }
1323 });
1324 document.getElementById('profile-settings-lists').addEventListener('mousedown', e => {
1325 if(e.button === 1) {
1326 openInNewTab(`https://twitter.com/${pageUser.screen_name}/lists`);
1327 }
1328 });
1329 document.getElementById('profile-settings-lists').addEventListener('click', async () => {
1330 // document.getElementById('loading-box').hidden = false;
1331 history.pushState({}, null, `https://twitter.com/${pageUser.screen_name}/lists`);
1332 everAddedAdditional = false;
1333 mediaToUpload = [];
1334 document.getElementById('profile-media-div').innerHTML = '';
1335 document.getElementById('tweet-to-bg').hidden = true;
1336 document.getElementById('profile-additional').innerHTML = '';
1337 document.getElementById('profile-friends-div').innerHTML = '';
1338 updateSubpage();
1339 updateSelection();
1340 renderLists();
1341 });
1342 document.getElementById('profile-settings-share').addEventListener('click', async () => {
1343 navigator.share({ url: `https://twitter.com/${pageUser.screen_name}` });
1344 });
1345 document.getElementById('profile-settings-copy').addEventListener('click', async () => {
1346 navigator.clipboard.writeText(`https://twitter.com/${pageUser.screen_name}`);
1347 });
1348 if(document.getElementById('profile-settings-copy-id')) document.getElementById('profile-settings-copy-id').addEventListener('click', async () => {
1349 navigator.clipboard.writeText(pageUser.id_str);
1350 });
1351 }
1352
1353 let links = Array.from(document.getElementById('profile-bio').getElementsByTagName('a'));
1354 links.forEach(link => {
1355 let realLink = pageUser.entities.description.urls.find(u => u.url === link.href);
1356 if (realLink) {
1357 link.href = realLink.expanded_url;
1358 if(!link.href.startsWith('https://twitter.com/')) link.target = '_blank';
1359 link.innerText = realLink.display_url;
1360 }
1361 });
1362
1363 if(everAddedAdditional) return;
1364 everAddedAdditional = true;
1365 let additionalInfo = document.getElementById('profile-additional');
1366 if(pageUser.location) {
1367 let location = document.createElement('span');
1368 location.classList.add('profile-additional-thing', 'profile-additional-location');
1369 location.innerText = pageUser.location.replace(/\n\n\n\n/g, "\n");
1370 additionalInfo.appendChild(location);
1371 if(vars.enableTwemoji) twemoji.parse(location);
1372 }
1373 if(pageUser.affiliates_highlighted_label) {
1374 let aff = document.createElement('span');
1375 aff.classList.add('profile-additional-thing', 'profile-additional-affiliates');
1376 aff.innerHTML = `
1377 <img style="display: inline-block;vertical-align: top;image-rendering: pixelated;" src="${pageUser.affiliates_highlighted_label.badge.url}" width="20" height="20">
1378 <a style="color:var(--almost-black)!important" href="${pageUser.affiliates_highlighted_label.url ? pageUser.affiliates_highlighted_label.url.url : '#'}">${pageUser.affiliates_highlighted_label.description}</a>
1379 `;
1380 additionalInfo.appendChild(aff);
1381 }
1382 if(pageUser.url) {
1383 let url = document.createElement('a');
1384 url.classList.add('profile-additional-thing', 'profile-additional-url');
1385 let realUrl = pageUser.entities.url.urls[0];
1386 url.innerText = realUrl.display_url;
1387 url.href = realUrl.expanded_url;
1388 if(!url.href.startsWith('https://twitter.com/')) url.target = "_blank";
1389 additionalInfo.appendChild(url);
1390 }
1391 if(pageUser.professional && pageUser.professional.category && pageUser.professional.category[0]) {
1392 let prof = document.createElement('span');
1393 prof.classList.add('profile-additional-thing', 'profile-additional-professional');
1394 prof.innerText = pageUser.professional.category[0].name;
1395 additionalInfo.appendChild(prof);
1396 if(vars.enableTwemoji) twemoji.parse(prof);
1397 }
1398 let joined = document.createElement('span');
1399 joined.classList.add('profile-additional-thing', 'profile-additional-joined');
1400 joined.innerText = `${LOC.joined.message} ${new Date(pageUser.created_at).toLocaleDateString(LANGUAGE.replace("_", "-"), {month: 'long', year: 'numeric', day: 'numeric'})}`;
1401 additionalInfo.appendChild(joined);
1402 if(pageUser.birthdate) {
1403 let birth = document.createElement('span');
1404 birth.classList.add('profile-additional-thing', 'profile-additional-birth');
1405 if(user.id_str === pageUser.id_str) {
1406 birth.classList.add('profile-additional-birth-me');
1407 }
1408 if(pageUser.birthdate.year && typeof pageUser.birthdate.month === 'number') {
1409 birth.innerText = `${LOC.born.message} ${months[pageUser.birthdate.month-1].replace("$NUMBER$", pageUser.birthdate.day)}, ${pageUser.birthdate.year}`;
1410 } else if(typeof pageUser.birthdate.month === 'number') {
1411 birth.innerText = `${LOC.born.message} ${months[pageUser.birthdate.month-1].replace("$NUMBER$", pageUser.birthdate.day)}`;
1412 } else if(pageUser.birthdate.year) {
1413 birth.innerText = `${LOC.born.message} ${pageUser.birthdate.year}`;
1414 }
1415 let date = new Date();
1416 if(pageUser.birthdate.month-1 === date.getMonth() && pageUser.birthdate.day === date.getDate()) {
1417 birth.innerText += ' ' + LOC.birthday_today.message;
1418 birth.classList.add('profile-additional-birth-today');
1419 }
1420 additionalInfo.appendChild(birth);
1421 }
1422
1423 setTimeout(() => {
1424 document.getElementById('loading-box').hidden = true;
1425 }, 10);
1426};
1427
1428async function renderTimeline(append = false, sliceAmount = 0) {
1429 let timelineContainer = document.getElementById('timeline');
1430 if(!append) timelineContainer.innerHTML = '';
1431 let data = timeline.data.slice(sliceAmount, timeline.data.length);;
1432 if(pinnedTweet && subpage === "profile" && !append) await appendTweet(pinnedTweet, timelineContainer, {
1433 top: {
1434 text: LOC.pinned_tweet.message,
1435 icon: "\uf003",
1436 color: "var(--link-color)",
1437 class: 'pinned'
1438 },
1439 bigFont: false
1440 })
1441 for(let i in data) {
1442 let t = data[i];
1443 if(!t) continue;
1444 if(pinnedTweet && t.id_str === pinnedTweet.id_str) continue;
1445 if (t.retweeted_status) {
1446 if(pageUser.id_str === user.id_str) t.retweeted_status.current_user_retweet = t;
1447 await appendTweet(t.retweeted_status, timelineContainer, {
1448 top: {
1449 text: `<a href="https://twitter.com/${t.user.screen_name}">${escapeHTML(t.user.name)}</a> ${LOC.retweeted.message}`,
1450 icon: "\uf006",
1451 color: "#77b255",
1452 class: 'retweet-label'
1453 }
1454 });
1455 } else {
1456 if (t.self_thread) {
1457 let selfThreadTweet = timeline.data.find(tweet => tweet.id_str === t.self_thread.id_str);
1458 if (selfThreadTweet && selfThreadTweet.id_str !== t.id_str && seenThreads.indexOf(selfThreadTweet.id_str) === -1) {
1459 await appendTweet(selfThreadTweet, timelineContainer, {
1460 selfThreadContinuation: true,
1461 bigFont: selfThreadTweet.favorite_count > averageLikeCount*1.2 && selfThreadTweet.favorite_count > 3 && (!selfThreadTweet.full_text || selfThreadTweet.full_text.length < 250)
1462 });
1463 await appendTweet(t, timelineContainer, {
1464 noTop: true,
1465 bigFont: t.favorite_count > averageLikeCount*1.2 && t.favorite_count > 3 && (!t.full_text || t.full_text.length < 250)
1466 });
1467 seenThreads.push(selfThreadTweet.id_str);
1468 } else {
1469 await appendTweet(t, timelineContainer, {
1470 selfThreadButton: true,
1471 bigFont: t.favorite_count > averageLikeCount*1.2 && t.favorite_count > 3 && (!t.full_text || t.full_text.length < 250)
1472 });
1473 }
1474 } else {
1475 await appendTweet(t, timelineContainer, {
1476 bigFont: t.favorite_count > averageLikeCount*1.2 && t.favorite_count > 3 && (!t.full_text || t.full_text.length < 250)
1477 });
1478 }
1479 }
1480 };
1481 document.getElementById('loading-box').hidden = true;
1482 loadingNewTweets = false;
1483 return true;
1484}
1485function renderNewTweetsButton() {
1486 if (timeline.toBeUpdated > 0) {
1487 document.getElementById('new-tweets').hidden = false;
1488 document.getElementById('new-tweets').innerText = LOC.see_new_tweets.message;
1489 } else {
1490 document.getElementById('new-tweets').hidden = true;
1491 }
1492}
1493
1494document.addEventListener('clearActiveTweet', () => {
1495 if(activeTweet) {
1496 activeTweet.classList.remove('tweet-active');
1497 }
1498 activeTweet = undefined;
1499});
1500document.addEventListener('findActiveTweet', () => {
1501 let tweets = Array.from(document.getElementsByClassName('tweet'));
1502 if(activeTweet) {
1503 activeTweet.classList.remove('tweet-active');
1504 }
1505 let scrollPoint = scrollY + innerHeight/2;
1506 activeTweet = tweets.find(t => scrollPoint > t.offsetTop && scrollPoint < t.offsetTop + t.offsetHeight);
1507 if(activeTweet) {
1508 activeTweet.classList.add('tweet-active');
1509 }
1510});
1511let loadingNewTweets = true;
1512let lastTweetDate = 0;
1513let activeTweet;
1514let tweetsToLoad = {};
1515let lastScroll = Date.now();
1516let loadingFollowing = false;
1517let loadingFollowers = false;
1518let loadingFollowersYouKnow = false;
1519let followingMoreBtn, followersMoreBtn, followersYouFollowMoreBtn;
1520
1521setTimeout(async () => {
1522 if(!vars) {
1523 await loadVars();
1524 }
1525 while(!LOC || !LOC.january) {
1526 await sleep(10);
1527 }
1528 months = [LOC.january.message, LOC.february.message, LOC.march.message, LOC.april.message, LOC.may.message, LOC.june.message, LOC.july.message, LOC.august.message, LOC.september.message, LOC.october.message, LOC.november.message, LOC.december.message];
1529
1530 // weird bug
1531 if(!document.getElementById('new-tweets')) {
1532 return setTimeout(() => location.reload(), 500);
1533 }
1534 try {
1535 document.getElementById('new-tweets').addEventListener('click', () => {
1536 timeline.toBeUpdated = 0;
1537 timeline.data = timeline.dataToUpdate;
1538 timeline.dataToUpdate = [];
1539 renderNewTweetsButton();
1540 renderTimeline();
1541 });
1542 } catch(e) {
1543 setTimeout(() => location.reload(), 500);
1544 console.error(e);
1545 return;
1546 }
1547
1548 // mouse
1549 let banner = document.getElementById('profile-banner');
1550 let navProfileInfo = document.getElementById('nav-profile-info');
1551 document.addEventListener('scroll', async () => {
1552 lastScroll = Date.now();
1553 // find active tweet by scroll amount
1554 if(Date.now() - lastTweetDate > 50) {
1555 lastTweetDate = Date.now();
1556 let tweets = Array.from(document.getElementsByClassName('tweet'));
1557
1558 let scrollPoint = scrollY + innerHeight/2;
1559 let newActiveTweet = tweets.find(t => scrollPoint > t.offsetTop && scrollPoint < t.offsetTop + t.offsetHeight);
1560 if(!activeTweet || (newActiveTweet && !activeTweet.className.startsWith(newActiveTweet.className))) {
1561 if(activeTweet) {
1562 activeTweet.classList.remove('tweet-active');
1563 }
1564 if(newActiveTweet) newActiveTweet.classList.add('tweet-active');
1565 if(vars.autoplayVideos && !document.getElementsByClassName('modal')[0]) {
1566 if(activeTweet) {
1567 let video = activeTweet.querySelector('.tweet-media > video[controls]');
1568 if(video) {
1569 video.pause();
1570 }
1571 }
1572 if(newActiveTweet) {
1573 let newVideo = newActiveTweet.querySelector('.tweet-media > video[controls]');
1574 let newVideoOverlay = newActiveTweet.querySelector('.tweet-media > .tweet-media-video-overlay');
1575 if(newVideo && !newVideo.ended) {
1576 newVideo.play();
1577 } else if(newVideoOverlay && !newVideoOverlay.style.display) {
1578 newVideoOverlay.click();
1579 }
1580 }
1581 }
1582 activeTweet = newActiveTweet;
1583 }
1584 }
1585
1586 // make user nav appear
1587 if(window.scrollY >= 600) {
1588 if(!navProfileInfo.style.opacity) {
1589 navProfileInfo.style.opacity = 1;
1590 }
1591 } else {
1592 if(navProfileInfo.style.opacity) {
1593 navProfileInfo.style.opacity = '';
1594 }
1595 }
1596
1597 // banner scroll
1598 banner.style.top = `${5+Math.min(window.scrollY/4, 470/4)}px`;
1599
1600 // load more stuff
1601 if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 1000) {
1602 if(subpage === 'following') {
1603 if(!loadingFollowing) followingMoreBtn.click();
1604 return;
1605 }
1606 if(subpage === 'followers') {
1607 if(!loadingFollowers) followersMoreBtn.click();
1608 return;
1609 }
1610 if(subpage === 'followers_you_follow') {
1611 if(!loadingFollowersYouKnow) followersYouFollowMoreBtn.click();
1612 return;
1613 }
1614 if (loadingNewTweets || timeline.data.length === 0 || stopLoad) return;
1615 loadingNewTweets = true;
1616 let tl;
1617 try {
1618 if (!user_protected && !user_blocked_by) {
1619 if(subpage === "likes") {
1620 let data = await API.user.getFavorites(pageUser.id_str, favoritesCursor);
1621 tl = data.tl;
1622 favoritesCursor = data.cursor;
1623 } else {
1624 if(subpage === 'media') {
1625 tl = await API.user.getMediaTweets(pageUser.id_str, mediaCursor);
1626 mediaCursor = tl.cursor;
1627 tl = tl.tweets;
1628 } else {
1629 tl = await API.user.getTweetsV2(pageUser.id_str, tweetsCursor, subpage !== 'profile');
1630 tweetsCursor = tl.cursor;
1631 tl = tl.tweets;
1632 }
1633 }
1634 } else if (user_blocked_by) {
1635 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.blocked_by_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.why_you_cant_see_block_user.message.replaceAll("$SCREEN_NAME$",pageUser.screen_name)}</p></div>`;
1636 return;
1637 } else if (user_protected) {
1638 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.user_protected.message}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.follow_to_see.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p></div>`;
1639 return;
1640 } /*else if (user_blocking) {
1641 document.getElementById("timeline").innerHTML = `<div dir="auto" style="padding: 50px;color: var(--darker-gray); font-size: 20px;"><h2>${LOC.you_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</h2><p style="font-size: 15px;" href="https://twitter.com/${pageUser.screen_name}">${LOC.do_you_want_see_blocked_user.message.replace("$SCREEN_NAME$",pageUser.screen_name)}</p><button class="nice-button" id="see-tweet-btn">${LOC.I_want_see_blocked_user.message}</button> </div>`;
1642
1643 return;
1644 }*/
1645 } catch (e) {
1646 console.error(e);
1647 loadingNewTweets = false;
1648 return;
1649 }
1650 let originalLength = timeline.data.length;
1651 timeline.data = timeline.data.concat(tl);
1652 averageLikeCount = timeline.data.filter(t => !t.retweeted_status).map(t => t.favorite_count).sort((a, b) => a - b)[Math.floor(timeline.data.length/2)];
1653 if(subpage === 'profile') {
1654 if(!tweetsCursor) {
1655 stopLoad = true;
1656 }
1657 } else {
1658 if(previousLastTweet && previousLastTweet.id_str === timeline.data[timeline.data.length - 1].id_str) return stopLoad = true;
1659 }
1660 previousLastTweet = timeline.data[timeline.data.length - 1];
1661 await renderTimeline(true, originalLength);
1662 }
1663 }, { passive: true });
1664 // document.addEventListener('mousemove', e => {
1665 // if(Date.now() - lastScroll > 10) {
1666 // let t = e.target;
1667 // if(t.className.includes('tweet ') || t.className === 'tweet-interact' || t.className === 'tweet-body' || t.className === 'tweet-media') {
1668 // if(t.className === 'tweet-interact' || t.className === 'tweet-media') t = t.parentElement.parentElement;
1669 // else if(t.className === 'tweet-body') t = t.parentElement;
1670 // let id = t.className.split('id-')[1];
1671 // if(!id) return;
1672 // id = id.split(' ')[0];
1673 // if(!tweetsToLoad[id]) tweetsToLoad[id] = 1;
1674 // else tweetsToLoad[id]++;
1675 // if(tweetsToLoad[id] === 15) {
1676 // API.tweet.getRepliesV2(id);
1677 // API.tweet.getLikers(id);
1678 // t.classList.add('tweet-preload');
1679 // console.log(`Preloading ${id}`);
1680 // }
1681 // }
1682 // }
1683 // });
1684
1685 // buttons
1686 document.getElementById('tweet-to').addEventListener('click', () => {
1687 document.getElementById('navbar-tweet-button').click();
1688 setTimeout(() => {
1689 document.getElementsByClassName('navbar-new-tweet-text')[0].value = `@${pageUser.screen_name} `;
1690 }, 10);
1691 });
1692 let tweetNavMoreMenu = document.getElementById('tweet-nav-more-menu');
1693 let tweetNavClicked = false;
1694 let tweetNavMoreMenuHR = document.getElementById('tweet-nav-more-menu-hr');
1695 let tweetNavMoreMenuHNR = document.getElementById('tweet-nav-more-menu-hnr');
1696 document.getElementById('tweet-nav-more').addEventListener('click', () => {
1697 if (tweetNavMoreMenu.hidden) {
1698 tweetNavMoreMenu.hidden = false;
1699 if(subpage === 'replies') {
1700 tweetNavMoreMenu.style.height = '77px';
1701 tweetNavMoreMenuHNR.hidden = false;
1702 document.getElementById('tweet-nav-more-menu-hnr-label').hidden = false;
1703 }
1704 }
1705 if(tweetNavClicked) return;
1706 tweetNavClicked = true;
1707 setTimeout(() => {
1708 function closeMenu(e) {
1709 if(e.target.closest('#tweet-nav-more-menu')) {
1710 return;
1711 }
1712 tweetNavClicked = false;
1713 setTimeout(() => {
1714 tweetNavMoreMenu.hidden = true;
1715 tweetNavMoreMenu.style.height = '';
1716 tweetNavMoreMenuHNR.hidden = true;
1717 document.getElementById('tweet-nav-more-menu-hnr-label').hidden = true;
1718 }, 50);
1719 document.body.removeEventListener('click', closeMenu);
1720 }
1721 document.body.addEventListener('click', closeMenu);
1722 }, 50);
1723 });
1724 function updateHideStyle() {
1725 let style = '';
1726 if(tweetNavMoreMenuHR.checked) {
1727 style += `.tweet-top-retweet-label { display: none !important; }`;
1728 }
1729 if(tweetNavMoreMenuHNR.checked) {
1730 style += `.tweet-non-reply { display: none !important; }`;
1731 }
1732 document.getElementById('style-hide-retweets').innerHTML = style;
1733 }
1734 tweetNavMoreMenuHR.addEventListener('change', updateHideStyle);
1735 tweetNavMoreMenuHNR.addEventListener('change', updateHideStyle);
1736 document.getElementById('wtf-refresh').addEventListener('click', async () => {
1737 renderDiscovery(false);
1738 });
1739 followingMoreBtn = document.getElementById('following-more');
1740 followingMoreBtn.addEventListener('click', async e => {
1741 if(!followingCursor || loadingFollowing) return;
1742 e.target.innerText = LOC.loading.message;
1743 renderFollowing(false, followingCursor);
1744 });
1745 followersMoreBtn = document.getElementById('followers-more');
1746 followersMoreBtn.addEventListener('click', async e => {
1747 if(!followersCursor || loadingFollowers) return;
1748 e.target.innerText = LOC.loading.message;
1749 renderFollowers(false, followersCursor);
1750 });
1751 followersYouFollowMoreBtn = document.getElementById('followers_you_follow-more');
1752 followersYouFollowMoreBtn.addEventListener('click', async e => {
1753 if(!followersYouKnowCursor || loadingFollowersYouKnow) return;
1754 e.target.innerText = LOC.loading.message;
1755 renderFollowersYouFollow(false, followersYouKnowCursor);
1756 });
1757 function updatePath(e) {
1758 if(e.target.closest('.tweet-nav-active') || e.target.classList.contains('profile-stat-active') || e.target.closest('.profile-stat-disabled')) {
1759 return e.preventDefault();
1760 }
1761 e.preventDefault();
1762 let el = e.target;
1763 if(!el) return;
1764 if(!el.href) el = el.parentElement;
1765 history.pushState({}, null, el.href);
1766 updateSubpage();
1767 updateSelection();
1768 timeline = {
1769 data: [],
1770 dataToUpdate: [],
1771 toBeUpdated: 0
1772 }
1773 seenThreads = [];
1774 pinnedTweet = undefined;
1775 tweetsCursor = undefined;
1776 favoritesCursor = undefined;
1777 followersCursor = undefined;
1778 followingCursor = undefined;
1779 followersYouKnowCursor = undefined;
1780 mediaCursor = undefined;
1781 if(window.scrollY > 400) window.scrollTo(0, 400);
1782 if(subpage === 'following') {
1783 renderFollowing();
1784 } else if(subpage === 'followers') {
1785 renderFollowers();
1786 } else if(subpage === 'followers_you_follow') {
1787 renderFollowersYouFollow();
1788 } else if(subpage === 'lists') {
1789 renderLists();
1790 } else {
1791 loadingNewTweets = true;
1792 updateTimeline();
1793 }
1794 }
1795 document.getElementById('tweet-nav-tweets').addEventListener('click', updatePath);
1796 document.getElementById('tweet-nav-replies').addEventListener('click', updatePath);
1797 document.getElementById('tweet-nav-media').addEventListener('click', updatePath);
1798 if(document.getElementById('profile-media-text')) document.getElementById('profile-media-text').addEventListener('click', updatePath);
1799 document.getElementById('profile-stat-tweets-link').addEventListener('click', updatePath);
1800 document.getElementById('profile-stat-following-link').addEventListener('click', updatePath);
1801 document.getElementById('profile-stat-followers-link').addEventListener('click', updatePath);
1802 document.getElementById('profile-stat-favorites-link').addEventListener('click', updatePath);
1803 document.getElementById('profile-stat-media-link').addEventListener('click', updatePath);
1804 document.getElementById('profile-friends-text').addEventListener('click', updatePath);
1805 document.addEventListener('click', async e => {
1806 let el = e.target;
1807 if(!el) return;
1808 if(el.tagName !== 'A') el = el.closest('a');
1809 if(!el) return;
1810 if(el.tagName === "A") {
1811 let path;
1812 try {
1813 let url = new URL(el.href);
1814 path = url.pathname;
1815 if(url.hostname !== 'twitter.com') return;
1816 } catch(e) {
1817 return;
1818 }
1819 if(/^\/[A-z-0-9-_]{1,15}$/.test(path) && ["/home", "/", "/notifications", "/messages", "/settings", "/search", "/explore", "/login", "/register", "/logout"].indexOf(path) === -1) {
1820 if(document.querySelector(".modal")) return;
1821 e.preventDefault();
1822 window.scrollTo(0, 0);
1823 mediaToUpload = [];
1824 loadingNewTweets = true;
1825 document.getElementById('loading-box').hidden = false;
1826 everAddedAdditional = false;
1827 document.getElementById('timeline').innerHTML = `
1828 <div class="loading-data" id="tweets-loader">
1829 <img src="${chrome.runtime.getURL(`images/loading.svg`)}" width="64" height="64">
1830 </div>`;
1831 document.getElementById('profile-media-div').innerHTML = '';
1832 document.getElementById('tweet-to-bg').hidden = true;
1833 document.getElementById('profile-additional').innerHTML = '';
1834 document.getElementById('profile-friends-div').innerHTML = '';
1835 history.pushState({}, null, `https://twitter.com/${path.substring(1)}`);
1836 updateSubpage();
1837 updateSelection();
1838 await updateUserData();
1839 updateTimeline();
1840 renderDiscovery();
1841 }
1842 }
1843 });
1844 window.addEventListener("popstate", async () => {
1845 if(document.querySelector('.tweet-viewer')) return;
1846 let path = location.pathname;
1847 if(path.endsWith("/")) path = path.substring(0, path.length - 1);
1848 if(isProfilePath(path) || (path.split('/').length === 3 && location.pathname.endsWith('/following') || location.pathname.endsWith('/followers') || location.pathname.endsWith('/followers_you_follow') || location.pathname.endsWith('/lists') || location.pathname.endsWith('/media') || location.pathname.endsWith('/likes') || location.pathname.endsWith('/with_replies'))) {
1849 document.getElementById('loading-box').hidden = false;
1850 everAddedAdditional = false;
1851 loadingNewTweets = true;
1852 mediaToUpload = [];
1853 document.getElementById('profile-media-div').innerHTML = '';
1854 document.getElementById('tweet-to-bg').hidden = true;
1855 document.getElementById('profile-additional').innerHTML = '';
1856 document.getElementById('profile-friends-div').innerHTML = '';
1857 updateSubpage();
1858 updateSelection();
1859 document.getElementById('timeline').innerHTML = '';
1860 await updateUserData();
1861 updateTimeline();
1862 renderDiscovery();
1863 }
1864 });
1865
1866 document.getElementById('user-search-input').addEventListener('keydown', e => {
1867 if(e.key === 'Enter') {
1868 document.getElementById('user-search-icon').click();
1869 }
1870 });
1871 document.getElementById('user-search-icon').addEventListener("click", () => {
1872 document.getElementById('search-input').value = document.getElementById('user-search-input').value + ` from:${pageUser.screen_name}`;
1873 document.getElementById('search-icon').click();
1874 })
1875
1876 let mediaDiv = document.getElementById('profile-media-div');
1877 let mediaText = document.getElementById('profile-media-text');
1878 let mediaObserver = new MutationObserver(() => {
1879 mediaText.hidden = mediaDiv.childElementCount === 0;
1880 })
1881 mediaObserver.observe(mediaDiv, {
1882 childList: true
1883 });
1884
1885 // Update dates every minute
1886 setInterval(() => {
1887 let tweetDates = Array.from(document.getElementsByClassName('tweet-time'));
1888 let tweetQuoteDates = Array.from(document.getElementsByClassName('tweet-time-quote'));
1889 let all = [...tweetDates, ...tweetQuoteDates];
1890 all.forEach(date => {
1891 date.innerText = timeElapsed(+date.dataset.timestamp);
1892 });
1893 }, 60000);
1894
1895 // Custom events
1896 document.addEventListener('newTweet', e => {
1897 if(pageUser.id_str === user.id_str) {
1898 let tweet = e.detail;
1899 if(pinnedTweet) {
1900 let firstTweet = document.getElementById('timeline').firstChild;
1901 appendTweet(tweet, document.getElementById('timeline'), { after: firstTweet, disableAfterReplyCounter: true, bigFont: tweet.favorite_count > averageLikeCount*1.2 && tweet.favorite_count > 3 });
1902 } else {
1903 appendTweet(tweet, document.getElementById('timeline'), { prepend: true, bigFont: tweet.favorite_count > averageLikeCount*1.2 && tweet.favorite_count > 3 });
1904 }
1905 }
1906 });
1907
1908 // Customization
1909 let r = document.querySelector(':root');
1910 let profileLinkColor = document.getElementById('profile-link-color');
1911 let colorSyncButton = document.getElementById('color-sync-button');
1912 let cssSyncButton = document.getElementById('css-sync-button');
1913 let cssLoadButton = document.getElementById('css-load-button');
1914 let darkModeVars = document.getElementById('dark-mode-vars');
1915 let lightModeVars = document.getElementById('light-mode-vars');
1916 let cssTextArea = document.getElementById('profile-css-textarea');
1917 let saveDraft = document.getElementById('save-draft');
1918 let loadDraft = document.getElementById('load-draft');
1919
1920 darkModeVars.addEventListener('change', async () => {
1921 if(isDarkModeEnabled) {
1922 await switchDarkMode(true);
1923 let vars = parseVariables(darkModeVars.value);
1924 for(let i in vars) {
1925 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
1926 }
1927 }
1928 });
1929 lightModeVars.addEventListener('change', async () => {
1930 if(!isDarkModeEnabled) {
1931 await switchDarkMode(false);
1932 let vars = parseVariables(lightModeVars.value);
1933 for(let i in vars) {
1934 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
1935 }
1936 }
1937 });
1938 cssTextArea.addEventListener('change', () => {
1939 if(!customCSS) {
1940 customCSS = document.createElement('style');
1941 customCSS.id = 'oldtwitter-custom-css';
1942 document.head.appendChild(customCSS);
1943 }
1944 customCSS.innerHTML = cssTextArea.value;
1945 });
1946 darkModeVars.addEventListener('keydown', async e => {
1947 if(e.keyCode === 83 && e.ctrlKey) {
1948 e.preventDefault();
1949 e.stopImmediatePropagation();
1950 if(isDarkModeEnabled) {
1951 await switchDarkMode(true);
1952 let vars = parseVariables(darkModeVars.value);
1953 for(let i in vars) {
1954 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
1955 }
1956 }
1957 }
1958 });
1959 lightModeVars.addEventListener('keydown', async e => {
1960 if(e.keyCode === 83 && e.ctrlKey) {
1961 e.preventDefault();
1962 e.stopImmediatePropagation();
1963 if(!isDarkModeEnabled) {
1964 await switchDarkMode(false);
1965 let vars = parseVariables(lightModeVars.value);
1966 for(let i in vars) {
1967 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
1968 }
1969 }
1970 }
1971 });
1972 cssTextArea.addEventListener('keydown', e => {
1973 if(e.key === "Tab") {
1974 e.preventDefault();
1975 e.stopImmediatePropagation();
1976 let pos = cssTextArea.selectionStart;
1977 cssTextArea.value = cssTextArea.value.slice(0, pos) + " " + cssTextArea.value.slice(pos);
1978 cssTextArea.selectionStart = cssTextArea.selectionEnd = pos + 2;
1979 } else if(e.keyCode === 83 && e.ctrlKey) {
1980 e.preventDefault();
1981 e.stopImmediatePropagation();
1982 if(!customCSS) {
1983 customCSS = document.createElement('style');
1984 customCSS.id = 'oldtwitter-custom-css';
1985 document.head.appendChild(customCSS);
1986 }
1987 customCSS.innerHTML = cssTextArea.value;
1988 }
1989 });
1990 saveDraft.addEventListener('click', () => {
1991 chrome.storage.local.set({
1992 cssDraft: {
1993 darkVars: darkModeVars.value,
1994 lightVars: lightModeVars.value,
1995 css: cssTextArea.value
1996 }
1997 });
1998 toast.info(LOC.draft_saved.message);
1999 });
2000 loadDraft.addEventListener('click', () => {
2001 chrome.storage.local.get(['cssDraft'], async d => {
2002 if(d.cssDraft) {
2003 if(!d.cssDraft.darkVars && !d.cssDraft.lightVars && !d.cssDraft.css) return toast.error(LOC.no_draft.message);
2004 darkModeVars.value = d.cssDraft.darkVars;
2005 lightModeVars.value = d.cssDraft.lightVars;
2006 cssTextArea.value = d.cssDraft.css;
2007 toast.info(LOC.draft_loaded.message);
2008
2009 if(!customCSS) {
2010 customCSS = document.createElement('style');
2011 customCSS.id = 'oldtwitter-custom-css';
2012 document.head.appendChild(customCSS);
2013 }
2014 customCSS.innerHTML = cssTextArea.value;
2015
2016 if(isDarkModeEnabled) {
2017 await switchDarkMode(true);
2018 let vars = parseVariables(darkModeVars.value);
2019 for(let i in vars) {
2020 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
2021 }
2022 } else {
2023 await switchDarkMode(false);
2024 let vars = parseVariables(lightModeVars.value);
2025 for(let i in vars) {
2026 if(r.style.getPropertyValue(i)) r.style.setProperty(i, vars[i]);
2027 }
2028 }
2029 } else {
2030 toast.error(LOC.no_draft.message);
2031 }
2032 });
2033 });
2034 cssSyncButton.addEventListener('click', async () => {
2035 cssSyncButton.disabled = true;
2036 try {
2037 let res = await fetch(`https://dimden.dev/services/twitter_link_colors/v2/setcss`, {
2038 method: 'POST',
2039 headers: {
2040 'Content-Type': 'application/json'
2041 },
2042 body: JSON.stringify({
2043 css: cssTextArea.value,
2044 dark_mode_vars: darkModeVars.value,
2045 light_mode_vars: lightModeVars.value,
2046 private_token: await getOtAuthToken(!(user.followers_count >= 5000 && cssEligibleAuto && !cssEligible))
2047 })
2048 }).then(i => i.text());
2049 if(res === 'auth_error') {
2050 chrome.storage.local.get(["otPrivateTokens"], data => {
2051 delete data.otPrivateTokens[pageUser.id_str];
2052 chrome.storage.local.set({ otPrivateTokens: data.otPrivateTokens });
2053 cssSyncButton.disabled = false;
2054 alert(LOC.invalid_auth_token.message);
2055 });
2056 } else if(res === 'set') {
2057 chrome.storage.local.get(["profileCustomCSS"], async data => {
2058 if(!data.profileCustomCSS) {
2059 data.profileCustomCSS = {};
2060 }
2061 data.profileCustomCSS[pageUser.id_str] = {
2062 css: cssTextArea.value,
2063 darkModeVars: darkModeVars.value,
2064 lightModeVars: lightModeVars.value
2065 };
2066 chrome.storage.local.set({ profileCustomCSS: data.profileCustomCSS });
2067 });
2068 alert(LOC.css_set.message);
2069 } else {
2070 alert(res);
2071 }
2072 } catch(e) {
2073 console.error(e);
2074 alert(LOC.error_setting_css.message);
2075 } finally {
2076 cssSyncButton.disabled = false;
2077 }
2078 });
2079 cssLoadButton.addEventListener('click', async () => {
2080 fetch(`https://dimden.dev/services/twitter_link_colors/v2/get_data/${pageUser.id_str}`).then(r => r.json()).then(data => {
2081 cssTextArea.value = data.css;
2082 darkModeVars.value = data.css_vars_dark;
2083 lightModeVars.value = data.css_vars_light;
2084
2085 if(data.css) {
2086 if(!customCSS) {
2087 customCSS = document.createElement('style');
2088 customCSS.id = 'oldtwitter-custom-css';
2089 document.head.appendChild(customCSS);
2090 }
2091 customCSS.innerHTML = data.css;
2092 }
2093 if(data.css_vars_dark && isDarkModeEnabled) {
2094 let vars = parseVariables(data.css_vars_dark);
2095 for(let i in vars) {
2096 r.style.setProperty(i, vars[i]);
2097 }
2098 } else if(data.css_vars_light && !isDarkModeEnabled) {
2099 let vars = parseVariables(data.css_vars_light);
2100 for(let i in vars) {
2101 r.style.setProperty(i, vars[i]);
2102 }
2103 } else {
2104 switchDarkMode(isDarkModeEnabled);
2105 }
2106 });
2107 });
2108 colorSyncButton.addEventListener('click', async () => {
2109 colorSyncButton.disabled = true;
2110 let color = profileLinkColor.value;
2111 if(color.startsWith('#')) color = color.slice(1);
2112 try {
2113 let res = await fetch(`https://dimden.dev/services/twitter_link_colors/v2/setcolor`, {
2114 method: 'POST',
2115 headers: {
2116 'Content-Type': 'application/json'
2117 },
2118 body: JSON.stringify({
2119 color,
2120 private_token: await getOtAuthToken()
2121 })
2122 }).then(i => i.text());
2123 if(res === 'auth_error') {
2124 chrome.storage.local.get(["otPrivateTokens"], data => {
2125 delete data.otPrivateTokens[pageUser.id_str];
2126 chrome.storage.local.set({ otPrivateTokens: data.otPrivateTokens });
2127 colorSyncButton.disabled = false;
2128 alert(LOC.invalid_auth_token.message);
2129 });
2130 } else if(res === 'error') {
2131 alert(LOC.error_setting_color.message);
2132 } else {
2133 alert(LOC.link_color_set.message);
2134 chrome.storage.local.get(["linkColors"], async lc => {
2135 let linkColors = lc.linkColors || {};
2136 linkColors[user.id_str] = color;
2137 chrome.storage.local.set({ linkColors });
2138 });
2139 }
2140 } catch(e) {
2141 console.error(e);
2142 alert(LOC.error_setting_color.message);
2143 } finally {
2144 colorSyncButton.disabled = false;
2145 }
2146 });
2147
2148 // Run
2149 updateSubpage();
2150 await updateUserData();
2151 if(subpage !== 'following' && subpage !== 'followers' && subpage !== 'followers_you_follow' && subpage !== 'lists') updateTimeline();
2152 else if(subpage === 'following') {
2153 renderFollowing();
2154 } else if(subpage === 'followers') {
2155 renderFollowers();
2156 } else if(subpage === 'followers_you_follow') {
2157 renderFollowersYouFollow();
2158 } else if(subpage === 'lists') {
2159 renderLists();
2160 }
2161 if(location.hash === "#dm") {
2162 setTimeout(() => {
2163 let event = new CustomEvent('messageUser', { detail: { id: `${user.id_str}-${pageUser.id_str}`, user: pageUser } });
2164 document.dispatchEvent(event);
2165 location.hash = "";
2166 }, 1000);
2167 }
2168 renderDiscovery();
2169 renderTrends(true);
2170 setInterval(() => renderDiscovery(false), 60000 * 5);
2171 setInterval(() => renderTrends(true), 60000 * 5);
2172}, 50);