├── .github └── FUNDING.yml ├── LICENSE.md ├── Twitch-Previews ├── _locales │ ├── de │ │ └── messages.json │ ├── en │ │ └── messages.json │ ├── es │ │ └── messages.json │ ├── fr │ │ └── messages.json │ ├── ja │ │ └── messages.json │ ├── ko │ │ └── messages.json │ ├── pt_BR │ │ └── messages.json │ └── ru │ │ └── messages.json ├── images │ ├── TP128.png │ ├── TP16.png │ ├── TP24.png │ ├── TP32.png │ ├── TP48.png │ ├── TP64.png │ ├── TP96.png │ ├── close_btn.png │ ├── donate_heart.png │ ├── dragndrop_hint.jpg │ ├── error.png │ ├── expand.png │ ├── fScrnWithChat_custom.png │ ├── fScrnWithChat_default.png │ ├── fScrnWithChat_main.png │ ├── gamepad_active.png │ ├── gamepad_idle.png │ ├── multistream.png │ ├── multistream_chat.png │ ├── multistream_layout.png │ ├── multistream_sidebar.png │ ├── opd_sub_code_info.jpg │ ├── opd_sub_code_info_gift.jpg │ ├── pip.png │ ├── settings.png │ ├── tp_offline.jpg │ ├── tp_sidebar_search.png │ ├── tp_sidebar_search_close.png │ ├── tpt.png │ ├── translate.png │ ├── updatetoast_img.jpg │ └── vidPreviewVolBtn.png ├── main │ ├── cd.css │ ├── cd.js │ ├── tp_i18n.js │ ├── tp_sub.js │ ├── tp_sub_toast.css │ └── tp_sub_toast_i18n.js ├── manifest.json ├── opd │ ├── opd.css │ ├── opd_clips.html │ ├── opd_clips.js │ ├── opd_fb.html │ ├── opd_fb.js │ ├── opd_previews.html │ ├── opd_previews.js │ ├── opd_yt.html │ ├── opd_yt.js │ ├── rec_pb.css │ ├── rec_pb.html │ ├── rec_pb_player.css │ ├── rec_pb_player.html │ ├── sub.css │ ├── sub.html │ └── sub.js └── popups │ └── updatePopup.html ├── build ├── build.py ├── build_all.bat ├── build_chrome.bat ├── build_edge.bat ├── build_firefox.bat ├── build_opera.bat └── create_firefox_dev_proj.bat ├── previews_main.png └── readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: previews_for_ttv 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | **© Mark M ** 2 | 3 | **Previews (For TTV)** 4 | 5 | We don't really need to write anything here but: 6 | 7 | You are free to modify for your own personal use.
8 | You need permission to distribute the code or parts of it.
9 | You can't sell extension/addon or features of the extension/addon. 10 | -------------------------------------------------------------------------------- /Twitch-Previews/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "Live-Vorschauen, wenn Sie mit der Maus über Streams fahren|Eine Reihe von Funktionen zur Verbesserung der Lebensqualität von Twitch", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Übersetzen", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "Live previews when hovering over streams on twitch | A bunch of quality of life improvement features to twitch.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Translate", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "Vistas previas de transmisiones en vivo al pasar el mouse | Un montón de funciones para mejorar la calidad de vida de Twitch.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Traducir", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "Aperçus en direct lors du survol des flux | Un tas de fonctionnalités d'amélioration de la qualité de vie sur Twitch.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Traduire", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "ストリームにカーソルを合わせたときのライブ プレビュー | Twitch の生活の質を向上させる機能の束。", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "翻訳", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/ko/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "마우스를 이용한 방송 미리 보기 | 기타 다양한 트수 생활 보조 기능.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "번역", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "Visualizações da transmissão ao vivo ao passar o mouse | Um monte de recursos de melhoria de qualidade de vida para Twitch.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Traduzir", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "appName": { 3 | "message": "Previews (For TTV)", 4 | "description": "Extension name." 5 | }, 6 | "appDesc": { 7 | "message": "превью в реальном времени при наведении курсора на потоки на Twitch | Набор функций улучшения качества жизни для Twitch.", 8 | "description": "Extension description." 9 | }, 10 | "translateStr": { 11 | "message": "Перевести текст", 12 | "description": "Translate Button text." 13 | } 14 | } -------------------------------------------------------------------------------- /Twitch-Previews/images/TP128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP128.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP16.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP24.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP32.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP48.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP64.png -------------------------------------------------------------------------------- /Twitch-Previews/images/TP96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/TP96.png -------------------------------------------------------------------------------- /Twitch-Previews/images/close_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/close_btn.png -------------------------------------------------------------------------------- /Twitch-Previews/images/donate_heart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/donate_heart.png -------------------------------------------------------------------------------- /Twitch-Previews/images/dragndrop_hint.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/dragndrop_hint.jpg -------------------------------------------------------------------------------- /Twitch-Previews/images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/error.png -------------------------------------------------------------------------------- /Twitch-Previews/images/expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/expand.png -------------------------------------------------------------------------------- /Twitch-Previews/images/fScrnWithChat_custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/fScrnWithChat_custom.png -------------------------------------------------------------------------------- /Twitch-Previews/images/fScrnWithChat_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/fScrnWithChat_default.png -------------------------------------------------------------------------------- /Twitch-Previews/images/fScrnWithChat_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/fScrnWithChat_main.png -------------------------------------------------------------------------------- /Twitch-Previews/images/gamepad_active.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/gamepad_active.png -------------------------------------------------------------------------------- /Twitch-Previews/images/gamepad_idle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/gamepad_idle.png -------------------------------------------------------------------------------- /Twitch-Previews/images/multistream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/multistream.png -------------------------------------------------------------------------------- /Twitch-Previews/images/multistream_chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/multistream_chat.png -------------------------------------------------------------------------------- /Twitch-Previews/images/multistream_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/multistream_layout.png -------------------------------------------------------------------------------- /Twitch-Previews/images/multistream_sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/multistream_sidebar.png -------------------------------------------------------------------------------- /Twitch-Previews/images/opd_sub_code_info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/opd_sub_code_info.jpg -------------------------------------------------------------------------------- /Twitch-Previews/images/opd_sub_code_info_gift.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/opd_sub_code_info_gift.jpg -------------------------------------------------------------------------------- /Twitch-Previews/images/pip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/pip.png -------------------------------------------------------------------------------- /Twitch-Previews/images/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/settings.png -------------------------------------------------------------------------------- /Twitch-Previews/images/tp_offline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/tp_offline.jpg -------------------------------------------------------------------------------- /Twitch-Previews/images/tp_sidebar_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/tp_sidebar_search.png -------------------------------------------------------------------------------- /Twitch-Previews/images/tp_sidebar_search_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/tp_sidebar_search_close.png -------------------------------------------------------------------------------- /Twitch-Previews/images/tpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/tpt.png -------------------------------------------------------------------------------- /Twitch-Previews/images/translate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/translate.png -------------------------------------------------------------------------------- /Twitch-Previews/images/updatetoast_img.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/updatetoast_img.jpg -------------------------------------------------------------------------------- /Twitch-Previews/images/vidPreviewVolBtn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/Twitch-Previews/images/vidPreviewVolBtn.png -------------------------------------------------------------------------------- /Twitch-Previews/main/cd.css: -------------------------------------------------------------------------------- 1 | .tp-player-control { 2 | display: inline-flex; 3 | cursor: pointer; 4 | background-repeat: no-repeat; 5 | background-size: contain; 6 | border-radius: 4px; 7 | } 8 | 9 | .tp-player-control:hover { 10 | background-color: #ffffff33; 11 | } 12 | .tp-player-control:active { 13 | background-color: rgb(38, 38, 38); 14 | } 15 | #tp_clip_download_btn { 16 | justify-content: center; 17 | } -------------------------------------------------------------------------------- /Twitch-Previews/main/cd.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | if (document.getElementById('tp_clip_download_btn')) { 3 | return; 4 | } 5 | setTimeout(function () { 6 | let video = document.querySelector('video'); 7 | if (video && video.src.indexOf('.mp4?') > -1) { 8 | try { 9 | let append_containers = document.querySelectorAll('.player-controls__right-control-group'); 10 | if (append_containers.length) { 11 | let btn_container = document.createElement('div'); 12 | btn_container.id = "tp_clip_download_btn"; 13 | btn_container.classList.add('tp-player-control'); 14 | btn_container.title = "Download Clip"; 15 | 16 | btn_container.style.width = "3rem"; 17 | btn_container.style.height = "3rem"; 18 | btn_container.style.zIndex = "1"; 19 | 20 | btn_container.innerHTML = '' + 21 | ''; 23 | 24 | btn_container.onclick = function (){ 25 | let video_el = document.querySelector('video'); 26 | if (video_el && video_el.src.indexOf('.mp4?') > -1) { 27 | /*let element = document.createElement('a'); 28 | element.setAttribute('href', 'data:video/mp4;mp4,' + encodeURIComponent(video_el.src)); 29 | element.setAttribute('download', document.title); 30 | element.style.display = 'none'; 31 | document.body.appendChild(element); 32 | element.click(); 33 | document.body.removeChild(element);*/ 34 | 35 | let isFirefox = typeof browser !== "undefined"; 36 | let _browser = isFirefox ? browser : chrome; 37 | _browser.runtime.sendMessage({action: "bg_clip_download_btn_click", detail: video_el.src}, function(response) { 38 | 39 | }); 40 | } else { 41 | document.getElementById('tp_clip_download_btn').remove(); 42 | alert('no clip found'); 43 | } 44 | } 45 | append_containers[append_containers.length - 1].firstChild.before(btn_container); 46 | if (append_containers.length > 1) { 47 | append_containers[append_containers.length - 2].style.opacity = '0'; 48 | } 49 | } 50 | } catch (e) { 51 | console.log(e) 52 | } 53 | } 54 | }, 3000); 55 | })() 56 | -------------------------------------------------------------------------------- /Twitch-Previews/main/tp_sub.js: -------------------------------------------------------------------------------- 1 | // (c) Mark M . 2 | 3 | let isFirefox = typeof browser !== "undefined"; 4 | let _browser = isFirefox ? browser : chrome; 5 | const _tp_i18n = await import(_browser.runtime.getURL("main/tp_sub_toast_i18n.js")); 6 | let selected_lang = 'en'; 7 | 8 | _browser.storage.local.get('tp_options', function(result) { 9 | selected_lang = result.tp_options.selected_lang; 10 | }); 11 | 12 | function sendMessageToBG(obj) { 13 | _browser.runtime.sendMessage(obj, function(response) { 14 | 15 | }); 16 | } 17 | 18 | function getRuntimeUrl(path) { 19 | return _browser.runtime.getURL(path); 20 | } 21 | 22 | function geti18nMessage(msgName) { 23 | return _browser.i18n.getMessage(msgName); 24 | } 25 | 26 | function _i18n(name, args) { 27 | let translation = _tp_i18n.i18n[name][selected_lang]; 28 | if (args) { 29 | args.forEach((arg) => { 30 | translation = translation.replace('%s', arg); 31 | }) 32 | } 33 | return translation; 34 | } 35 | 36 | function initDragForSubToast(toast_container) { 37 | dragElement(toast_container); 38 | 39 | function dragElement(elmnt) { 40 | let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; 41 | let drag_el = elmnt.closest('#tp_subscribe_toast_content'); 42 | elmnt.onmousedown = dragMouseDown; 43 | 44 | function dragMouseDown(e) { 45 | e = e || window.event; 46 | e.preventDefault(); 47 | pos3 = e.clientX; 48 | pos4 = e.clientY; 49 | document.onmouseup = closeDragElement; 50 | document.onmousemove = elementDrag; 51 | } 52 | 53 | function elementDrag(e) { 54 | e = e || window.event; 55 | e.preventDefault(); 56 | pos1 = pos3 - e.clientX; 57 | pos2 = pos4 - e.clientY; 58 | pos3 = e.clientX; 59 | pos4 = e.clientY; 60 | drag_el.style.top = (drag_el.offsetTop - pos2) + "px"; 61 | drag_el.style.left = (drag_el.offsetLeft - pos1) + "px"; 62 | } 63 | 64 | function closeDragElement() { 65 | document.onmouseup = null; 66 | document.onmousemove = null; 67 | } 68 | } 69 | } 70 | 71 | export function sub_checkIsSubActive(show_settings_callback) { 72 | return new Promise((resolve, reject) => { 73 | _browser.storage.local.get('tp_user_sub', function(result) { 74 | let isSub = result.tp_user_sub?.isActive; 75 | if (isSub) { 76 | if (result.tp_user_sub.is_gifted) { 77 | let end_date = new Date(result.tp_user_sub.last_payment_time.split('T')[0]); 78 | end_date.setDate(end_date.getDate() + result.tp_user_sub.validation_period); 79 | let diffDays = Math.ceil((new Date(end_date) - new Date()) / (1000 * 60 * 60 * 24)); 80 | if (diffDays < 0) { 81 | resolve(true); // gifted sub ended // true so that the user doesn't see both dialogs 82 | sendMessageToBG({action: 'disable_subscription', detail: true}); 83 | show_gifted_sub_ended_toast(show_settings_callback); 84 | } else { 85 | if ((Date.now() - result.tp_user_sub.last_update_time) / 1000 > 172800) { // 2 days 86 | _browser.runtime.sendMessage({action: "check_permission_previews", detail: true}, function(res) { 87 | if (res.result === 'granted') { 88 | _browser.runtime.sendMessage({action: "validate_gifted_subscription", detail: result.tp_user_sub.ppid}, function(response) { // handles updates in bg 89 | 90 | }); 91 | resolve(true); 92 | } else { 93 | _browser.runtime.sendMessage({action: "show_permission_previews", detail: true}, function(response) { 94 | 95 | }); 96 | resolve(false); 97 | } 98 | }); 99 | } else { 100 | resolve(true); 101 | } 102 | } 103 | } else { 104 | if ((Date.now() - result.tp_user_sub.last_update_time) / 1000 > 172800) { // 2 days 105 | _browser.runtime.sendMessage({action: "check_permission_previews", detail: true}, function(res) { 106 | if (res.result === 'granted') { 107 | _browser.runtime.sendMessage({action: "validate_subscription", detail: result.tp_user_sub.ppid}, function(response) { // handles updates in bg 108 | 109 | }); 110 | resolve(true); 111 | } else { 112 | _browser.runtime.sendMessage({action: "show_permission_previews", detail: true}, function(response) { 113 | 114 | }); 115 | resolve(false); 116 | } 117 | }) 118 | } else { 119 | resolve(true); 120 | } 121 | } 122 | } else { 123 | resolve(false); 124 | } 125 | }); 126 | }) 127 | } 128 | 129 | function checkShouldShowSubToastByFeatureUse(weight = 0.5) { 130 | return new Promise((resolve, reject) => { 131 | _browser.storage.local.get('used_feature_count', function(result) { 132 | if (result.used_feature_count) { 133 | if (result.used_feature_count > 30) { 134 | _browser.storage.local.set({'used_feature_count': 1}, function() {}); 135 | resolve(true); 136 | } else { 137 | let count = result.used_feature_count + weight; 138 | _browser.storage.local.set({'used_feature_count': count}, function() {}); 139 | resolve(false); 140 | } 141 | } else { 142 | _browser.storage.local.set({'used_feature_count': 1}, function() {}); 143 | resolve(false); 144 | } 145 | }) 146 | }) 147 | } 148 | 149 | export function sub_checkShouldShowSubToast(show_settings_callback, weight) { 150 | checkShouldShowSubToastByFeatureUse(weight).then((res)=> { 151 | if (res) { 152 | _browser.storage.local.get('tpInstallTime', function(result) { 153 | if (result.tpInstallTime) { 154 | if ((Date.now() - result.tpInstallTime) / 1000 > 2628288) { // one month 155 | _browser.storage.local.get('lastSeenSubToast', function(result) { 156 | if (result.lastSeenSubToast) { 157 | if ((Date.now() - result.lastSeenSubToast) / 1000 > 86400) { // 24 hours 158 | show_subscribe_toast(show_settings_callback); 159 | } 160 | } else { 161 | show_subscribe_toast(show_settings_callback); 162 | } 163 | }); 164 | } 165 | } 166 | }) 167 | } 168 | }) 169 | } 170 | 171 | 172 | function get_subscribe_toast(show_settings_callback) { 173 | let content = document.createElement('div'); 174 | content.id = 'tp_subscribe_toast_content'; 175 | content.classList.add('tp-subscribe-toast-content'); 176 | content.classList.add('tp-sub-toast-animated'); 177 | 178 | let logo = document.createElement('img'); 179 | logo.src = getRuntimeUrl('images/TP32.png'); 180 | logo.classList.add('tp-subscribe-toast-logo'); 181 | 182 | let title = document.createElement('div'); 183 | title.classList.add('tp-subscribe-toast-title'); 184 | title.innerText = _i18n('app_name'); 185 | 186 | let body = document.createElement('div'); 187 | body.classList.add('subscribe_toast_body'); 188 | 189 | let range_container = document.createElement('div'); 190 | range_container.innerHTML = '\n' + 191 | '\n' + 192 | '\n' + 193 | ' \n' + 194 | ' \n' + 195 | ' \n' + 196 | ' \n' + 197 | ' \n' + 198 | ' \n' + 199 | ' \n' + 200 | ' \n' + 201 | ' \n' + 202 | ''; 203 | 204 | let have_code_btn = document.createElement('div'); 205 | have_code_btn.classList.add('tp-subscribe-toast-btn'); 206 | have_code_btn.style.width = '28.75%'; 207 | have_code_btn.style.borderTopRightRadius = '3px'; 208 | have_code_btn.style.borderBottomLeftRadius = '10px'; 209 | have_code_btn.innerText = _i18n('subscribe_toast_have_code'); 210 | 211 | have_code_btn.onclick = function () { 212 | let sub_payload = {'tp_sub_origin_intent': 'have_code', 'subscribe_price': false}; 213 | _browser.storage.local.set({'sub_payload': sub_payload}, function() { 214 | sendMessageToBG({action: 'open_sub_page', detail: true}) 215 | }); 216 | }; 217 | 218 | let closeBtn = document.createElement('div'); 219 | closeBtn.classList.add('tp-subscribe-toast-btn'); 220 | closeBtn.style.width = '28.75%'; 221 | closeBtn.style.borderTopLeftRadius = '3px'; 222 | closeBtn.style.borderTopRightRadius = '3px'; 223 | closeBtn.innerText = _i18n('subscribe_toast_close'); 224 | 225 | let closeBtn_timer = document.createElement('span'); 226 | closeBtn_timer.innerText = '4'; 227 | closeBtn_timer.classList.add('tp-subscribe-toast-close-btn-timer'); 228 | 229 | closeBtn.appendChild(closeBtn_timer); 230 | 231 | closeBtn.onclick = function () { 232 | content.remove(); 233 | }; 234 | 235 | let subscribe_btn = document.createElement('div'); 236 | subscribe_btn.classList.add('tp-subscribe-toast-btn'); 237 | subscribe_btn.id = 'tp-subscribe-toast-subscribe-btn'; 238 | subscribe_btn.style.width = '42.5%'; 239 | subscribe_btn.style.borderTopLeftRadius = '10px'; 240 | subscribe_btn.style.borderBottomRightRadius = '10px'; 241 | subscribe_btn.innerText = _i18n('subscribe_toast_subscribe'); 242 | 243 | subscribe_btn.onclick = function () { 244 | let convert = {10:'$2.5', 20:'$3', 30:'$4', 40:'$5', 50:'$6', 60:'$7', 70:'$10', 80:'$15', 90:'$20'}; 245 | let sub_payload = {'tp_sub_origin_intent': 'toast_subscribe', 'subscribe_price': convert[range_container.querySelector('#tp_subscribe_toast_range').value]}; 246 | _browser.storage.local.set({'sub_payload': sub_payload}, function() { 247 | sendMessageToBG({action: 'open_sub_page', detail: true}) 248 | }); 249 | }; 250 | 251 | 252 | let top_btns_container = document.createElement('div'); 253 | top_btns_container.classList.add('tp-subscribe-toast-top-btn-container'); 254 | let translate_btn = document.createElement('img'); 255 | translate_btn.classList.add('tp-subscribe-toast-top-btn'); 256 | translate_btn.src = getRuntimeUrl('images/translate.png'); 257 | translate_btn.title = geti18nMessage('translateStr'); 258 | translate_btn.onclick = ()=> sendMessageToBG({action: "subToast_translate_btn_click", detail: 'https://translate.google.com/?sl=auto&tl=auto&text=' + encodeURIComponent(body.innerText) + '&op=translate'}); 259 | 260 | let settings_btn = document.createElement('img'); 261 | settings_btn.classList.add('tp-subscribe-toast-top-btn'); 262 | settings_btn.src = getRuntimeUrl('images/settings.png'); 263 | settings_btn.title = _i18n('subscribe_toast_settings'); 264 | if (show_settings_callback) { 265 | settings_btn.onclick = ()=> show_settings_callback(); 266 | } else { 267 | settings_btn.style.display = 'none'; 268 | } 269 | 270 | if (selected_lang === 'en') { 271 | top_btns_container.appendChild(translate_btn); 272 | } 273 | top_btns_container.appendChild(settings_btn); 274 | 275 | title.prepend(logo); 276 | content.appendChild(title); 277 | content.appendChild(body); 278 | content.appendChild(range_container); 279 | 280 | let bottom_btns_container = document.createElement('div'); 281 | bottom_btns_container.appendChild(have_code_btn); 282 | bottom_btns_container.appendChild(closeBtn); 283 | bottom_btns_container.appendChild(subscribe_btn); 284 | content.appendChild(bottom_btns_container); 285 | 286 | content.appendChild(top_btns_container); 287 | initDragForSubToast(title); 288 | initDragForSubToast(body); 289 | return { 290 | content: content, 291 | body: body, 292 | closeBtn: closeBtn, 293 | closeBtn_timer: closeBtn_timer 294 | } 295 | } 296 | 297 | export function show_subscribe_toast(show_settings_callback) { 298 | let toast = get_subscribe_toast(show_settings_callback); 299 | let content = toast.content; 300 | let body = toast.body; 301 | let closeBtn = toast.closeBtn; 302 | let closeBtn_timer = toast.closeBtn_timer; 303 | 304 | body.innerHTML = _i18n('subscribe_toast_body'); 305 | content.classList.add('tp-sub-toast-pos-1'); 306 | content.classList.add('tp-sub-toast-slideInDown'); 307 | closeBtn_timer.style.display = 'inline-flex'; 308 | closeBtn.classList.add('tp-subscribe-toast-close-btn-disable'); 309 | 310 | document.body.appendChild(content); 311 | 312 | _browser.storage.local.set({'lastSeenSubToast': Date.now()}, function() {}); 313 | 314 | let time_left = 3; 315 | let timer = setInterval(function(){ 316 | if (time_left <= 0) { 317 | clearInterval(timer); 318 | closeBtn_timer.style.display = 'none'; 319 | closeBtn.classList.remove('tp-subscribe-toast-close-btn-disable'); 320 | } else { 321 | closeBtn_timer.innerText = time_left + ''; 322 | } 323 | time_left--; 324 | }, 1000); 325 | } 326 | 327 | function show_gifted_sub_ended_toast(show_settings_callback) { 328 | let toast = get_subscribe_toast(show_settings_callback); 329 | let content = toast.content; 330 | let body = toast.body; 331 | //let closeBtn = toast.closeBtn; 332 | //let closeBtn_timer = toast.closeBtn_timer; 333 | 334 | body.innerText = _i18n('gifted_sub_ended_toast_body',[_i18n('app_name')]); 335 | content.classList.add('tp-sub-toast-pos-2'); 336 | content.classList.add('tp-sub-toast-slideInRight'); 337 | 338 | document.body.appendChild(content); 339 | } 340 | -------------------------------------------------------------------------------- /Twitch-Previews/main/tp_sub_toast.css: -------------------------------------------------------------------------------- 1 | .tp-sub-toast-animated { 2 | -webkit-animation-duration: 200ms; 3 | animation-duration: 200ms; 4 | -webkit-animation-fill-mode: both; 5 | animation-fill-mode: both; 6 | } 7 | 8 | @-webkit-keyframes tp-sub-toast-slideInDown { 9 | from { 10 | -webkit-transform: translate3d(0, -100%, 0); 11 | transform: translate3d(0, -100%, 0); 12 | visibility: visible; 13 | } 14 | 15 | to { 16 | -webkit-transform: translate3d(0, 0, 0); 17 | transform: translate3d(0, 0, 0); 18 | } 19 | } 20 | @keyframes tp-sub-toast-slideInDown { 21 | from { 22 | -webkit-transform: translate3d(0, -100%, 0); 23 | transform: translate3d(0, -100%, 0); 24 | visibility: visible; 25 | } 26 | 27 | to { 28 | -webkit-transform: translate3d(0, 0, 0); 29 | transform: translate3d(0, 0, 0); 30 | } 31 | } 32 | .tp-sub-toast-slideInDown { 33 | -webkit-animation-duration: 100ms; 34 | animation-duration: 100ms; 35 | -webkit-animation-name: tp-sub-toast-slideInDown; 36 | animation-name: tp-sub-toast-slideInDown; 37 | } 38 | 39 | @-webkit-keyframes tp-sub-toast-slideInRight { 40 | from { 41 | -webkit-transform: translate3d(100%, 0, 0); 42 | transform: translate3d(100%, 0, 0); 43 | } 44 | 45 | to { 46 | -webkit-transform: translate3d(0, 0, 0); 47 | transform: translate3d(0, 0, 0); 48 | visibility: visible; 49 | } 50 | } 51 | 52 | @keyframes tp-sub-toast-slideInRight { 53 | from { 54 | -webkit-transform: translate3d(100%, 0, 0); 55 | transform: translate3d(100%, 0, 0); 56 | } 57 | 58 | to { 59 | -webkit-transform: translate3d(0, 0, 0); 60 | transform: translate3d(0, 0, 0); 61 | visibility: visible; 62 | } 63 | } 64 | 65 | .tp-sub-toast-slideInRight { 66 | -webkit-animation-name: tp-sub-toast-slideInRight; 67 | animation-name: tp-sub-toast-slideInRight; 68 | } 69 | 70 | .tp-subscribe-toast-content * { 71 | box-sizing: border-box; 72 | } 73 | 74 | .tp-subscribe-toast-content { 75 | position: fixed; 76 | z-index: 99999999; 77 | width: 450px; 78 | height: max-content; 79 | padding: 0 0 0 0; 80 | font-size: 14px; 81 | text-align: center; 82 | color: white; 83 | background: #9c60ff; 84 | box-shadow: rgba(24, 24, 27, 0.75) 10px 15px 10px -5px; 85 | border-radius: 10px; 86 | line-height: 21px; 87 | } 88 | 89 | .tp-sub-toast-pos-1 { 90 | top: 2%; 91 | left: 16%; 92 | } 93 | 94 | .tp-sub-toast-pos-2 { 95 | bottom: 16px; 96 | right: 16px; 97 | } 98 | 99 | .tp-subscribe-toast-logo { 100 | background-color: rgb(35,35,40); 101 | border-radius: 50%; 102 | padding: 5px; 103 | width: 32px; 104 | height: 32px; 105 | margin: 4px 5px 5px 0; 106 | } 107 | 108 | .tp-subscribe-toast-title { 109 | font-weight: bold; 110 | display: flex; 111 | justify-content: center; 112 | align-items: center; 113 | padding-top: 10px; 114 | font-size: 16px; 115 | cursor: default; 116 | } 117 | 118 | .subscribe_toast_body { 119 | font-weight: normal; 120 | text-align: left; 121 | padding: 10px 16px; 122 | } 123 | 124 | .tp-subscribe-toast-btn { 125 | display: inline-block; 126 | width: 33.3%; 127 | margin-top: 15px; 128 | padding: 5px 0 8px 0; 129 | bottom: 0; 130 | right: 0; 131 | left: 0; 132 | font-weight: bold; 133 | cursor: pointer; 134 | } 135 | 136 | .tp-subscribe-toast-btn:hover { 137 | background-color: rgba(255, 255, 255, 0.4) !important; 138 | } 139 | 140 | .tp-subscribe-toast-btn:active { 141 | background-color: rgba(255, 255, 255, 0.2) !important; 142 | } 143 | 144 | #tp-subscribe-toast-subscribe-btn { 145 | animation: tp-glow 7s infinite linear; 146 | -webkit-animation: tp-glow 7s infinite linear; 147 | animation-delay: 1s; 148 | -webkit-animation-delay: 1s; 149 | background-color: rgba(255,255,255,0.05); 150 | /*box-shadow: -2px -2px 10px 0px rgb(255,255,255,0.7) !important; 151 | background-color: rgb(255,215,0,0.90);*/ 152 | } 153 | 154 | #tp-subscribe-toast-subscribe-btn:before { 155 | content: ''; 156 | position: absolute; 157 | width: 25px; 158 | height: 34px; 159 | background-image: linear-gradient( 160 | 120deg, 161 | rgba(255,255,255, 0) 30%, 162 | rgba(255,255,255, .8), 163 | rgba(255,255,255, 0) 70% 164 | ); 165 | bottom: 0; 166 | right: 166px; 167 | animation: tp-shine 7s infinite linear; 168 | -webkit-animation: tp-shine 7s infinite linear; 169 | animation-delay: 1s; 170 | -webkit-animation-delay: 1s; 171 | opacity: 0; 172 | } 173 | 174 | @keyframes tp-shine { 175 | 0% { 176 | opacity: 0; 177 | right: 166px; 178 | } 179 | 4% { 180 | opacity: 0.7; 181 | } 182 | 8% { 183 | opacity: 0; 184 | right: 0; 185 | } 186 | 100% { 187 | opacity: 0; 188 | right: 0; 189 | } 190 | } 191 | 192 | @keyframes tp-glow { 193 | 0% { 194 | box-shadow: -2px -2px 10px -2px rgba(255,255,255,0); 195 | } 196 | 6% { 197 | box-shadow: -2px -2px 10px -2px rgba(255,255,255,0.7); 198 | } 199 | 10% { 200 | box-shadow: -2px -2px 10px -2px rgba(255,255,255,0.7); 201 | } 202 | 15% { 203 | box-shadow: -2px -2px 10px -2px rgba(255,255,255,0); 204 | } 205 | 100% { 206 | box-shadow: -2px -2px 10px -2px rgba(255,255,255,0); 207 | } 208 | } 209 | 210 | .tp-subscribe-toast-close-btn-disable { 211 | pointer-events: none; 212 | color: darkgrey; 213 | } 214 | 215 | .tp-subscribe-toast-close-btn-timer { 216 | display: none; 217 | justify-content: center; 218 | align-items: center; 219 | position: absolute; 220 | margin-left: 10px; 221 | font-weight: normal; 222 | border: 1px solid white; 223 | border-radius: 50%; 224 | padding: 0 5px; 225 | font-size: 11px; 226 | width: 19px; 227 | height: 19px; 228 | color: white; 229 | } 230 | 231 | .tp-subscribe-toast-top-btn-container { 232 | display: inline-block; 233 | position: absolute; 234 | left: 10px; 235 | top: 10px 236 | } 237 | 238 | .tp-subscribe-toast-top-btn { 239 | width: 20px; 240 | height: 20px; 241 | padding: 3px; 242 | border-radius: 5px; 243 | } 244 | 245 | .tp-subscribe-toast-top-btn:hover { 246 | background-color: rgba(255, 255, 255, 0.4); 247 | cursor: pointer; 248 | color: white; 249 | } 250 | 251 | .tp-subscribe-toast-top-btn:active { 252 | background-color: rgba(255, 255, 255, 0.2); 253 | } 254 | 255 | .tp-subscribe-toast-content datalist { 256 | display: flex; 257 | justify-content: space-between; 258 | color: whitesmoke; 259 | width: 80%; 260 | margin: 0 auto; 261 | } 262 | 263 | .tp-subscribe-toast-content input { 264 | width: 80%; 265 | color: white; 266 | margin-bottom: 13px; 267 | height: 4px; 268 | padding-bottom: 3px; 269 | cursor: pointer; 270 | } -------------------------------------------------------------------------------- /Twitch-Previews/main/tp_sub_toast_i18n.js: -------------------------------------------------------------------------------- 1 | export const i18n = { 2 | app_name: { 3 | 'en': 'Previews (For TTV)', 4 | 'es': 'Previews (For TTV)', 5 | 'de': 'Previews (For TTV)', 6 | 'fr': 'Previews (For TTV)', 7 | 'pt_BR': 'Previews (For TTV)', 8 | 'ko': 'Previews (For TTV)', 9 | 'ja': 'Previews (For TTV)', 10 | 'ru':'Previews (For TTV)' 11 | }, 12 | subscribe_toast_body: { 13 | 'en': 'Previews (For TTV) is made by one developer - Me! :)
Your support is crucial to making new awesome features and maintain a quality experience.
If you use the extension all the time and find it useful to you, please consider subscribing!

* Subscribers don\'t see this message', 14 | 'es': 'Previews (For TTV) está hecha por un desarrollador - ¡Yo! :)
Su apoyo es crucial para crear nuevas características increíbles y mantener una experiencia de calidad.
Si usa la extensión todo el tiempo y la encuentra útil, por favor considere suscribiéndose!

* Los suscriptores no ven este mensaje', 15 | 'de': 'Previews (For TTV) wird von nur einem Entwickler erstellt - Me! :)
Ihre Unterstützung ist entscheidend, um neue tolle Funktionen zu erstellen und ein qualitativ hochwertiges Erlebnis zu erhalten.
Wenn Sie die Erweiterung ständig verwenden und sie für Sie nützlich finden, denken Sie bitte darüber nach abonnieren!

