Extension to return old Twitter layout from 2015.
at master 22 kB view raw
1let user = {}; 2let cursor; 3let linkColors = {}; 4let listId = location.pathname.split('/')[3]; 5let subpage; 6// Util 7 8function updateSubpage() { 9 Array.from(document.getElementsByClassName('list-switch')).forEach(el => el.classList.remove('list-switch-active')); 10 document.getElementById('list-members-container').hidden = true; 11 document.getElementById('list-tweets-container').hidden = true; 12 document.getElementById('list-followers-container').hidden = true; 13 end = false; 14 cursor = undefined; 15 16 if(location.href.endsWith('/members')) { 17 subpage = 'members'; 18 document.getElementById('list-members-container').hidden = false; 19 document.getElementById('list-members').innerHTML = ''; 20 document.getElementById('ns-members').classList.add('list-switch-active'); 21 } else if(location.href.endsWith('/followers')) { 22 subpage = 'followers'; 23 document.getElementById('list-followers-container').hidden = false; 24 document.getElementById('list-followers').innerHTML = ''; 25 document.getElementById('ns-followers').classList.add('list-switch-active'); 26 } else { 27 subpage = 'tweets'; 28 document.getElementById('list-tweets-container').hidden = false; 29 document.getElementById('list-tweets').innerHTML = ''; 30 document.getElementById('ns-tweets').classList.add('list-switch-active'); 31 } 32} 33function updateUserData() { 34 API.account.verifyCredentials().then(u => { 35 user = u; 36 userDataFunction(u); 37 renderUserData(); 38 }).catch(e => { 39 if (e === "Not logged in") { 40 window.location.href = "https://twitter.com/i/flow/login?newtwitter=true"; 41 } 42 console.error(e); 43 }); 44} 45// Render 46function renderUserData() { 47 document.getElementById('wtf-viewall').href = `https://twitter.com/i/connect_people?newtwitter=true&user_id=${user.id_str}`; 48} 49 50function renderListData(data) { 51 if(data.custom_banner_media) { 52 document.getElementById('list-banner').src = data.custom_banner_media.media_info.original_img_url; 53 } else { 54 document.getElementById('list-banner').src = data.default_banner_media.media_info.original_img_url; 55 } 56 57 document.getElementsByTagName('title')[0].innerText = `${data.name} List - OldTwitter`; 58 document.getElementById('list-name').innerText = data.name; 59 document.getElementById('list-name').classList.toggle('user-protected', data.mode === 'Private'); 60 document.getElementById('list-description').innerText = data.description; 61 document.getElementById('list-members-count').innerText = data.member_count; 62 document.getElementById('list-followers-count').innerText = data.subscriber_count; 63 if(data.user_results && data.user_results.result) { 64 document.getElementById('list-user').href = `https://twitter.com/${data.user_results.result.legacy.screen_name}/lists`; 65 document.getElementById('list-avatar').src = `${(data.user_results.result.legacy.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(data.user_results.result.legacy.id_str) % 7}_normal.png`): data.user_results.result.legacy.profile_image_url_https}`.replace('_normal', '_bigger'); 66 let actions = document.getElementById('list-actions'); 67 actions.innerHTML = ``; 68 if(data.user_results.result.rest_id === user.id_str) { 69 actions.innerHTML = ` 70 <button class="nice-button" id="list-btn-edit">${LOC.edit.message}</button> 71 <button class="nice-button" id="list-btn-delete">${LOC.delete.message}</button> 72 `; 73 document.getElementById('list-btn-edit').addEventListener('click', () => { 74 let modal = createModal(` 75 <div id="list-editor"> 76 <h1 class="cool-header">${LOC.edit_list.message}</h1><br> 77 <span id="list-editor-error" style="color:red"></span><br> 78 ${LOC.name.message}:<br><input maxlength="25" type="text" id="list-name-input" value="${escapeHTML(data.name)}"><br><br> 79 ${LOC.description.message}:<br><textarea maxlength="100" type="text" id="list-description-input">${escapeHTML(data.description)}</textarea><br> 80 <br> 81 ${LOC.is_private.message}: <input type="checkbox" style="width: 15px;" id="list-private-input" ${data.mode === 'Private' ? 'checked' : ''}><br> 82 <br> 83 <button class="nice-button" id="list-btn-save">${LOC.save.message}</button> 84 <button class="nice-button" id="list-btn-members">${LOC.edit_members.message}</button> 85 </div> 86 <div id="list-editor-members" hidden> 87 <h1 class="cool-header">${LOC.edit_members.message}</h1> 88 <span id='list-editor-members-back'>${LOC.back.message}</span> 89 <br> 90 <div id="list-editor-members-container"></div> 91 <div class="box" style="border-bottom:none"></div> 92 <div id="list-editor-members-more" class="center-text" style="padding-left: 90px;">${LOC.load_more.message}</div> 93 </div> 94 `, 'list-editor-modal'); 95 document.getElementById('list-btn-save').addEventListener('click', async () => { 96 document.getElementById('list-editor-error').innerText = ''; 97 let name = document.getElementById('list-name-input').value; 98 let description = document.getElementById('list-description-input').value; 99 let isPrivate = document.getElementById('list-private-input').checked; 100 try { 101 await API.list.update(data.id_str, name, description, isPrivate); 102 document.getElementById('list-name').classList.toggle('user-protected', isPrivate); 103 } catch(e) { 104 return document.getElementById('list-editor-error').innerText = e && e.message ? e.message : e; 105 } 106 modal.remove(); 107 renderListData(await API.list.get(data.id_str)); 108 }); 109 let membersCursor; 110 let membersContainer = document.getElementById('list-editor-members-container'); 111 async function getMembers() { 112 let listMembers = await API.list.getMembers(data.id_str, membersCursor); 113 membersCursor = listMembers.cursor; 114 listMembers = listMembers.list; 115 if(!cursor || listMembers.length === 0) document.getElementById('list-editor-members-more').hidden = true; 116 for(let i in listMembers) { 117 let t = listMembers[i]; 118 let followingElement = document.createElement('div'); 119 followingElement.classList.add('following-item'); 120 followingElement.innerHTML = ` 121 <div style="height:48px"> 122 <a href="https://twitter.com/${t.screen_name}" class="following-item-link"> 123 <img src="${(t.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(t.id_str) % 7}_normal.png`): t.profile_image_url_https}" alt="${t.screen_name}" class="following-item-avatar tweet-avatar" width="48" height="48"> 124 <div class="following-item-text"> 125 <span class="tweet-header-name following-item-name">${escapeHTML(t.name)}</span><br> 126 <span class="tweet-header-handle">@${t.screen_name}</span> 127 ${t.followed_by ? `<span class="follows-you-label">${LOC.follows_you.message}</span>` : ''} 128 </div> 129 </a> 130 </div> 131 <div> 132 <button class="following-item-btn nice-button">${LOC.remove.message}</button> 133 </div>`; 134 135 let removeButton = followingElement.querySelector('.following-item-btn'); 136 removeButton.addEventListener('click', async () => { 137 await API.list.removeMember(listId, t.id_str); 138 document.getElementById('list-members-count').innerText = parseInt(document.getElementById('list-members-count').innerText) - 1; 139 followingElement.remove(); 140 }); 141 142 membersContainer.appendChild(followingElement); 143 if(vars.enableTwemoji) twemoji.parse(followingElement); 144 } 145 } 146 document.getElementById('list-btn-members').addEventListener('click', async () => { 147 document.getElementById('list-editor').hidden = true; 148 document.getElementById('list-editor-members').hidden = false; 149 getMembers(); 150 }); 151 document.getElementById('list-editor-members-more').addEventListener('click', getMembers); 152 document.getElementById('list-editor-members-back').addEventListener('click', () => { 153 document.getElementById('list-editor').hidden = false; 154 document.getElementById('list-editor-members').hidden = true; 155 }); 156 }); 157 document.getElementById('list-btn-delete').addEventListener('click', async () => { 158 let modal = createModal(` 159 <h1 class="cool-header">${LOC.delete_list.message}</h1><br> 160 <span>${LOC.delete_list_sure.message}</span> 161 <br><br> 162 <button class="nice-button" id="list-btn-delete-confirm">${LOC.delete.message}</button> 163 `, 'list-editor-modal'); 164 document.getElementById('list-btn-delete-confirm').addEventListener('click', async () => { 165 await API.list.delete(data.id_str); 166 modal.remove(); 167 window.location.href = `https://twitter.com/${user.screen_name}/lists`; 168 }); 169 }); 170 } else { 171 actions.innerHTML = `<button class="nice-button" id="list-btn-subscribe">${data.following ? LOC.unsubscribe.message : LOC.subscribe.message}</button>`; 172 document.getElementById('list-btn-subscribe').addEventListener('click', async () => { 173 if(data.following) { 174 await API.list.unsubscribe(data.id_str); 175 document.getElementById('list-followers-count').innerText = +document.getElementById('list-followers-count').innerText - 1; 176 data.following = false; 177 document.getElementById('list-btn-subscribe').innerText = LOC.subscribe.message; 178 } else { 179 await API.list.subscribe(data.id_str); 180 document.getElementById('list-followers-count').innerText = +document.getElementById('list-followers-count').innerText + 1; 181 data.following = true; 182 document.getElementById('list-btn-subscribe').innerText = LOC.unsubscribe.message; 183 } 184 }); 185 } 186 } 187} 188async function renderListTweets(c) { 189 let [listInfo, listTweets] = await Promise.allSettled([ 190 API.list.get(listId), 191 API.list.getTweets(listId, c) 192 ]).catch(e => { 193 console.error(e); 194 }); 195 if(listTweets.reason) { 196 console.error(listTweets.reason); 197 document.getElementById('loading-box').hidden = false; 198 document.getElementById('loading-box-error').innerHTML = `${LOC.list_not_found.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`; 199 return false; 200 } 201 listInfo = listInfo.value; 202 listTweets = listTweets.value; 203 cursor = listTweets.cursor; 204 listTweets = listTweets.list; 205 if(!cursor || listTweets.length === 0) end = true; 206 renderListData(listInfo); 207 let container = document.getElementById('list-tweets'); 208 for(let i in listTweets) { 209 let t = listTweets[i]; 210 if(t.retweeted_status) { 211 await appendTweet(t.retweeted_status, container, { 212 top: { 213 text: `<a href="https://twitter.com/${t.user.screen_name}">${escapeHTML(t.user.name)}</a> ${LOC.retweeted.message}`, 214 icon: "\uf006", 215 color: "#77b255", 216 class: 'retweet-label' 217 }, 218 translate: vars.autotranslateProfiles.includes(t.user.id_str) 219 }); 220 } else { 221 await appendTweet(t, container, { 222 bigFont: typeof t.full_text === 'string' && t.full_text.length < 75, 223 translate: vars.autotranslateProfiles.includes(t.user.id_str) 224 }); 225 } 226 } 227 return true; 228} 229async function renderListMembers(c) { 230 let [listInfo, listMembers] = await Promise.allSettled([ 231 API.list.get(listId), 232 API.list.getMembers(listId, c) 233 ]).catch(e => { 234 console.error(e); 235 }); 236 if(listMembers.reason) { 237 console.error(listTweets.reason); 238 document.getElementById('loading-box').hidden = false; 239 document.getElementById('loading-box-error').innerHTML = `${LOC.list_not_found.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`; 240 return false; 241 } 242 listInfo = listInfo.value; 243 listMembers = listMembers.value; 244 cursor = listMembers.cursor; 245 listMembers = listMembers.list; 246 if(!cursor || listMembers.length === 0) end = true; 247 renderListData(listInfo); 248 let container = document.getElementById('list-members'); 249 for(let i in listMembers) { 250 let t = listMembers[i]; 251 appendUser(t, container); 252 } 253 return true; 254} 255async function renderListFollowers(c) { 256 let [listInfo, listFollowers] = await Promise.allSettled([ 257 API.list.get(listId), 258 API.list.getFollowers(listId, c) 259 ]).catch(e => { 260 console.error(e); 261 }); 262 if(listFollowers.reason) { 263 console.error(listTweets.reason); 264 document.getElementById('loading-box').hidden = false; 265 document.getElementById('loading-box-error').innerHTML = `${LOC.list_not_found.message}<br><a href="https://twitter.com/home">${LOC.go_homepage.message}</a>`; 266 return false; 267 } 268 listInfo = listInfo.value; 269 listFollowers = listFollowers.value; 270 cursor = listFollowers.cursor; 271 listFollowers = listFollowers.list; 272 if(!cursor || listFollowers.length === 0) end = true; 273 renderListData(listInfo); 274 let container = document.getElementById('list-followers'); 275 for(let i in listFollowers) { 276 let t = listFollowers[i]; 277 let followingElement = document.createElement('div'); 278 followingElement.classList.add('user-item'); 279 followingElement.innerHTML = ` 280 <div style="height:48px"> 281 <a href="https://twitter.com/${t.screen_name}" class="user-item-link"> 282 <img src="${(t.default_profile_image && vars.useOldDefaultProfileImage) ? chrome.runtime.getURL(`images/default_profile_images/default_profile_${Number(t.id_str) % 7}_normal.png`): t.profile_image_url_https}" alt="${t.screen_name}" class="user-item-avatar tweet-avatar" width="48" height="48"> 283 <div class="user-item-text"> 284 <span class="tweet-header-name user-item-name">${escapeHTML(t.name)}</span><br> 285 <span class="tweet-header-handle">@${t.screen_name}</span> 286 </div> 287 </a> 288 </div> 289 <div> 290 <button class="user-item-btn nice-button ${t.following ? 'following' : 'follow'}">${t.following ? LOC.following.message: LOC.follow.message}</button> 291 </div>`; 292 293 let followButton = followingElement.querySelector('.user-item-btn'); 294 followButton.addEventListener('click', async () => { 295 if (followButton.classList.contains('following')) { 296 await API.user.unfollow(t.screen_name); 297 followButton.classList.remove('following'); 298 followButton.classList.add('follow'); 299 followButton.innerText = LOC.follow.message; 300 } else { 301 await API.user.follow(t.screen_name); 302 followButton.classList.remove('follow'); 303 followButton.classList.add('following'); 304 followButton.innerText = LOC.following.message; 305 } 306 }); 307 308 container.appendChild(followingElement); 309 } 310 return true; 311} 312 313async function renderList() { 314 if(subpage === 'tweets') { 315 if(!await renderListTweets(cursor)) return; 316 } else if(subpage === 'members') { 317 if(!await renderListMembers(cursor)) return; 318 } else if(subpage === 'followers') { 319 if(!await renderListFollowers(cursor)) return; 320 } 321 document.getElementById('loading-box').hidden = true; 322 return true; 323} 324 325let lastTweetDate = 0; 326let activeTweet; 327let loadingNewTweets = false; 328let end = false; 329 330setTimeout(async () => { 331 if(!vars) { 332 await loadVars(); 333 } 334 // weird bug 335 if(!document.getElementById('wtf-refresh')) { 336 return setTimeout(() => location.reload(), 500); 337 } 338 try { 339 document.getElementById('wtf-refresh').addEventListener('click', async () => { 340 renderDiscovery(false); 341 }); 342 } catch(e) { 343 setTimeout(() => location.reload(), 500); 344 console.error(e); 345 return; 346 } 347 window.addEventListener("popstate", async () => { 348 cursor = undefined; 349 updateSubpage(); 350 renderList(); 351 }); 352 document.addEventListener('scroll', async () => { 353 // find active tweet by scroll amount 354 if(Date.now() - lastTweetDate > 50) { 355 lastTweetDate = Date.now(); 356 let tweets = Array.from(document.getElementsByClassName('tweet')); 357 358 let scrollPoint = scrollY + innerHeight/2; 359 let newActiveTweet = tweets.find(t => scrollPoint > t.offsetTop && scrollPoint < t.offsetTop + t.offsetHeight); 360 if(!activeTweet || (newActiveTweet && !activeTweet.className.startsWith(newActiveTweet.className))) { 361 if(activeTweet) { 362 activeTweet.classList.remove('tweet-active'); 363 } 364 if(newActiveTweet) newActiveTweet.classList.add('tweet-active'); 365 if(vars.autoplayVideos && !document.getElementsByClassName('modal')[0]) { 366 if(activeTweet) { 367 let video = activeTweet.querySelector('.tweet-media > video[controls]'); 368 if(video) { 369 video.pause(); 370 } 371 } 372 if(newActiveTweet) { 373 let newVideo = newActiveTweet.querySelector('.tweet-media > video[controls]'); 374 let newVideoOverlay = newActiveTweet.querySelector('.tweet-media > .tweet-media-video-overlay'); 375 if(newVideo && !newVideo.ended) { 376 newVideo.play(); 377 } else if(newVideoOverlay && !newVideoOverlay.style.display) { 378 newVideoOverlay.click(); 379 } 380 } 381 } 382 activeTweet = newActiveTweet; 383 } 384 } 385 // loading new tweets 386 if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 500 && !end) { 387 if (loadingNewTweets) return; 388 loadingNewTweets = true; 389 await renderList(); 390 setTimeout(() => { 391 loadingNewTweets = false; 392 }, 250); 393 } 394 }); 395 396 document.addEventListener('clearActiveTweet', () => { 397 if(activeTweet) { 398 activeTweet.classList.remove('tweet-active'); 399 } 400 activeTweet = undefined; 401 }); 402 document.addEventListener('findActiveTweet', () => { 403 let tweets = Array.from(document.getElementsByClassName('tweet')); 404 if(activeTweet) { 405 activeTweet.classList.remove('tweet-active'); 406 } 407 let scrollPoint = scrollY + innerHeight/2; 408 activeTweet = tweets.find(t => scrollPoint > t.offsetTop && scrollPoint < t.offsetTop + t.offsetHeight); 409 if(activeTweet) { 410 activeTweet.classList.add('tweet-active'); 411 } 412 }); 413 414 document.getElementById('list-members-div').addEventListener('click', () => { 415 document.getElementById('ns-members').click(); 416 }); 417 document.getElementById('list-followers-div').addEventListener('click', () => { 418 document.getElementById('ns-followers').click(); 419 }); 420 421 let listSwitches = Array.from(document.getElementsByClassName('list-switch')); 422 listSwitches.forEach(s => { 423 s.addEventListener('click', async () => { 424 let id = s.id.split('-')[1]; 425 switch(id) { 426 case 'tweets': history.pushState({}, null, `/i/lists/${listId}`); break; 427 case 'members': history.pushState({}, null, `/i/lists/${listId}/members`); break; 428 case 'followers': history.pushState({}, null, `/i/lists/${listId}/followers`); break; 429 } 430 // document.getElementById('loading-box').hidden = false; 431 updateSubpage(); 432 cursor = undefined; 433 renderList(); 434 }); 435 }); 436 // Update dates every minute 437 setInterval(() => { 438 let tweetDates = Array.from(document.getElementsByClassName('tweet-time')); 439 let tweetQuoteDates = Array.from(document.getElementsByClassName('tweet-time-quote')); 440 let all = [...tweetDates, ...tweetQuoteDates]; 441 all.forEach(date => { 442 date.innerText = timeElapsed(+date.dataset.timestamp); 443 }); 444 }, 60000); 445 446 // Run 447 updateSubpage(); 448 updateUserData(); 449 renderDiscovery(); 450 renderList(); 451 setInterval(updateUserData, 60000 * 3); 452 setInterval(() => renderDiscovery(false), 60000 * 5); 453}, 50);