* Abonnenten sehen diese Nachricht nicht', 16 | 'fr': 'Previews (For TTV) est créée par un seul développeur - Moi ! :)
Votre soutien est crucial pour créer de nouvelles fonctionnalités géniales et maintenir une expérience de qualité.
Si vous utilisez l\'extension tout le temps et que vous la trouvez utile, veuillez considérer abonnement !

* Les abonnés ne voient pas ce message', 17 | 'pt_BR': 'Previews (For TTV) é feita por apenas um desenvolvedor - Eu! :)
Seu suporte é crucial para criar novos recursos incríveis e manter uma experiência de qualidade.
Se você usa a extensão o tempo todo e a considera útil, considere assinando!

* Os assinantes não veem esta mensagem', 18 | 'ko': 'Previews (For TTV) 는 한 명의 개발자가 만들었습니다. 바로 저입니다! :)
귀하의 지원은 새롭고 멋진 기능을 만들고 양질의 환경을 유지하는 데 매우 중요합니다.
확장 프로그램을 항상 사용하고 유용하다고 생각한다면 구독을 고려해 보세요!

* 구독자에게는 이 메시지가 표시되지 않습니다.', 19 | 'ja': 'Previews (For TTV) は 1 人の開発者、つまり私によって作成されました。 :)
新しい素晴らしい機能を作成し、質の高いエクスペリエンスを維持するには、あなたのサポートが不可欠です
この拡張機能を常に使用していて便利だと思われる場合は、登録をご検討ください!

* 購読者にはこのメッセージは表示されません', 20 | 'ru':'Previews (For TTV) сделано одним разработчиком - мной! :)
Ваша поддержка имеет решающее значение для создания новых замечательных функций и поддержания качества работы.
Если вы используете расширение постоянно и считаете его полезным для себя, рассмотрите возможность подписка!

* Пользователи с подпиской не видят это сообщение' 21 | }, 22 | gifted_sub_ended_toast_body: { 23 | 'en': 'Your %s gifted subscription has ended.\nSubscribe or redeem a code to continue your subscription.', 24 | 'es': 'Su suscripción de regalo de %s ha finalizado.\nSuscríbete o canjea un código para continuar con tu suscripción.', 25 | 'de': 'Ihr geschenktes Abonnement von %s ist abgelaufen.\nAbonnieren Sie oder lösen Sie einen Code ein, um Ihr Abonnement fortzusetzen.', 26 | 'fr': 'Votre abonnement gratuit %s est terminé.\nAbonnez-vous ou utilisez un code pour continuer votre abonnement.', 27 | 'pt_BR': 'Sua assinatura de presente %s terminou.\nAssine ou resgate um código para continuar sua assinatura.', 28 | 'ko': '귀하의 %s 선물 구독이 종료되었습니다.\n구독을 계속하려면 코드를 구독하거나 교환하세요.', 29 | 'ja': '%s ギフト サブスクリプションは終了しました。\nサブスクリプションを継続するには、サブスクライブするか、コードを引き換えてください。', 30 | 'ru':'Ваша подарочная подписка %s закончилась.\nПодпишитесь или активируйте код, чтобы продолжить подписку.' 31 | }, 32 | subscribe_toast_have_code: { 33 | 'en': 'Redeem Code', 34 | 'es': 'Canjear código', 35 | 'de': 'Code einlösen', 36 | 'fr': 'Utiliser le code', 37 | 'pt_BR': 'Resgatar código', 38 | 'ko': '코드를 사용', 39 | 'ja': 'コードを引き換える', 40 | 'ru':'Активировать код' 41 | }, 42 | subscribe_toast_close: { 43 | 'en': 'Not now', 44 | 'es': 'no ahora', 45 | 'de': 'Nicht jetzt', 46 | 'fr': 'Fermer', 47 | 'pt_BR': 'Agora não', 48 | 'ko': '닫기', 49 | 'ja': '今はやめろ', 50 | 'ru':'Не сейчас' 51 | }, 52 | subscribe_toast_subscribe: { 53 | 'en': 'Subscribe', 54 | 'es': 'Suscribir', 55 | 'de': 'Abonnieren', 56 | 'fr': 'S\'abonner', 57 | 'pt_BR': 'Se inscrever', 58 | 'ko': '구독하다', 59 | 'ja': '申し込む', 60 | 'ru':'Подписывайся' 61 | }, 62 | subscribe_toast_settings: { 63 | 'en': 'Settings', 64 | 'es': 'Ajustes', 65 | 'de': 'Einstellungen', 66 | 'fr': 'Réglages', 67 | 'pt_BR': 'Configurações', 68 | 'ko': '세팅', 69 | 'ja': '設定', 70 | 'ru':'Настройки' 71 | }, 72 | } -------------------------------------------------------------------------------- /Twitch-Previews/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "__MSG_appName__", 4 | "description": "__MSG_appDesc__", 5 | "author": "Mark M ", 6 | "default_locale": "en", 7 | "version": "6.0", 8 | "permissions": ["storage"], 9 | "optional_permissions": ["https://previews-app.com/*", "https://clips.twitch.tv/*", "https://www.youtube.com/*", "https://*.facebook.com/*"], 10 | "background": { 11 | "scripts": ["main/background.js"], 12 | "persistent": false 13 | }, 14 | "content_scripts": [ 15 | { 16 | "matches": ["https://www.twitch.tv/*"], 17 | "css": ["main/css.css","main/tp_sub_toast.css", "main/settings.css", "main/APS_settings.css"], 18 | "js": ["main/core.js"], 19 | "run_at": "document_end", 20 | "all_frames": true 21 | } 22 | ], 23 | "icons": { 24 | "16": "images/TP16.png", 25 | "24": "images/TP24.png", 26 | "32": "images/TP32.png", 27 | "48": "images/TP48.png", 28 | "64": "images/TP64.png", 29 | "128": "images/TP128.png" 30 | }, 31 | "browser_action": { 32 | "default_icon": "images/TP48.png", 33 | "default_title": "Previews (For TTV)" 34 | }, 35 | "web_accessible_resources": ["images/*", "main/settings.html", "main/APS_settings.html", "main/tp_i18n.js", "main/tp_sub.js", "main/tp_sub_toast_i18n.js"], 36 | "content_security_policy": "script-src 'self' https://previews-app.com; object-src 'self'" 37 | } 38 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 15vh; 3 | background-color: rgb(24, 24, 27); 4 | text-align: center; 5 | color: whitesmoke; 6 | font-family: Arial, Helvetica, sans-serif; 7 | font-weight: bold; 8 | } 9 | #tp_img { 10 | margin-bottom: -10px; 11 | } 12 | #tp_ext_name { 13 | font-size: 25px; 14 | } 15 | #tp_ext_desc { 16 | margin-top: 35px; 17 | font-size: 14px; 18 | } 19 | .tp-btn { 20 | width: 135px; 21 | height: 25px; 22 | display: inline-flex; 23 | justify-content: center; 24 | padding: 5px 25px; 25 | margin: 30px 10px 0 10px; 26 | align-items: center; 27 | background-color: #ffffff26; 28 | cursor: pointer; 29 | font-weight: bold; 30 | border: 1px solid grey; 31 | border-radius: 10px; 32 | transition: all 0.2s; 33 | } 34 | .tp-btn:hover { 35 | background-color: rgba(255,255,255,0.4); 36 | } 37 | .tp-btn:active { 38 | background-color: rgba(255,255,255,0.2); 39 | } 40 | #tp_allow_permissions_btn { 41 | background-color: #9147ff; 42 | } 43 | #tp_allow_permissions_btn:hover { 44 | background-color: #9c60ff; 45 | } -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_clips.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 |
To enable the Clip Downloader, the extension needs permission to run on "clips.twitch.tv"
12 |
Allow
13 |
Cancel
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_clips.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | 3 | let isFirefox = typeof browser !== "undefined"; 4 | let _browser = isFirefox ? browser : chrome; 5 | let options = {}; 6 | const _tp_i18n = await import(_browser.runtime.getURL("./main/tp_i18n.js")).then((res) => { 7 | _browser.storage.local.get('tp_options', function(result) { 8 | options = result.tp_options; 9 | let items = document.querySelectorAll('[tp_i18n]'); 10 | items.forEach((item) => { 11 | item.innerText = res.i18n[item.attributes.tp_i18n.value][options.selected_lang]; 12 | }) 13 | }); 14 | }); 15 | 16 | function closeTab() { 17 | parent.close(); 18 | window.close(); 19 | this.close(); 20 | } 21 | 22 | document.getElementById('tp_allow_permissions_btn').addEventListener('click', function (e) { 23 | _browser.permissions.request({ 24 | origins: ['https://clips.twitch.tv/*'] 25 | }, (granted) => { 26 | if (granted) { 27 | _browser.runtime.sendMessage({action:'sendMessageToTabs', detail: "tp_enable_clip_downloader"}, function(response) {}); 28 | closeTab(); 29 | } else { 30 | console.log("denied"); 31 | } 32 | }); 33 | }) 34 | 35 | document.getElementById('tp_cancel_btn').addEventListener('click', function (e) { 36 | closeTab(); 37 | }) 38 | 39 | } 40 | main().then(); -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_fb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 | 12 |
To enable Facebook Gaming Channels, the extension needs permission to fetch the streams from Facebook
13 |
Allow
14 |
Cancel
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_fb.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | 3 | let isFirefox = typeof browser !== "undefined"; 4 | let _browser = isFirefox ? browser : chrome; 5 | let options = {}; 6 | let isNewPermissions = false; 7 | const _tp_i18n = await import(_browser.runtime.getURL("./main/tp_i18n.js")).then((res) => { 8 | _browser.storage.local.get('tp_options', function(result) { 9 | options = result.tp_options; 10 | let items = document.querySelectorAll('[tp_i18n]'); 11 | items.forEach((item) => { 12 | item.innerText = res.i18n[item.attributes.tp_i18n.value][options.selected_lang]; 13 | }) 14 | }); 15 | }); 16 | 17 | function closeTab() { 18 | parent.close(); 19 | window.close(); 20 | this.close(); 21 | } 22 | 23 | document.getElementById('tp_allow_permissions_btn').addEventListener('click', function (e) { 24 | _browser.permissions.request({ 25 | origins: ['https://*.facebook.com/*'] 26 | }, (granted) => { 27 | if (granted) { 28 | if (isNewPermissions) { 29 | _browser.runtime.sendMessage({action:'tp_clear_FBsidebar_cached_streams', detail: ""}, function(response) { 30 | _browser.runtime.sendMessage({action:'sendMessageToTabs', detail: "tp_enable_FBsidebar_new_permissions"}, function(response) {}); 31 | closeTab(); 32 | }); 33 | } else { 34 | _browser.runtime.sendMessage({action:'sendEnableFBMessageToTabs', detail: "tp_enable_FBsidebar"}, function(response) {}); 35 | closeTab(); 36 | } 37 | } else { 38 | console.log("denied"); 39 | } 40 | }); 41 | }) 42 | 43 | document.getElementById('tp_cancel_btn').addEventListener('click', function (e) { 44 | closeTab(); 45 | }) 46 | 47 | _browser.storage.local.get('FB_request_new_permission', function(result) { 48 | if (result.FB_request_new_permission) { 49 | _browser.storage.local.set({'FB_request_new_permission': false}, function() {}); 50 | isNewPermissions = true; 51 | //document.getElementById('tp_new_permissions_text').style.display = 'block'; 52 | } 53 | }); 54 | 55 | } 56 | main().then(); -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_previews.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 |
Looks like the extension doesn't have permission to communicate with our validation server (previews-app.com). 12 | Allow permissions to continue using your subscription.
13 |
Allow
14 |
Cancel
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_previews.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | 3 | let isFirefox = typeof browser !== "undefined"; 4 | let _browser = isFirefox ? browser : chrome; 5 | let options = {}; 6 | const _tp_i18n = await import(_browser.runtime.getURL("./main/tp_i18n.js")).then((res) => { 7 | _browser.storage.local.get('tp_options', function(result) { 8 | options = result.tp_options; 9 | let items = document.querySelectorAll('[tp_i18n]'); 10 | items.forEach((item) => { 11 | item.innerText = res.i18n[item.attributes.tp_i18n.value][options.selected_lang]; 12 | }) 13 | }); 14 | }); 15 | 16 | function closeTab() { 17 | parent.close(); 18 | window.close(); 19 | this.close(); 20 | } 21 | 22 | document.getElementById('tp_allow_permissions_btn').addEventListener('click', function (e) { 23 | _browser.permissions.request({ 24 | origins: ['https://previews-app.com/*'] 25 | }, (granted) => { 26 | if (granted) { 27 | _browser.storage.local.get('tp_user_sub', function(result) { 28 | if (result.tp_user_sub) { 29 | let ppid = result.tp_user_sub.ppid; 30 | let action = ppid.indexOf('-') === -1 ? 'validate_gifted_subscription':'validate_subscription'; 31 | _browser.runtime.sendMessage({action: action, detail: ppid}, function(response) { // handles updates in bg 32 | 33 | }); 34 | } 35 | closeTab(); 36 | }); 37 | } else { 38 | console.log("denied"); 39 | } 40 | }); 41 | }) 42 | 43 | document.getElementById('tp_cancel_btn').addEventListener('click', function (e) { 44 | closeTab(); 45 | }) 46 | } 47 | main().then(); -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_yt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 |
To enable Sidebar YouTube Channels, the extension needs permission to fetch the streams from YouTube
12 |
Allow
13 |
Cancel
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/opd_yt.js: -------------------------------------------------------------------------------- 1 | async function main() { 2 | 3 | let isFirefox = typeof browser !== "undefined"; 4 | let _browser = isFirefox ? browser : chrome; 5 | let options = {}; 6 | const _tp_i18n = await import(_browser.runtime.getURL("./main/tp_i18n.js")).then((res) => { 7 | _browser.storage.local.get('tp_options', function(result) { 8 | options = result.tp_options; 9 | let items = document.querySelectorAll('[tp_i18n]'); 10 | items.forEach((item) => { 11 | item.innerText = res.i18n[item.attributes.tp_i18n.value][options.selected_lang]; 12 | }) 13 | }); 14 | }); 15 | 16 | function closeTab() { 17 | parent.close(); 18 | window.close(); 19 | this.close(); 20 | } 21 | 22 | document.getElementById('tp_allow_permissions_btn').addEventListener('click', function (e) { 23 | _browser.permissions.request({ 24 | origins: ['https://www.youtube.com/*'] 25 | }, (granted) => { 26 | if (granted) { 27 | _browser.runtime.sendMessage({action:'sendMessageToTabs', detail: "tp_enable_YTsidebar"}, function(response) {}); 28 | closeTab(); 29 | } else { 30 | console.log("denied"); 31 | } 32 | }); 33 | }) 34 | 35 | document.getElementById('tp_cancel_btn').addEventListener('click', function (e) { 36 | closeTab(); 37 | }) 38 | } 39 | main().then(); -------------------------------------------------------------------------------- /Twitch-Previews/opd/rec_pb.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 3vh; 3 | background-color: rgb(24, 24, 27); 4 | text-align: center; 5 | color: whitesmoke; 6 | font-family: Arial, Helvetica, sans-serif; 7 | font-weight: bold; 8 | } 9 | #tp_img { 10 | margin-bottom: -10px; 11 | } 12 | #tp_ext_name { 13 | font-size: 25px; 14 | } 15 | #tp_ext_desc { 16 | margin-top: 35px; 17 | } 18 | .tp-btn { 19 | display: inline-block; 20 | width: 170px; 21 | border: 1px solid grey; 22 | transition: all 0.2s; 23 | cursor: pointer; 24 | padding: 15px 25px; 25 | border-radius: 10px; 26 | margin: 30px 10px 0 10px; 27 | } 28 | .tp-btn:hover { 29 | background-color: rgba(255,255,255,0.4); 30 | } 31 | .tp-btn:active { 32 | background-color: rgba(255,255,255,0.2); 33 | } 34 | #vid { 35 | width: 50%; 36 | height: auto; 37 | margin-top: 35px; 38 | outline: none; 39 | border: none; 40 | } -------------------------------------------------------------------------------- /Twitch-Previews/opd/rec_pb.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 | 12 |
Note: due to a browser issue, recordings are saved without metadata like time duration, to play the recordings properly - open them in a new tab (drag the saved video to the browser).
13 |
Download Recording
14 |
Recordings Player
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/rec_pb_player.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: rgb(24, 24, 27); 3 | text-align: center; 4 | color: whitesmoke; 5 | font-family: Arial, Helvetica, sans-serif; 6 | font-weight: bold; 7 | } 8 | #tp_img { 9 | margin-bottom: -10px; 10 | } 11 | #tp_ext_name { 12 | font-size: 25px; 13 | } 14 | #tp_ext_desc { 15 | margin-top: 35px; 16 | } 17 | .tp-btn { 18 | display: block; 19 | width: 170px; 20 | border: 1px solid grey; 21 | transition: all 0.2s; 22 | cursor: pointer; 23 | padding: 15px 25px; 24 | border-radius: 10px; 25 | margin: 35px auto; 26 | } 27 | .tp-btn:hover { 28 | background-color: rgba(255,255,255,0.4); 29 | } 30 | .tp-btn:active { 31 | background-color: rgba(255,255,255,0.2); 32 | } 33 | #vid { 34 | width: 60%; 35 | height: auto; 36 | margin-top: 35px; 37 | outline: none; 38 | border: none; 39 | } 40 | #tp_select_file_input { 41 | display: none; 42 | } 43 | 44 | #tp_loading { 45 | display: none; 46 | font-size: 20px; 47 | position: absolute; 48 | left: 20%; 49 | top: 10%; 50 | } -------------------------------------------------------------------------------- /Twitch-Previews/opd/rec_pb_player.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 | 11 |
Previews (For TTV)
12 |
Loading....
13 | 14 |
Select File
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/sub.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 2vh; 3 | background-color: rgb(24, 24, 27); 4 | text-align: center; 5 | color: whitesmoke; 6 | font-family: Arial, Helvetica, sans-serif; 7 | font-weight: bold; 8 | } 9 | #tp_img { 10 | margin-bottom: -10px; 11 | } 12 | #tp_ext_name { 13 | font-size: 25px; 14 | } 15 | .sub-top-note { 16 | font-size: 17px; 17 | margin-top: 25px; 18 | } 19 | .tp_ext_desc { 20 | margin-top: 35px; 21 | font-size: 14px; 22 | } 23 | .tp-btn { 24 | width: 135px; 25 | height: 25px; 26 | display: inline-flex; 27 | justify-content: center; 28 | padding: 5px 25px; 29 | margin: 30px 10px 0 10px; 30 | align-items: center; 31 | background-color: #ffffff26; 32 | cursor: pointer; 33 | font-weight: bold; 34 | border: 1px solid grey; 35 | border-radius: 10px; 36 | transition: all 0.2s; 37 | } 38 | .tp-btn:hover { 39 | background-color: rgba(255,255,255,0.4); 40 | } 41 | .tp-btn:active { 42 | background-color: rgba(255,255,255,0.2); 43 | } 44 | .tp_allow_permissions_btn { 45 | background-color: #9147ff; 46 | } 47 | .tp_allow_permissions_btn:hover { 48 | background-color: #9c60ff; 49 | } 50 | .sub-section { 51 | color: darkgray; 52 | transform: scale(0.8); 53 | transition: transform 0.5s, box-shadow 0.5s; 54 | width: 50%; 55 | margin: 30px auto; 56 | border-radius: 10px; 57 | padding: 20px; 58 | } 59 | .sub-section .tp-btn{ 60 | pointer-events: none; 61 | } 62 | .tp-sub-section-highlighted { 63 | color: whitesmoke !important; 64 | pointer-events: all !important; 65 | transform: scale(1) !important; 66 | box-shadow: 0 0 10px 0px white; 67 | } 68 | .tp-sub-section-highlighted .tp-btn, .tp-sub-section-highlighted .opd_sub_code_info { 69 | pointer-events: all !important; 70 | } 71 | .sub-section-title { 72 | font-size: 24px; 73 | font-weight: bold; 74 | text-align: left; 75 | } 76 | #tp_subscribe_btn { 77 | margin-top: 0px; 78 | } 79 | table select { 80 | color: white; 81 | background-color: rgb(24, 24, 27); 82 | border-radius: 5px; 83 | } 84 | #tp_validate_input { 85 | margin-top: 30px; 86 | color: white; 87 | background-color: rgb(24, 24, 27); 88 | border-radius: 5px; 89 | border: 1px solid grey; 90 | height: 25px; 91 | text-align: center; 92 | } 93 | #tp_discord_input { 94 | margin-top: 15px; 95 | color: white; 96 | background-color: rgb(24, 24, 27); 97 | border-radius: 5px; 98 | border: 1px solid grey; 99 | height: 25px; 100 | text-align: center; 101 | } 102 | #tp_validate_btn { 103 | margin-top: 15px; 104 | } 105 | 106 | .tp-sub-section-number { 107 | border: 2px solid white; 108 | border-radius: 50%; 109 | padding: 0 8px; 110 | background-color: dimgrey; 111 | } 112 | 113 | #validation_error_text_el { 114 | color: red; 115 | } 116 | 117 | #discord_error_text_el { 118 | color: red; 119 | margin-top: 10px; 120 | } 121 | 122 | #opd_sub_discord_info_text { 123 | color: grey; 124 | font-size: 10px; 125 | margin-top: 10px; 126 | } 127 | 128 | #discord_body { 129 | border-bottom: 1px solid darkgray; 130 | padding-bottom: 40px; 131 | } 132 | #discord_checkmark { 133 | display: none; 134 | margin-top: 30px; 135 | } 136 | 137 | #discord_checkmark > svg { 138 | width: 80px; 139 | height: 80px; 140 | color: limegreen; 141 | } 142 | 143 | #opd_sub_thanks_text { 144 | margin-top: 30px; 145 | font-size: 48px; 146 | color: limegreen; 147 | } 148 | 149 | #opd_sub_discord_msg a { 150 | color: cornflowerblue; 151 | } 152 | 153 | #opd_sub_discord_msg a:hover { 154 | color: #88b2f3; 155 | } 156 | 157 | .tp-action-loading-spinner { 158 | display: none; 159 | margin: 15px auto 0; 160 | width: 20px; 161 | height: 20px; 162 | border: 3px solid rgb(24, 24, 27); 163 | border-top: 3px solid #3498db; 164 | border-radius: 50%; 165 | animation: tp-validation-loading-spin 1s linear infinite; 166 | } 167 | 168 | @keyframes tp-validation-loading-spin { 169 | 0% { transform: rotate(0deg); } 170 | 100% { transform: rotate(360deg); } 171 | } 172 | 173 | .opd_sub_code_info { 174 | cursor: default; 175 | font-weight: normal; 176 | } 177 | 178 | .opd_sub_code_info { 179 | pointer-events: none; 180 | } 181 | 182 | #opd_sub_code_info, #opd_sub_gift_a_sub_code_info { 183 | display: none; 184 | position: absolute; 185 | width: 440px; 186 | height: 400px; 187 | top: -320px; 188 | right: -65px; 189 | border-radius: 20px; 190 | } 191 | 192 | .opd_sub_code_info:hover #opd_sub_code_info,.opd_sub_code_info:hover #opd_sub_gift_a_sub_code_info { 193 | display: block; 194 | } 195 | #tp_manage_unsub_btn { 196 | margin-top: 35px; 197 | position: absolute; 198 | left: 0; 199 | bottom: 8px; 200 | border: none; 201 | width: auto; 202 | height: auto; 203 | background-color: transparent; 204 | padding: 2px; 205 | color: grey; 206 | } 207 | #opd_sub_have_code_btn { 208 | display: inline-flex; 209 | position: absolute; 210 | top: 16px; 211 | left: 16px; 212 | } 213 | #opd_sub_gift_a_sub_btn { 214 | display: inline-flex; 215 | position: absolute; 216 | top: 71px; 217 | left: 16px; 218 | } 219 | #opd_sub_unsub_instructions { 220 | text-align: left; 221 | margin-top: 30px; 222 | font-size: 15px; 223 | font-weight: normal; 224 | } 225 | .tp_gift_a_sub_plan_btn { 226 | height: 27px; 227 | } 228 | #sub_manage_subscription_details_container { 229 | text-align: left; 230 | font-size: 14px; 231 | margin-top: 25px; 232 | } 233 | 234 | #opd_redeem_gifted_sub_bottom_note { 235 | transition: color 1s; 236 | } 237 | 238 | .opd_sub_gift_a_sub_bottom_note { 239 | width: 50%; 240 | margin: -15px auto; 241 | text-align: left; 242 | color: grey; 243 | font-size: 11px; 244 | } 245 | 246 | .tp-contact-us-btn { 247 | display: inline-flex; 248 | margin: 0; 249 | position: fixed; 250 | bottom: 16px; 251 | right: 16px; 252 | } 253 | 254 | .animated { 255 | -webkit-animation-duration: 1000ms; 256 | animation-duration: 1000ms; 257 | -webkit-animation-fill-mode: both; 258 | animation-fill-mode: both; 259 | } 260 | 261 | @-webkit-keyframes bounce { 262 | from, 263 | 20%, 264 | 53%, 265 | to { 266 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 267 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 268 | -webkit-transform: translate3d(0, 0, 0); 269 | transform: translate3d(0, 0, 0); 270 | } 271 | 272 | 40%, 273 | 43% { 274 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 275 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 276 | -webkit-transform: translate3d(0, -30px, 0) scaleY(1.1); 277 | transform: translate3d(0, -30px, 0) scaleY(1.1); 278 | } 279 | 280 | 70% { 281 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 282 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 283 | -webkit-transform: translate3d(0, -15px, 0) scaleY(1.05); 284 | transform: translate3d(0, -15px, 0) scaleY(1.05); 285 | } 286 | 287 | 80% { 288 | -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 289 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 290 | -webkit-transform: translate3d(0, 0, 0) scaleY(0.95); 291 | transform: translate3d(0, 0, 0) scaleY(0.95); 292 | } 293 | 294 | 90% { 295 | -webkit-transform: translate3d(0, -4px, 0) scaleY(1.02); 296 | transform: translate3d(0, -4px, 0) scaleY(1.02); 297 | } 298 | } 299 | @keyframes bounce { 300 | from, 301 | 20%, 302 | 53%, 303 | to { 304 | -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 305 | animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 306 | -webkit-transform: translate3d(0, 0, 0); 307 | transform: translate3d(0, 0, 0); 308 | } 309 | 310 | 40%, 311 | 43% { 312 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 313 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 314 | -webkit-transform: translate3d(0, -30px, 0) scaleY(1.1); 315 | transform: translate3d(0, -30px, 0) scaleY(1.1); 316 | } 317 | 318 | 70% { 319 | -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 320 | animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); 321 | -webkit-transform: translate3d(0, -15px, 0) scaleY(1.05); 322 | transform: translate3d(0, -15px, 0) scaleY(1.05); 323 | } 324 | 325 | 80% { 326 | -webkit-transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 327 | transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); 328 | -webkit-transform: translate3d(0, 0, 0) scaleY(0.95); 329 | transform: translate3d(0, 0, 0) scaleY(0.95); 330 | } 331 | 332 | 90% { 333 | -webkit-transform: translate3d(0, -4px, 0) scaleY(1.02); 334 | transform: translate3d(0, -4px, 0) scaleY(1.02); 335 | } 336 | } 337 | .bounce { 338 | -webkit-animation-name: bounce; 339 | animation-name: bounce; 340 | -webkit-transform-origin: center bottom; 341 | transform-origin: center bottom; 342 | } 343 | 344 | @-webkit-keyframes fadeIn { 345 | from { 346 | opacity: 0; 347 | } 348 | 349 | to { 350 | opacity: 1; 351 | } 352 | } 353 | @keyframes fadeIn { 354 | from { 355 | opacity: 0; 356 | } 357 | 358 | to { 359 | opacity: 1; 360 | } 361 | } 362 | .fadeIn { 363 | -webkit-animation-duration: 100ms; 364 | animation-duration: 100ms; 365 | -webkit-animation-name: fadeIn; 366 | animation-name: fadeIn; 367 | } -------------------------------------------------------------------------------- /Twitch-Previews/opd/sub.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 | 10 |
Previews (For TTV)
11 |
12 |
Thank you.. for...... 5 subscribers
13 | 14 |
15 |
1 Allow Permissions
16 |
Allow permission for the extension to communicate with our validation server (previews-app.com)
17 |
Allow
18 |
19 | 20 |
21 |
2 Subscribe Via PayPal Or Credit-Card
22 |
Subscribe Via PayPal Or Credit-Card
23 |
24 | 25 | 26 | 27 | 39 |
Select Custom Subscription Price
40 | 41 | 42 | 43 |
44 |
Subscribe
45 |
46 | 47 |
48 |
3 Enter Code
49 |
50 |
Enter the "Profile id" code on the receipt you received from PayPal
51 | 52 |
53 |
Validate
54 |
55 |
56 |
57 |
* The gifted subscription period will start once payment is completed.
58 | 59 | 60 |
Redeem Code
61 |
Gift a Sub
62 | 63 |
64 | 65 |
66 |
Gift a Sub
67 | 68 |
69 |
1 Subscribe Via PayPal Or Credit-Card
70 | 73 |
74 |
75 | 76 | 77 | 78 | 85 |
Options
86 | 87 | 88 | 89 |
90 | 91 |
92 |
93 |
1 Month
94 |
$2.50 USD
95 |
96 |
97 |
98 |
99 |
2 Months
100 |
$4.50 USD
101 |
102 |
103 |
104 |
105 |
3 Months
106 |
$7.00 USD
107 |
108 |
109 |
110 |
111 |
6 Months
112 |
$13.00 USD
113 |
114 |
115 |
116 |
117 |
12 Months
118 |
$24.00 USD
119 |
120 |
121 |
122 | 123 |
124 | 125 |
126 |
2 Enter Code
127 | 128 |
Send your friend the "Transaction ID" code located in the email receipt you received from PayPal
129 |
Done
130 |
131 |
* The gifted subscription period will start once payment is completed.
132 | 133 |
134 | 135 |
136 |
137 |
Thank You!
138 | 139 |
140 |
141 |
(Optional) Add your discord id to get a special subscriber role on our discord!
142 | 143 |
144 |
Send
145 |
146 |
147 |
* It may take up to 24 hours for the role to apply
148 |
149 |
150 |
151 | 152 | 153 |
154 | 155 |
156 | 157 |
Close Tab
158 |
159 | 160 |
161 | 162 |
163 |
164 | 165 |
166 | 170 | 171 | Manage Subscription
172 |
Manage Subscription Manage Subscription Manage Subscription Manage Subscription
173 |
174 |
175 | 176 |
177 |
178 |
179 |
Ends
180 |
181 |
182 | 183 |
Redeem Code
184 |
Subscribe
185 |
Gift A Sub
186 | 187 | 188 |
cancel subscription
189 |
190 | 191 |
192 | 193 |
194 |
195 |
Follow these instructions to cancel your subscription
196 |
197 |
198 | 205 |
206 |
207 |
208 | 209 |
210 | 211 | 213 | 214 | Contact 215 |
216 | 217 | 218 | 219 | -------------------------------------------------------------------------------- /Twitch-Previews/opd/sub.js: -------------------------------------------------------------------------------- 1 | // (c) Mark M . 2 | 3 | async function main() { 4 | 5 | let isFirefox = typeof browser !== "undefined"; 6 | let _browser = isFirefox ? browser : chrome; 7 | let options = {}; 8 | let _i18n; 9 | const _tp_i18n = await import(_browser.runtime.getURL("./main/tp_i18n.js")).then((res) => { 10 | _browser.storage.local.get('tp_options', function(result) { 11 | options = result.tp_options; 12 | let items = document.querySelectorAll('[tp_i18n]'); 13 | items.forEach((item) => { 14 | item.innerText = res.i18n[item.attributes.tp_i18n.value][options.selected_lang]; 15 | }) 16 | let items_html = document.querySelectorAll('[tp_i18n_html]'); 17 | items_html.forEach((item) => { 18 | item.innerHTML = _i18n(item.attributes.tp_i18n_html.value); 19 | }) 20 | }); 21 | _i18n = function(name, args) { 22 | let translation = res.i18n[name][options.selected_lang]; 23 | if (args) { 24 | args.forEach((arg) => { 25 | translation = translation.replace('%s', arg); 26 | }) 27 | } 28 | return translation; 29 | } 30 | }); 31 | 32 | let server_origins = 'https://previews-app.com/*'; 33 | let redeem_code_intent = false; 34 | let sections = document.querySelectorAll('.sub-section'); 35 | document.querySelector('#opd_sub_code_info').src = _browser.runtime.getURL('../images/opd_sub_code_info.jpg'); 36 | document.querySelector('#opd_sub_gift_a_sub_code_info').src = _browser.runtime.getURL('../images/opd_sub_code_info_gift.jpg'); 37 | 38 | document.getElementById('tp_allow_permissions_btn').addEventListener('click', function (e) { 39 | _browser.permissions.request({ 40 | origins: [server_origins] 41 | }, (granted) => { 42 | if (granted) { 43 | flow_permission_allowed(); 44 | } else { 45 | console.log("denied"); 46 | } 47 | }); 48 | }) 49 | 50 | let isFetchingFromServer = false; 51 | document.getElementById('tp_subscribe_btn').addEventListener('click', function (e) { 52 | document.getElementById('tp_subscribe_paypal_btn').click(); 53 | setTimeout(function () { 54 | setSectionNumberCompleted(1); 55 | highlightSection('sub_section_sub_phase_3'); 56 | sections[2].scrollIntoView({behavior: "smooth", block: "start"}); 57 | }, 100); 58 | }); 59 | 60 | 61 | let loading_spinner = document.querySelector('#tp_validation_loading_spinner'); 62 | let validate_btn = document.querySelector('#tp_validate_btn'); 63 | validate_btn.addEventListener('click', function (e) { 64 | if (isFetchingFromServer) { 65 | return; 66 | } 67 | let val_input = document.querySelector('#tp_validate_input'); 68 | let val = val_input.value; 69 | if (!val || val.length < 5) { 70 | return; 71 | } 72 | val = val.trim(); 73 | val_input.value = val; 74 | let action = 'validate_subscription'; 75 | if (redeem_code_intent) { 76 | if (val.indexOf('-') === -1) { 77 | action = 'validate_gifted_subscription'; 78 | } 79 | } 80 | 81 | loading_spinner.style.display = 'inline-block'; 82 | validate_btn.style.display = 'none'; 83 | validate_btn.style.cursor = 'not-allowed'; 84 | isFetchingFromServer = true; 85 | _browser.runtime.sendMessage({action: action, detail: val}, function(response) { 86 | loading_spinner.style.display = 'none'; 87 | validate_btn.style.display = 'inline-flex'; 88 | setTimeout(function () { 89 | validate_btn.style.cursor = 'pointer'; 90 | isFetchingFromServer = false; 91 | }, 5000); 92 | 93 | if (response.result === 'okay') { 94 | if (val.indexOf('-') === -1) { 95 | document.querySelector('#discord_container')?.remove(); 96 | } 97 | showPage('sub_thanks_page'); 98 | setTimeout(function () { 99 | highlightSection('sub_section_sub_thanks'); 100 | },100); 101 | } else { 102 | setSectionNumberError(2); 103 | document.querySelector('#validation_error_text_el').innerText = response.result.message ? (response.result.message + ': ' + response.result.status_code) : (_i18n('something_went_wrong') + response.result.status_code); 104 | 105 | let info_text = document.querySelector('#opd_redeem_gifted_sub_bottom_note'); 106 | info_text.style.color = 'white'; 107 | let contact_btn = document.querySelector('.tp-contact-us-btn'); 108 | contact_btn.classList.add('animated'); 109 | contact_btn.classList.add('bounce'); 110 | setTimeout(()=>{ 111 | contact_btn.classList.remove('animated'); 112 | contact_btn.classList.remove('bounce'); 113 | info_text.style.color = 'grey'; 114 | }, 1000); 115 | } 116 | }); 117 | }) 118 | 119 | let discord_loading_spinner = document.querySelector('#tp_discord_loading_spinner'); 120 | let discord_send_btn = document.querySelector('#tp_discord_send_btn'); 121 | discord_send_btn.addEventListener('click', function (e) { 122 | if (isFetchingFromServer) { 123 | return; 124 | } 125 | let val_input = document.querySelector('#tp_discord_input'); 126 | let val = val_input.value; 127 | if (!val || val.length < 5 || val.indexOf('#') === -1) { 128 | return; 129 | } 130 | val = val.trim(); 131 | val_input.value = val; 132 | 133 | discord_loading_spinner.style.display = 'inline-block'; 134 | discord_send_btn.style.display = 'none'; 135 | discord_send_btn.style.cursor = 'not-allowed'; 136 | isFetchingFromServer = true; 137 | _browser.runtime.sendMessage({action: 'add_discord_id', detail: val}, function(response) { 138 | discord_loading_spinner.style.display = 'none'; 139 | discord_send_btn.style.display = 'inline-flex'; 140 | setTimeout(function () { 141 | discord_send_btn.style.cursor = 'pointer'; 142 | isFetchingFromServer = false; 143 | }, 5000); 144 | 145 | if (response.result === 'okay') { 146 | document.querySelector('#discord_body').style.display = 'none'; 147 | document.querySelector('#discord_checkmark').style.display = 'block'; 148 | } else { 149 | document.querySelector('#discord_error_text_el').innerText = response.result.message ? (response.result.message + ': ' + response.result.status_code) : (_i18n('something_went_wrong') + response.result.status_code); 150 | 151 | let contact_btn = document.querySelector('.tp-contact-us-btn'); 152 | contact_btn.classList.add('animated'); 153 | contact_btn.classList.add('bounce'); 154 | setTimeout(()=>{ 155 | contact_btn.classList.remove('animated'); 156 | contact_btn.classList.remove('bounce'); 157 | }, 1000); 158 | } 159 | }); 160 | }) 161 | 162 | let redeem_code_floating_btn = document.querySelector('#opd_sub_have_code_btn'); 163 | redeem_code_floating_btn.addEventListener('click', function (e) { 164 | redeem_code_intent = true; 165 | showPage('sub_page'); 166 | startRedeemCodePage(); 167 | }); 168 | 169 | let manage_sub_redeem_code_btn = document.querySelector('#manage_sub_redeem_code_btn'); 170 | manage_sub_redeem_code_btn.addEventListener('click', function (e) { 171 | redeem_code_intent = true; 172 | showPage('sub_page'); 173 | startRedeemCodePage(); 174 | }); 175 | 176 | let gift_a_sub_floating_btn = document.querySelector('#opd_sub_gift_a_sub_btn'); 177 | gift_a_sub_floating_btn.addEventListener('click', function (e) { 178 | showPage('sub_gift_a_sub_page'); 179 | highlightSectionSingle('sub_section_gift_a_sub_phase_1'); 180 | highlightSectionSingle('sub_section_gift_a_sub_phase_2'); 181 | }); 182 | 183 | let manage_sub_gift_a_sub_btn = document.querySelector('#manage_sub_gift_a_sub_btn'); 184 | manage_sub_gift_a_sub_btn.addEventListener('click', function (e) { 185 | showPage('sub_gift_a_sub_page'); 186 | highlightSectionSingle('sub_section_gift_a_sub_phase_1'); 187 | highlightSectionSingle('sub_section_gift_a_sub_phase_2'); 188 | }); 189 | 190 | let manage_sub_subscribe_btn = document.querySelector('#manage_sub_subscribe_btn'); 191 | manage_sub_subscribe_btn.addEventListener('click', function (e) { 192 | redeem_code_intent = false; 193 | showPage('sub_page'); 194 | checkDomainPermissions_flow(); 195 | }); 196 | 197 | document.querySelectorAll('.tp_gift_a_sub_plan_btn').forEach((el)=>{ 198 | el.onclick = (e) => { 199 | document.querySelector('#opd_sub_gift_a_sub_select').value = el.attributes['data-tp-gifted-value'].value; 200 | document.querySelector('#tp_gift_a_sub_paypal_btn').click(); 201 | let sub_section = el.closest('.sub-section'); 202 | sub_section.style.boxShadow = '0 0 10px 0px lime'; 203 | sub_section.querySelector('.tp-sub-section-number').style.backgroundColor = 'limegreen'; 204 | sub_section.querySelector('.tp-sub-section-number').style.color = 'whitesmoke'; 205 | } 206 | }) 207 | 208 | document.getElementById('tp_gift_a_sub_done_btn').addEventListener('click', function (e) { 209 | document.querySelector('#discord_container')?.remove(); 210 | showPage('sub_thanks_page'); 211 | setTimeout(function () { 212 | highlightSection('sub_section_sub_thanks'); 213 | },100); 214 | }); 215 | 216 | document.getElementById('tp_close_tab_btn').addEventListener('click', function (e) { 217 | parent.close(); 218 | window.close(); 219 | this.close(); 220 | }); 221 | 222 | document.getElementById('tp_manage_unsub_btn').addEventListener('click', function (e) { 223 | showPage('sub_unsub_page'); 224 | highlightSection('sub_section_unsub'); 225 | }); 226 | 227 | _browser.storage.local.get('sub_payload', function(result) { 228 | if (result.sub_payload) { 229 | 230 | switch (result.sub_payload.tp_sub_origin_intent) { 231 | case "have_code": 232 | redeem_code_intent = true; 233 | showPage('sub_page'); 234 | startRedeemCodePage(); 235 | break; 236 | case "toast_subscribe": 237 | redeem_code_intent = false; 238 | showPage('sub_page'); 239 | document.querySelector('#opd_sub_subscribe_price_select').value = result.sub_payload.subscribe_price ? result.sub_payload.subscribe_price : '$5'; 240 | document.querySelector('#opd_redeem_gifted_sub_bottom_note').style.display = 'none'; 241 | checkDomainPermissions_flow(); 242 | break; 243 | case "settings_subscribe": 244 | redeem_code_intent = false; 245 | showPage('sub_page'); 246 | document.querySelector('#opd_redeem_gifted_sub_bottom_note').style.display = 'none'; 247 | checkDomainPermissions_flow(); 248 | break; 249 | case "settings_gift_a_sub": 250 | redeem_code_intent = false; 251 | showPage('sub_gift_a_sub_page'); 252 | highlightSectionSingle('sub_section_gift_a_sub_phase_1'); 253 | highlightSectionSingle('sub_section_gift_a_sub_phase_2'); 254 | break; 255 | case "settings_manage_sub": 256 | redeem_code_intent = false; 257 | showPage('sub_manage_page'); 258 | setTimeout(function () { 259 | highlightSection('sub_section_sub_manage'); 260 | }, 100) 261 | _browser.storage.local.get('tp_user_sub', function(result) { 262 | if (result.tp_user_sub) { 263 | if (result.tp_user_sub.is_gifted) { 264 | document.getElementById('sub_manage_subscription_details_container_normal').style.display = 'none'; 265 | document.getElementById('tp_manage_unsub_btn').style.display = 'none'; 266 | document.getElementById('sub_manage_subscription_details_type').innerText = _i18n('gifted_sub_text'); 267 | let end_date = new Date(result.tp_user_sub.last_payment_time.split('T')[0]); 268 | end_date.setDate(end_date.getDate() + result.tp_user_sub.validation_period); 269 | let month = ["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"]; 270 | let diffDays = Math.ceil((new Date(end_date) - new Date()) / (1000 * 60 * 60 * 24)); 271 | document.getElementById('sub_manage_subscription_details_end_time').innerText = end_date.getDate() + '-' + month[end_date.getMonth()] + '-' + end_date.getFullYear() + ' (' + diffDays + ' ' + _i18n('gifted_sub_days_left') + ')'; 272 | } else { 273 | document.getElementById('sub_manage_subscription_details_container_gifted').style.display = 'none'; 274 | document.getElementById('manage_sub_redeem_code_btn').style.display = 'none'; 275 | document.getElementById('manage_sub_subscribe_btn').style.display = 'none'; 276 | } 277 | } 278 | }); 279 | break; 280 | default: 281 | showPage('sub_page'); 282 | checkDomainPermissions_flow(); 283 | break; 284 | } 285 | 286 | _browser.storage.local.set({'sub_payload': false}, function() {}); 287 | } else { 288 | redeem_code_intent = false; 289 | showPage('sub_page'); 290 | checkDomainPermissions_flow(); 291 | } 292 | }); 293 | 294 | function startRedeemCodePage() { 295 | checkDomainPermissions_flow(); 296 | document.querySelector('#sub_top_note').innerText = _i18n('opd_redeem_code_top_note'); 297 | document.querySelector('#sub_section_sub_phase_2').style.display = 'none'; 298 | document.querySelector('#opd_sub_have_code_btn').style.display = 'none'; 299 | document.querySelector('#opd_sub_gift_a_sub_btn').style.display = 'none'; 300 | document.querySelector('#opd_sub_code_info_icon').style.display = 'none'; 301 | document.querySelector('#opd_redeem_gifted_sub_bottom_note').style.display = 'block'; 302 | document.querySelector('#opd_sub_validate_msg').innerText = _i18n('opd_sub_gift_a_sub_title'); 303 | document.querySelector('#tp_validate_input').placeholder = '1A2B3C4D5E6F7G8H9'; 304 | document.querySelector('#validation_error_text_el').innerText = ''; 305 | sections[2].querySelector('.tp-sub-section-number').innerText = '2'; 306 | } 307 | 308 | function checkDomainPermissions_flow() { 309 | _browser.permissions.contains({ 310 | origins: [server_origins] 311 | }, (result) => { 312 | if (result) { 313 | flow_permission_allowed(); 314 | } else { 315 | highlightSection('sub_section_sub_phase_1'); 316 | } 317 | }); 318 | } 319 | 320 | function flow_permission_allowed() { 321 | setSectionNumberCompleted(0); 322 | document.querySelector('#sub_section_sub_phase_1').style.display = 'none'; 323 | document.querySelector('#sub_section_sub_phase_2').querySelector('.tp-sub-section-number').innerText = '1'; 324 | document.querySelector('#sub_section_sub_phase_3').querySelector('.tp-sub-section-number').innerText = '2'; 325 | if (redeem_code_intent) { 326 | highlightSection('sub_section_sub_phase_3'); 327 | } else { 328 | highlightSection('sub_section_sub_phase_2'); 329 | } 330 | sections[1].onmouseenter = ()=> { 331 | highlightSection('sub_section_sub_phase_2'); 332 | } 333 | sections[2].onmouseenter = ()=> { 334 | highlightSection('sub_section_sub_phase_3'); 335 | } 336 | } 337 | 338 | 339 | function setSectionNumberCompleted(num) { 340 | sections[num].querySelector('.tp-sub-section-number').style.backgroundColor = 'limegreen'; 341 | sections[num].querySelector('.tp-sub-section-number').style.color = 'whitesmoke'; 342 | } 343 | function setSectionNumberError(num) { 344 | sections[num].querySelector('.tp-sub-section-number').style.backgroundColor = 'red'; 345 | sections[num].querySelector('.tp-sub-section-number').style.color = 'whitesmoke'; 346 | } 347 | 348 | function highlightSection(id) { 349 | document.querySelectorAll('.sub-section').forEach((x)=>{x.id === id ? x.classList.add('tp-sub-section-highlighted'): x.classList.remove('tp-sub-section-highlighted')}) 350 | } 351 | 352 | function highlightSectionSingle(id) { 353 | document.querySelector('#' + id).classList.add('tp-sub-section-highlighted'); 354 | } 355 | 356 | function showPage(id) { 357 | document.querySelectorAll('.sub_page').forEach((x)=>{x.id === id ? x.style.display = 'block': x.style.display = 'none'}) 358 | } 359 | } 360 | main().then(); -------------------------------------------------------------------------------- /Twitch-Previews/popups/updatePopup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Previews (For TTV) 6 | 7 | 8 | 9 |

Previews Updated!

10 | 11 |
12 | 13 | 14 |

New Feature!

15 | 16 |
Auto channel points clicker
17 |
18 |
* this feature looks for the green channel points redeem button every 15 seconds and clicks it if it's there.
19 |
* it also works when chat is closed and when the tab or window is in the background.
20 |
* This feature is turned off by default and can be changed in the extension options.
21 | 22 | 23 | 24 | 25 |
26 |
27 |
Auto page refresh when the main twitch player gets an error (#1000, #2000, #4000)
28 | 29 |
30 |
* this feature works when the tab with the player that got an error is currently active.
31 |
* if the player got an error while the tab was not active (in the background or chrome wasn't the active window) the page will automatically refresh when you come back to it.
32 |
33 |
* This feature is turned off by default and can be changed in the extension options as shown by the red arrow in the picture below.
34 |
* If you can't see the extension icon at the top bar - click the white chrome extensions manager icon next to the Previews (For TTV) extension icon in the picture below.
35 | 36 |
37 | 40 | 43 |
44 |
45 | 46 |
47 |
If you like this extension, support the developer and share the extension with others :)
48 |
49 | 50 | 51 | 52 | 53 |
54 |
55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /build/build.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from distutils.dir_util import copy_tree 4 | import shutil 5 | import json 6 | 7 | firefox_dev_proj_dir_name = 'firefox_dev' 8 | 9 | def get_version(): 10 | manifest_path = 'temp/manifest.json' 11 | 12 | with open(manifest_path, 'r') as f: 13 | data = json.load(f) 14 | return data['version'].replace('.', '') 15 | 16 | def replace_strings_for_edge(): 17 | background_path = 'temp/main/background.js' 18 | 19 | with open(background_path, 'r') as f: 20 | data = f.read() 21 | data = data.replace('''tpga_browser = 'chrome';''', '''tpga_browser = 'edge';''') 22 | data = data.replace('https://chrome.google.com/webstore/detail/hpmbiinljekjjcjgijnlbmgcmoonclah/reviews/', 'https://microsoftedge.microsoft.com/addons/detail/nmekhdckniaiegiekejhmcmddplmliel') 23 | 24 | with open(background_path, 'w') as f: 25 | f.write(data) 26 | 27 | def replace_strings_for_opera(): 28 | background_path = 'temp/main/background.js' 29 | 30 | with open(background_path, 'r') as f: 31 | data = f.read() 32 | data = data.replace('''tpga_browser = 'chrome';''', '''tpga_browser = 'opera';''') 33 | data = data.replace('https://chrome.google.com/webstore/detail/hpmbiinljekjjcjgijnlbmgcmoonclah/reviews/', 'https://addons.opera.com/en/extensions/details/previews-for-ttv/') 34 | 35 | with open(background_path, 'w') as f: 36 | f.write(data) 37 | 38 | 39 | def replace_strings_for_firefox(f_dir): 40 | background_path = f_dir + '/main/background.js' 41 | 42 | with open(background_path, 'r') as f: 43 | data = f.read() 44 | data = data.replace('''tpga_browser = 'chrome';''', '''tpga_browser = 'firefox';''') 45 | data = data.replace('https://chrome.google.com/webstore/detail/hpmbiinljekjjcjgijnlbmgcmoonclah/reviews/', 'https://addons.mozilla.org/en-US/firefox/addon/previews-for-ttv/') 46 | 47 | with open(background_path, 'w') as f: 48 | f.write(data) 49 | 50 | 51 | def make_firefox_dev_proj(): 52 | if os.path.isdir(firefox_dev_proj_dir_name): 53 | shutil.rmtree(firefox_dev_proj_dir_name) 54 | os.mkdir(firefox_dev_proj_dir_name) 55 | copy_tree('../Twitch-Previews', firefox_dev_proj_dir_name) 56 | replace_strings_for_firefox(firefox_dev_proj_dir_name) 57 | 58 | 59 | def build(browser): 60 | os.mkdir('temp') 61 | copy_tree('../Twitch-Previews', 'temp') 62 | 63 | if browser == 'chrome': 64 | pass 65 | elif browser == 'firefox': 66 | if os.path.isdir(firefox_dev_proj_dir_name): 67 | shutil.rmtree(firefox_dev_proj_dir_name) 68 | replace_strings_for_firefox('temp') 69 | elif browser == 'opera': 70 | replace_strings_for_opera() 71 | elif browser == 'edge': 72 | replace_strings_for_edge() 73 | 74 | output_filename = 'PreviewsV' + get_version() + '-' + browser 75 | shutil.make_archive(output_filename, 'zip', 'temp') 76 | shutil.rmtree('temp') 77 | 78 | 79 | if len(sys.argv) > 1: 80 | if sys.argv[1] == firefox_dev_proj_dir_name: 81 | make_firefox_dev_proj() 82 | else: 83 | build(sys.argv[1]) 84 | -------------------------------------------------------------------------------- /build/build_all.bat: -------------------------------------------------------------------------------- 1 | python build.py chrome 2 | python build.py firefox 3 | python build.py opera 4 | python build.py edge -------------------------------------------------------------------------------- /build/build_chrome.bat: -------------------------------------------------------------------------------- 1 | python build.py chrome -------------------------------------------------------------------------------- /build/build_edge.bat: -------------------------------------------------------------------------------- 1 | python build.py edge -------------------------------------------------------------------------------- /build/build_firefox.bat: -------------------------------------------------------------------------------- 1 | python build.py firefox -------------------------------------------------------------------------------- /build/build_opera.bat: -------------------------------------------------------------------------------- 1 | python build.py opera -------------------------------------------------------------------------------- /build/create_firefox_dev_proj.bat: -------------------------------------------------------------------------------- 1 | python build.py firefox_dev -------------------------------------------------------------------------------- /previews_main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MarkM-dev/Previews-client/b312dfc99ae67626d5fc6118389d1cbdcf265939/previews_main.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![](previews_main.png) 2 |
3 |
4 | ## **Previews (For TTV)** 5 | 6 | **Previews (For TTV) creates a live video or image preview when hovering over a stream on the navigation sidebar (followed streams list on the side) and in twitch directories.** 7 |

8 | **Check out the features list below for more quality of life improvement features to twitch.** 9 |
10 |
11 | 12 | ## Download | **[Website](https://previews-app.com/)** 13 | **Previews (For TTV) is available for all browsers:
** 14 | 15 | **[Chrome Extension](https://chrome.google.com/webstore/detail/hpmbiinljekjjcjgijnlbmgcmoonclah/) 16 | | [Firefox Add-on](https://addons.mozilla.org/en-US/firefox/addon/previews-for-ttv/) 17 | | [Edge Add-on](https://microsoftedge.microsoft.com/addons/detail/nmekhdckniaiegiekejhmcmddplmliel) 18 | | [Opera Add-on](https://addons.opera.com/en/extensions/details/previews-for-ttv/)** 19 |
20 | * Chromium based browsers can install extensions from the chrome webstore. 21 |
22 | 23 | ## Features: 24 | - Image or live video stream Previews in the sidebar & directories. 25 | - Preview resize & volume control. 26 | - Streaming: See your own live stream thumbnail as seen by viewers on twitch. 27 | - Sidebar Favorite Channels List. 28 | - Sidebar YouTube Channels List. 29 | - Sidebar Facebook Gaming Channels List. 30 | - Multi Stream & Multi Chat. 31 | - Transparent / Dynamic Chat Overlay. 32 | - Full Screen With Chat Mode. 33 | - Auto Channel Points Clicker. 34 | - Clip Downloader. 35 | - Auto Refresh on player errors (#1000, #2000, #3000, #4000). 36 | - Predictions Notifications when you don't know it's happening (for example if your chat is closed or you are not in the tab or browser). 37 | - Predictions Sniper (will participate in predictions for you). 38 | - Prevent Automatic Video Quality Change. 39 | - Seek Streams Using Keyboard Arrow Keys. 40 | - Fast-Forward Stream. 41 | - Chrome-Cast -> Close Tab. 42 | - Screenshot Stream. 43 | - Record Stream. 44 | - FlashBang Defender. 45 | - Clear Chat Button. 46 | - Incognito Chat. 47 | - Native Picture In Picture in current stream (Browser's PIP feature). 48 | - Custom Picture In Picture via the button under the view count in the sidebar. 49 | - Auto Extend the sidebar to show all live streamers (when sidebar is open). 50 | - Sidebar Stream Search: A purple search button at the top of the sidebar to find live streamers easily in the sidebar. 51 | - Hide All Sidebar Sections Except The Followed Channels. 52 | - Mute Auto-Playing Videos In Various Pages. 53 | - Advanced Video Embeds. 54 | - Also supports inverted twitch layout (when chat is on the left and streamers on the right). 55 |
56 | 57 | ## How to use: 58 | 1. **Add the extension to your browser.** 59 | - If you already have a twitch tab opened - refresh it. 60 | 61 | 2. **Hover your mouse over a streamer on the sidebar / directories.** 62 | - A preview will appear next to the hovered stream. 63 |
64 |
65 | 66 | ## Feature Notes: 67 | * **Stream Previews** 68 | - Live image and video previews when hovering over streams in the sidebar and directories. 69 |

70 | 71 | 72 | * **Streaming - See Your Own Live Stream Thumbnail** 73 | - Shows your own stream's live thumbnail preview, as seen by viewers on Twitch, when hovering over the Twitch logo at the top left. 74 | - This feature is only active on 'twitch.tv' pages, it's not active in the clips directory or in the dashboard (like clips.twitch.tv). 75 |

76 | 77 | 78 | * **Multi Stream & Multi Chat** 79 | - You can add Multi-Stream and Multi-Chat on the fly to any page you're on directly from the sidebar or via the top search bar (if the Multi-Stream feature is enabled). 80 | - There is also a button that is located next to the stream uptime under the stream. 81 | - Clicking it will start Multi Stream on a new Twitch tab - so you can still enjoy the benefits of the sidebar and the search function at the top bar. 82 | - In the new Multi-Stream tab, add a stream from the sidebar or search for a stream at the top Twitch search bar and click the Multi-Stream button in the results to add the stream. 83 | - You can scroll the page, change background color & transparency, font color, weight, increase/decrease size, fullscreen, drag, resize and minimize the boxes (top bar and bottom right corner of the boxes). 84 | - If the Auto Channel Points feature is enabled, it will collect points in the chat box. 85 | - If you enable the 'Sidebar YouTube Channels' feature, you can add your subscribed YouTube channels to the Multi-Stream and use it cross-platform (also supports YouTube chat). 86 |

87 | 88 | 89 | * **Full Screen With Chat** 90 | - The button will show next to the 'theater mode' or 'fullscreen' button in the player controls. 91 | - Hovering over it will show two options: Full Screen with Custom chat overlay or Default Chat. 92 | - The Custom chat will automatically position itself over the video to the right and stretch to screen height. 93 | - You can resize, move it around, align to each side and change styles with the controls at the top of the chat box. 94 | - The Custom chat's settings (position, size, colors, etc..) will be saved for the current page session. 95 | - The Custom Chat will spawn back when you return to fullscreen after exiting fullscreen while in mode. 96 | - Exit the mode by clicking the button in the player controls again or click the close(x) button on the Custom Chat and exit fullscreen. 97 | - Firefox users - exit the 'Default-Chat' mode by double tapping ESC. 98 |

99 | 100 | 101 | * **Sidebar Favorite Channels** 102 | - A new Favorites list at the top of the sidebar (for your most favorite streamers). 103 | - Add streams to your favorites list by clicking the Favorites button next to the bell under the stream. 104 | - The list will show only the currently live streams in your favorites list. 105 | - Note: the feature relies on the followed channels list, so it will auto-expand (show more) when the sidebar is opened. if it's closed, the list will still populate but will be partial. 106 | - Note: it might take a few seconds for the list to show or update. 107 |

108 | 109 | 110 | * **Sidebar YouTube Channels** 111 | - A new list at the top of the Twitch sidebar to show your subscribed YouTube channels that are currently live on YouTube. 112 | - The list data will update every 5 minutes. 113 | - Supported by the Custom Picture-In-Picture and Multi-Stream & Multi-Chat features so you can use the Multi-Stream cross-platform. 114 | - You need to be logged in to YouTube on your browser (just go to youtube.com and login if you aren't already). 115 | - When enabling this feature, you will need to allow the extension to run on "youtube.com" (a prompt will show when enabling) - this is so the extension can fetch the streams from YouTube. 116 |

117 | 118 | 119 | * **Sidebar Facebook Gaming Channels** 120 | - A new list at the top of the Twitch sidebar to show your selected Facebook Gaming channels that are currently live on Facebook. 121 | - Enable the feature and click on the edit button to add Facebook streamers to the list. 122 | - The list data will update every 7 minutes. 123 | - Supported by the Custom Picture-In-Picture and Multi-Stream features so you can use the Multi-Stream cross-platform. 124 | - You don't need to be logged in on facebook. 125 | - It may take a few seconds for the list to update, depending on how many streamers you added. 126 | - When enabling this feature, you will need to allow the extension to run on "facebook.com" (a prompt will show when enabling) - this is so the extension can fetch the streams from Facebook. 127 |

128 | 129 | 130 | * **Auto Channel Points Clicker** 131 | - This feature automatically clicks the green channel points redeem button. 132 | - It also works when chat is closed and when the tab or window is in the background. 133 |

134 | 135 | 136 | * **Clip Downloader** 137 | - The button will show in the player controls of clips. 138 | - When enabling this feature, you will need to allow the extensions to run on "clips.twitch.tv" (a prompt will show when enabling). 139 |

140 | 141 | 142 | * **Sidebar Stream Search** 143 | - A purple search button on the top of the sidebar to find live streamers easily. 144 | - Searches within the currently shown streamers so the sidebar will automatically extend to show all live streamers when you start searching. 145 |

146 | 147 | 148 | * **Sidebar Extend** 149 | - Auto extends the sidebar to show all live streamers (when sidebar is open). 150 |

151 | 152 | 153 | * **Hide All Sidebar Sections Except The Followed Channels** 154 | - Hides all the other sections in the sidebar except the followed channels. 155 | - Note: this feature does not affect the 'sidebar favorites' feature, you will still see your favorites and YouTube channels lists. 156 |

157 | 158 | 159 | * **Mute Auto-Playing Videos In Various Pages** 160 | - Mutes the auto-playing video players in various pages. 161 |

162 | 163 | 164 | * **Native Picture In Picture** 165 | - The button will show next to the 'theater mode' button in the player controls. 166 | - Clicking it will start chrome's Picture In Picture for the current stream. 167 |

168 | 169 | 170 | * **Custom Picture in picture** 171 | - A small button will appear under the view count when you hover a streamer - click it and a persistent preview window will appear (you can add as many as you like). 172 |

173 | 174 | 175 | * **Auto Refresh When The Main Twitch Player Gets An Error (#1000, #2000, #3000, #4000)** 176 | - Refreshes the player if an error occurs. 177 |

178 | 179 | 180 | * **Prevent Automatic Video Quality Change** 181 | - Prevents automatic video quality change when twitch is in the background (when switching tabs / tasks). 182 | - Prevents 99% of Twitch player errors (#1000, #2000). 183 |

184 | 185 | 186 | * **Seek Streams Using Keyboard Arrow Keys** 187 | - Seeks 5 seconds back or forward using the keyboard left/right arrow keys. 188 | - Note: this feature moves the playback point in the buffer, which is constantly cleared by Twitch, so seeking might sometimes be interrupted. 189 | - Note: the buffer size varies between 0 seconds to two minutes. 190 |

191 | 192 | 193 | * **Fast-Forward Stream** 194 | - Useful if your stream is delayed. 195 | - The button will show in the player controls next to the 'play/pause' button. 196 | - Click it to fast-forward the stream to the latest point in the buffer. 197 |

198 | 199 | 200 | * **Chrome-Cast -> Close Tab** 201 | - The button will show in the player controls. 202 | - Click it to start casting on a new tab and then close the new tab without stopping the Chrome-Cast. 203 | - Everything is done automatically, you just need to select your casting device when prompted. 204 | - Note: experimental feature, try again if it fails. 205 |

206 | 207 | 208 | * **Screenshot Stream** 209 | - The button will show in the player controls. 210 | - You can capture multiple screenshots and then save only the ones you like. 211 | - The screenshots are captured at the same resolution as the stream. 212 |

213 | 214 | 215 | * **Record Stream** 216 | - The button will show in the player controls. 217 | - Click to start recording, click again to stop recording and save. 218 | - Works great with the "Seek Using Keyboard Arrows" feature. 219 | - Records at 60 frames per second, save size is about 500KB/second for 1080P(60). 220 | - Recordings will automatically stop and save to file if there was an error with the player or the stream was changed. 221 | - Note: you must have hardware-acceleration enabled in the browser (enabled by default). 222 | - Note: due to a chromium bug, the recordings are saved without metadata like time duration, so the duration will be in the file name. 223 |

224 | 225 | 226 | * **FlashBang Defender** 227 | - For when it's late night and the streamer opens a white screen. 228 | - Toggles a semi-transparent overlay on top of the stream. 229 | - The button will show in the player controls. 230 |

231 | 232 | 233 | * **Clear Chat Button** 234 | - The button will show under the chat. 235 | - Clears all the messages in chat. 236 |

237 | 238 | 239 | * **Incognito Chat Button** 240 | - The button will show under the chat (if you are banned). 241 | - Opens the chat in a resized incognito window. 242 |

243 | 244 | 245 | * **Predictions Notifications** 246 | - Predictions started and Predictions results notifications when you don't know it's happening (for example if your chat is closed or you are not in the tab or browser). 247 | - Works on twitch tabs in the browser. 248 | - When enabling the feature, you will need to allow notification permissions for twitch.tv (a prompt will show - if not, click on the lock icon on the left of the url and check if it's allowed there). 249 |

250 | 251 | 252 | * **Predictions Sniper** 253 | - The predictions sniper will participate in predictions for you. 254 | - Works on twitch tabs in the browser. 255 | - The sniper will choose the prediction option with the most amount of votes received at the time of entry (x seconds before prediction closes). 256 | - If you have your chat open (no need), you will see the prediction menu for a split second when the sniper is entering a prediction. 257 | - You can enable the 'Predictions notifications' feature if you want to know what's happening in real-time. 258 | - If there is already a bet made on the prediction, the sniper will not add more points. 259 | - To change Sniper settings for individual streams, click the Sniper settings button under the chat. 260 |

261 | - Default Settings: 262 | - Bet % - the percentage of channel points you want the sniper to bet. 263 | - Max num of points - the maximum amount of points the sniper is allowed to vote with. 264 | - Min vote margin % - a percentage representation of the minimum required vote margin between the two prediction options for the sniper to participate. 265 | - For example: option A- 100 votes, option B- 115 votes, vote spread: A-46.51% B-53.49%, vote margin: 6.98% (53.49% - 46.51%). if the minimum vote margin is lower than 6.98%, the sniper will participate in the prediction. 266 | - Seconds - the amount of seconds the sniper will make a prediction before the prediction closes (min 2s). 267 |

268 | - Remember that this is a statistical tool and wins are not guaranteed. 269 |

270 | 271 | 272 | * **Advanced Video Embeds** 273 | - This applies to the Multi-Stream feature and on-the-fly video embeds (not previews). 274 | - Bypasses purple screen. 275 | - Adds enabled Previews (For TTV) features to embeds (seek, fast-forward, auto-refresh, picture-in-picture, fullscreen with custom chat, cast -> close tab, flashbang defender, stream screenshot). 276 | - Seeking using the keyboard arrow keys in these embeds will be in 1.5 seconds intervals instead of the normal 5 seconds to allow easier streams synchronization for Multi-Stream. 277 | - Note: this feature will use more resources when using said embeds. if you notice performance issues you can turn this feature off. 278 | - Note: if you're using a different extension that replaces the Twitch main player, you might still experience a purple screen. 279 | - Experimental feature. 280 |

281 | 282 |
283 |
284 | 285 | 286 | 287 |
288 |
289 | 290 | - To change any feature setting click the extension at the top bar to open the extension's options menu. 291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 | 300 | © Mark M . 301 | --------------------------------------------------------------------------------