Merge branch 'master' of https://github.com/ajayyy/SponsorBlock into chapters

This commit is contained in:
Ajay 2022-08-19 23:16:45 -04:00
commit 42d76cf257
39 changed files with 4403 additions and 10737 deletions

View file

@ -30,7 +30,7 @@ jobs:
name: ChromeExtension name: ChromeExtension
path: dist path: dist
- run: mkdir ./builds - run: mkdir ./builds
- uses: montudor/action-zip@v1 - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with: with:
args: zip -qq -r ./builds/ChromeExtension.zip ./dist args: zip -qq -r ./builds/ChromeExtension.zip ./dist
@ -41,7 +41,7 @@ jobs:
with: with:
name: FirefoxExtension name: FirefoxExtension
path: dist path: dist
- uses: montudor/action-zip@v1 - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with: with:
args: zip -qq -r ./builds/FirefoxExtension.zip ./dist args: zip -qq -r ./builds/FirefoxExtension.zip ./dist
@ -52,7 +52,7 @@ jobs:
with: with:
name: ChromeExtensionBeta name: ChromeExtensionBeta
path: dist path: dist
- uses: montudor/action-zip@v1 - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with: with:
args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist
@ -62,7 +62,7 @@ jobs:
with: with:
name: FirefoxExtensionBeta name: FirefoxExtensionBeta
path: dist path: dist
- uses: montudor/action-zip@v1 - uses: montudor/action-zip@0852c26906e00f8a315c704958823928d8018b28
with: with:
args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist

View file

@ -9,6 +9,6 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: take the issue - name: take the issue
uses: bdougie/take-action@main uses: bdougie/take-action@28b86cd8d25593f037406ecbf96082db2836e928
env: env:
GITHUB_TOKEN: ${{ github.token }} GITHUB_TOKEN: ${{ github.token }}

View file

@ -25,7 +25,7 @@ jobs:
mv ./oss-attribution/attribution.txt ./public/oss-attribution/attribution.txt mv ./oss-attribution/attribution.txt ./public/oss-attribution/attribution.txt
- name: Create pull request to update list - name: Create pull request to update list
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with: with:
commit-message: Update OSS Attribution commit-message: Update OSS Attribution
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

View file

@ -19,7 +19,7 @@ jobs:
run: npm run ci:invidious run: npm run ci:invidious
- name: Create pull request to update list - name: Create pull request to update list
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with: with:
commit-message: Update Invidious List commit-message: Update Invidious List
author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

View file

@ -49,7 +49,7 @@ const reliableCheck = mapped
.filter(instance => instance.url.includes(instance.name)) .filter(instance => instance.url.includes(instance.name))
// finally map to array // finally map to array
const result: string[] = reliableCheck.map(instance => instance.name) const result: string[] = reliableCheck.map(instance => instance.name).sort()
writeFile(join(__dirname, "./invidiouslist.json"), JSON.stringify(result), (err) => { writeFile(join(__dirname, "./invidiouslist.json"), JSON.stringify(result), (err) => {
if (err) return console.log(err); if (err) return console.log(err);
}) })

View file

@ -1 +1 @@
["yewtu.be","vid.puffyan.us","invidious.snopyta.org","inv.riverside.rocks","invidious-us.kavin.rocks","invidious.osi.kr","tube.cthd.icu","invidious.flokinet.to","yt.artemislena.eu","invidious.mutahar.rocks","invidious.esmailelbob.xyz","youtube.076.ne.jp","invidious.weblibre.org","invidious.namazso.eu","invidious.kavin.rocks"] ["inv.cthd.icu","inv.riverside.rocks","invidio.xamh.de","invidious.kavin.rocks","invidious.namazso.eu","invidious.osi.kr","invidious.snopyta.org","vid.puffyan.us","yewtu.be","youtube.076.ne.jp","yt.artemislena.eu"]

View file

@ -1,7 +1,7 @@
{ {
"name": "__MSG_fullName__", "name": "__MSG_fullName__",
"short_name": "SponsorBlock", "short_name": "SponsorBlock",
"version": "4.6", "version": "4.7.1",
"default_locale": "en", "default_locale": "en",
"description": "__MSG_Description__", "description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app", "homepage_url": "https://sponsor.ajay.app",

13901
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -3,40 +3,39 @@
"version": "1.0.0", "version": "1.0.0",
"description": "", "description": "",
"main": "background.js", "main": "background.js",
"type": "module",
"dependencies": { "dependencies": {
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2" "react-dom": "^17.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/chrome": "^0.0.188", "@types/chrome": "^0.0.193",
"@types/firefox-webext-browser": "^94.0.1", "@types/firefox-webext-browser": "^94.0.1",
"@types/jest": "^27.5.1", "@types/jest": "^28.1.6",
"@types/react": "^17.0.43", "@types/react": "^17.0.47",
"@types/react-dom": "^17.0.14", "@types/react-dom": "^17.0.17",
"@types/selenium-webdriver": "^4.1.1", "@types/selenium-webdriver": "^4.1.2",
"@types/wicg-mediasession": "^1.1.3", "@types/wicg-mediasession": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.26.0", "@typescript-eslint/eslint-plugin": "^5.31.0",
"@typescript-eslint/parser": "^5.26.0", "@typescript-eslint/parser": "^5.31.0",
"chromedriver": "^101.0.0", "chromedriver": "^103.0.0",
"concurrently": "^7.2.1", "concurrently": "^7.3.0",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"eslint": "^8.16.0", "eslint": "^8.20.0",
"eslint-plugin-react": "^7.30.0", "eslint-plugin-react": "^7.30.1",
"fork-ts-checker-webpack-plugin": "^7.2.11", "fork-ts-checker-webpack-plugin": "^7.2.13",
"jest": "^28.1.0", "jest": "^28.1.3",
"jest-environment-jsdom": "^28.1.0", "jest-environment-jsdom": "^28.1.0",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"schema-utils": "^4.0.0", "schema-utils": "^4.0.0",
"selenium-webdriver": "^4.2.0", "selenium-webdriver": "^4.3.1",
"speed-measure-webpack-plugin": "^1.5.0", "speed-measure-webpack-plugin": "^1.5.0",
"ts-jest": "^28.0.3", "ts-jest": "^28.0.7",
"ts-loader": "^9.3.0", "ts-loader": "^9.3.1",
"ts-node": "^10.8.0", "ts-node": "^10.9.1",
"typescript": "4.7", "typescript": "4.7",
"web-ext": "^6.8.0", "web-ext": "^7.1.1",
"webpack": "^5.72.1", "webpack": "^5.74.0",
"webpack-cli": "^4.9.2", "webpack-cli": "^4.10.0",
"webpack-merge": "^5.8.0" "webpack-merge": "^5.8.0"
}, },
"scripts": { "scripts": {

View file

@ -164,6 +164,9 @@
"copyPublicID": { "copyPublicID": {
"message": "نسخ معرف المستخدم العام" "message": "نسخ معرف المستخدم العام"
}, },
"copySegmentID": {
"message": "نسخ معرف الجزء"
},
"discordAdvert": { "discordAdvert": {
"message": "انضم إلى سيرفر \"ديسكورد\" الرسمي لتقديم اقتراحات وتعليقات!" "message": "انضم إلى سيرفر \"ديسكورد\" الرسمي لتقديم اقتراحات وتعليقات!"
}, },
@ -200,6 +203,9 @@
"showDeleteButton": { "showDeleteButton": {
"message": "إظهار زر \"حذف\" على مشغّل اليوتيوب" "message": "إظهار زر \"حذف\" على مشغّل اليوتيوب"
}, },
"enableViewTracking": {
"message": "تمكين تتبع مرات التخطي"
},
"showNotice": { "showNotice": {
"message": "إظهار الإشعار مرة أخرى" "message": "إظهار الإشعار مرة أخرى"
}, },
@ -401,12 +407,18 @@
"Donate": { "Donate": {
"message": "تبرع" "message": "تبرع"
}, },
"considerDonating": {
"message": "ساعد في تمويل التطوير"
},
"hideDonationLink": { "hideDonationLink": {
"message": "إخفاء رابط التبرع" "message": "إخفاء رابط التبرع"
}, },
"darkModeOptionsPage": { "darkModeOptionsPage": {
"message": "الوضع الداكن في صفحة الخيارات" "message": "الوضع الداكن في صفحة الخيارات"
}, },
"helpPageThanksForInstalling": {
"message": "شكرا على تثبيت SponsorBlock."
},
"Editing": { "Editing": {
"message": "التعديل" "message": "التعديل"
}, },

View file

@ -186,7 +186,7 @@
"message": "Versteckt die Schaltflächen im YouTube-Videoplayer, um Segmente einzusenden." "message": "Versteckt die Schaltflächen im YouTube-Videoplayer, um Segmente einzusenden."
}, },
"showSkipButton": { "showSkipButton": {
"message": "\"Zum Highlight springen\"-Button im Youtube-Player anzeigen" "message": "Behalte \"Zum Highlight springen\"-Knopf in der Leiste"
}, },
"showInfoButton": { "showInfoButton": {
"message": "Zeige Info-Knopf im Youtube-Videoplayer" "message": "Zeige Info-Knopf im Youtube-Videoplayer"
@ -867,7 +867,7 @@
"message": "Dauerhaft verbergen" "message": "Dauerhaft verbergen"
}, },
"warningChatInfo": { "warningChatInfo": {
"message": "Du hast eine Warnung erhalten und kannst vorübergehend keine Segmente einreichen. Uns ist nämlich aufgefallen, dass du nicht bösartige Fehler in deinen Einreichungen machst. Bitte bestätige, dass du die Regeln verstanden hast. Darauffolgend können wir die Warnung entfernen. Du kannst diesem Chat auch mit discord.gg/SponsorBlock oder matrix.to/#/#sponsor:ajay.app beitreten" "message": "Du hast eine Warnung erhalten und kannst vorübergehend keine Segmente einreichen. Dies bedeutet, dass du Fehler gemacht hast welche nicht bösartig sind, bitte bestätige, dass du die Regeln verstanden hast, wir werden dann die Warnung entfernen. Du kannst diesem Chat auch mit discord.gg/SponsorBlock oder matrix.to/#/#sponsor:ajay.app beitreten"
}, },
"voteRejectedWarning": { "voteRejectedWarning": {
"message": "Abstimmung wegen einer Warnung abgelehnt. Klicke hier um einen Chat zu öffnen, oder versuch es später erneut, wenn du Zeit hast.", "message": "Abstimmung wegen einer Warnung abgelehnt. Klicke hier um einen Chat zu öffnen, oder versuch es später erneut, wenn du Zeit hast.",
@ -964,7 +964,7 @@
"message": "Dies wirkt sich sofort auf eigene Segmente aus" "message": "Dies wirkt sich sofort auf eigene Segmente aus"
}, },
"downvote": { "downvote": {
"message": "Negativ bewertet" "message": "Dagegen stimmen"
}, },
"upvote": { "upvote": {
"message": "Positiv bewerten" "message": "Positiv bewerten"

View file

@ -659,7 +659,7 @@
"message": "Preview/Recap" "message": "Preview/Recap"
}, },
"category_preview_description": { "category_preview_description": {
"message": "Quick recap of previous episodes, or a preview of what's coming up later in the current video. Meant for edited together clips, not for spoken summaries." "message": "Collection of clips that show what is coming up in in this video or other videos in a series where all information is repeated later in the video."
}, },
"category_preview_guideline1": { "category_preview_guideline1": {
"message": "Clips that appear later, or in a future video" "message": "Clips that appear later, or in a future video"
@ -838,11 +838,8 @@
"description": "This error appears in an alert when they try to whitelist a channel and the extension is unable to determine what channel they are looking at.", "description": "This error appears in an alert when they try to whitelist a channel and the extension is unable to determine what channel they are looking at.",
"message": "Channel ID is not loaded yet. If you are using an embedded video, try using the YouTube homepage instead. This could also be caused by changes in the YouTube layout, if you think so, make a comment here:" "message": "Channel ID is not loaded yet. If you are using an embedded video, try using the YouTube homepage instead. This could also be caused by changes in the YouTube layout, if you think so, make a comment here:"
}, },
"videoInfoFetchFailed": { "invidiousPermissionRefresh": {
"message": "It seems that something is blocking SponsorBlock's ability to get video data. Please see https://github.com/ajayyy/SponsorBlock/issues/741 for more info." "message": "The browser has revoked the permission needed to function on Invidious and other 3rd-party sites. Please click the button below to reactivate this permission."
},
"youtubePermissionRequest": {
"message": "It seems that SponsorBlock is unable to reach the YouTube API. To fix this, accept the permission prompt that will appear next, wait a few seconds, and then reload the page."
}, },
"acceptPermission": { "acceptPermission": {
"message": "Accept permission" "message": "Accept permission"
@ -914,11 +911,19 @@
"message": "Hide forever" "message": "Hide forever"
}, },
"warningChatInfo": { "warningChatInfo": {
"message": "You got a warning and cannot submit segments temporarily. This means that we noticed you were making some common mistakes that are not malicious, please just confirm that you understand the rules and we will remove the warning. You can also join this chat using discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app" "message": "We noticed you were making some common mistakes that are not malicious"
}, },
"voteRejectedWarning": { "warningTitle": {
"message": "Vote rejected due to a warning. Click to open a chat to resolve it, or come back later when you have time.", "message": "You got a warning"
"description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser." },
"questionButton": {
"message": "I have a question"
},
"warningConfirmButton": {
"message": "I understand the reason"
},
"warningError": {
"message": "Error when trying to acknowledge warning:"
}, },
"Donate": { "Donate": {
"message": "Donate" "message": "Donate"

View file

@ -239,6 +239,9 @@
"showSkipNotice": { "showSkipNotice": {
"message": "Notifier après qu'un segment ait été sauté" "message": "Notifier après qu'un segment ait été sauté"
}, },
"showCategoryGuidelines": {
"message": "Affiche l'aide de la catégorie"
},
"noticeVisibilityMode0": { "noticeVisibilityMode0": {
"message": "Notifications de passage" "message": "Notifications de passage"
}, },
@ -542,18 +545,39 @@
"message": "à", "message": "à",
"description": "Used between segments. Example: 1:20 to 1:30" "description": "Used between segments. Example: 1:20 to 1:30"
}, },
"generic_guideline1": {
"message": "Inclure les transitions entre les segments"
},
"generic_guideline2": {
"message": "Jouer comme si rien n'avait été passé"
},
"category_sponsor": { "category_sponsor": {
"message": "Message sponsorisé" "message": "Message sponsorisé"
}, },
"category_sponsor_description": { "category_sponsor_description": {
"message": "Promotion rémunérée, parrainage rémunéré et publicité directe. Pas pour l'autopromotion ou les présentations gratuites de causes, de créateurs, de sites web ou de produits qu'ils aiment." "message": "Promotion rémunérée, parrainage rémunéré et publicité directe. Pas pour l'autopromotion ou les présentations gratuites de causes, de créateurs, de sites web ou de produits qu'ils aiment."
}, },
"category_sponsor_guideline1": {
"message": "Promotions rémunérées"
},
"category_sponsor_guideline2": {
"message": "Pas pour les dons ou les produits dérivés"
},
"category_selfpromo": { "category_selfpromo": {
"message": "Non rémunéré/autopromotion" "message": "Non rémunéré/autopromotion"
}, },
"category_selfpromo_description": { "category_selfpromo_description": {
"message": "Semblable aux \"messages commerciaux\", excepté pour la promotion non rémunérée ou l'autopromotion. Cela inclut les marchandises, les dons et les informations sur leurs collaborateurs." "message": "Semblable aux \"messages commerciaux\", excepté pour la promotion non rémunérée ou l'autopromotion. Cela inclut les marchandises, les dons et les informations sur leurs collaborateurs."
}, },
"category_selfpromo_guideline1": {
"message": "Dons, abonnements (payant) et produits dérivés"
},
"category_selfpromo_guideline2": {
"message": "Remerciements gratuits qui n'apportent aucune information à la vidéo"
},
"category_selfpromo_guideline3": {
"message": "Pas pour des produits dérivés fait par des marques"
},
"category_exclusive_access": { "category_exclusive_access": {
"message": "Accès exclusif" "message": "Accès exclusif"
}, },
@ -564,12 +588,24 @@
"message": "Cette vidéo présente un produit, un service ou un lieu pour lequel un accès gratuit ou subventionné a été reçu", "message": "Cette vidéo présente un produit, un service ou un lieu pour lequel un accès gratuit ou subventionné a été reçu",
"description": "Short description for this category" "description": "Short description for this category"
}, },
"category_exclusive_access_guideline1": {
"message": "Toute la vidéo présente quelque chose dont le créateur a eu un accès gratuit ou subventionné"
},
"category_interaction": { "category_interaction": {
"message": "Rappel d'interaction (abonnement)" "message": "Rappel d'interaction (abonnement)"
}, },
"category_interaction_description": { "category_interaction_description": {
"message": "Lorsqu'il y a un bref rappel pour aimer, s'abonner ou les suivre parmi le contenu. Si le message est long ou porte sur quelque chose de spécifique, cela devrait plutôt être classé comme une autopromotion." "message": "Lorsqu'il y a un bref rappel pour aimer, s'abonner ou les suivre parmi le contenu. Si le message est long ou porte sur quelque chose de spécifique, cela devrait plutôt être classé comme une autopromotion."
}, },
"category_interaction_guideline1": {
"message": "Rappels courts à like, s'abonner ou suivre"
},
"category_interaction_guideline2": {
"message": "Inclut des rappels indirects à commenter"
},
"category_interaction_guideline3": {
"message": "Pas pour la promotion générale, seulement les appels à l'interaction"
},
"category_interaction_short": { "category_interaction_short": {
"message": "Rappel d'interaction" "message": "Rappel d'interaction"
}, },
@ -582,18 +618,36 @@
"category_intro_short": { "category_intro_short": {
"message": "Entracte" "message": "Entracte"
}, },
"category_intro_guideline1": {
"message": "Intervalle sans contenu réel"
},
"category_intro_guideline2": {
"message": "Pas pour les transitions avec des informations"
},
"category_outro": { "category_outro": {
"message": "Générique de fin" "message": "Générique de fin"
}, },
"category_outro_description": { "category_outro_description": {
"message": "Crédits ou écrans de fin YouTube. Pas pour les conclusions contenant des informations." "message": "Crédits ou écrans de fin YouTube. Pas pour les conclusions contenant des informations."
}, },
"category_outro_guideline1": {
"message": "Ne dois pas inclure de contenu, même si les écrans de fin sont apparus"
},
"category_preview": { "category_preview": {
"message": "Aperçu/Résumé" "message": "Aperçu/Résumé"
}, },
"category_preview_description": { "category_preview_description": {
"message": "Résumé rapide des épisodes précédents, ou aperçu de ce qui se passera plus tard dans la vidéo en cours. Pour les plans collectifs édités, pas pour les résumés parlés." "message": "Résumé rapide des épisodes précédents, ou aperçu de ce qui se passera plus tard dans la vidéo en cours. Pour les plans collectifs édités, pas pour les résumés parlés."
}, },
"category_preview_guideline1": {
"message": "Clips apparaissant plus tard ou dans une prochaine vidéo"
},
"category_preview_guideline2": {
"message": "Récapitulatif d'une vidéo précédente"
},
"category_preview_guideline3": {
"message": "Pas pour les sections qui ajoutent du contenu supplémentaire"
},
"category_filler": { "category_filler": {
"message": "Digressions/Blagues" "message": "Digressions/Blagues"
}, },
@ -603,6 +657,15 @@
"category_filler_short": { "category_filler_short": {
"message": "Remplissage" "message": "Remplissage"
}, },
"category_filler_guideline1": {
"message": "Scènes digressives uniquement pour le remplissage ou l'humour"
},
"category_filler_guideline2": {
"message": "Distractions, bêtisiers, replays"
},
"category_filler_guideline3": {
"message": "Pas pour les scènes requises pour comprendre le sujet"
},
"category_music_offtopic": { "category_music_offtopic": {
"message": "Musique : Segment non musical" "message": "Musique : Segment non musical"
}, },
@ -612,12 +675,27 @@
"category_music_offtopic_short": { "category_music_offtopic_short": {
"message": "Hors musique" "message": "Hors musique"
}, },
"category_music_offtopic_guideline1": {
"message": "Sections qui ne sont pas dans la musique officielle"
},
"category_music_offtopic_guideline2": {
"message": "Pas de musique pendant les concerts en direct"
},
"category_poi_highlight": { "category_poi_highlight": {
"message": "Point essentiel" "message": "Point essentiel"
}, },
"category_poi_highlight_description": { "category_poi_highlight_description": {
"message": "La partie de la vidéo que la plupart des gens veulent voir. Similaire à \"la vidéo commence à x mins\"." "message": "La partie de la vidéo que la plupart des gens veulent voir. Similaire à \"la vidéo commence à x mins\"."
}, },
"category_poi_highlight_guideline1": {
"message": "Section la plus regardée"
},
"category_poi_highlight_guideline2": {
"message": "Peut sauter le contexte"
},
"category_poi_highlight_guideline3": {
"message": "Peut passer au sujet cité dans le titre ou la miniature"
},
"category_livestream_messages": { "category_livestream_messages": {
"message": "Stream : lecture de dons et messages" "message": "Stream : lecture de dons et messages"
}, },
@ -867,6 +945,9 @@
"LearnMore": { "LearnMore": {
"message": "En savoir plus" "message": "En savoir plus"
}, },
"FullDetails": {
"message": "Tous les détails"
},
"CopyDownvoteButtonInfo": { "CopyDownvoteButtonInfo": {
"message": "Vote contre et crée une copie locale à soumettre à nouveau" "message": "Vote contre et crée une copie locale à soumettre à nouveau"
}, },

View file

@ -186,7 +186,7 @@
"message": "Nasconde i pulsanti che appaiono sul video per inviare i segmenti da nascondere." "message": "Nasconde i pulsanti che appaiono sul video per inviare i segmenti da nascondere."
}, },
"showSkipButton": { "showSkipButton": {
"message": "Mantieni Salta Per Evidenziare il Pulsante Sul Lettore" "message": "Mantieni l'Highlight del Video sulla Barra del Video"
}, },
"showInfoButton": { "showInfoButton": {
"message": "Mostra il pulsante delle informazioni sopra al video" "message": "Mostra il pulsante delle informazioni sopra al video"
@ -216,7 +216,7 @@
"message": "Memorizza i voti negativi del segmento" "message": "Memorizza i voti negativi del segmento"
}, },
"whatTrackDownvotes": { "whatTrackDownvotes": {
"message": "Qualsiasi segmento voti negativamente rimarrà nascosto anche dopo aver ricaricato" "message": "Segmenti votati negativamente rimarranno nascosti anche dopo aver ricaricato la pagina"
}, },
"trackDownvotesWarning": { "trackDownvotesWarning": {
"message": "Attenzione: Disabilitarlo eliminerà tutti i voti negativi precedentemente memorizzati" "message": "Attenzione: Disabilitarlo eliminerà tutti i voti negativi precedentemente memorizzati"
@ -282,7 +282,7 @@
"description": "Keybind label" "description": "Keybind label"
}, },
"setStartSponsorShortcut": { "setStartSponsorShortcut": {
"message": "Inizio/Fine segmento", "message": "Inizia/Finisci segmento",
"description": "Keybind label" "description": "Keybind label"
}, },
"setSubmitKeybind": { "setSubmitKeybind": {
@ -290,7 +290,7 @@
"description": "Keybind label" "description": "Keybind label"
}, },
"keybindDescription": { "keybindDescription": {
"message": "Selezionare una chiave digitandola e scegliere qualsiasi tasto modificatore che si desidera utilizzare." "message": "Seleziona un tasto digitandolo e scegli qualsiasi tasto modificatore che desideri utilizzare."
}, },
"0": { "0": {
"message": "Timeout della connessione. Controlla la tua connessione a Internet. Se internet sta funzionando, il server è probabilmente sovraccarico oppure giù." "message": "Timeout della connessione. Controlla la tua connessione a Internet. Se internet sta funzionando, il server è probabilmente sovraccarico oppure giù."
@ -328,7 +328,7 @@
"message": "Silenziare {0}?" "message": "Silenziare {0}?"
}, },
"skip_to_category": { "skip_to_category": {
"message": "Saltare a {0}?", "message": "Salta a {0}?",
"description": "Used for skipping to things (Skip to Highlight)" "description": "Used for skipping to things (Skip to Highlight)"
}, },
"skipped": { "skipped": {
@ -595,7 +595,7 @@
"message": "Promemoria d'Interazione (Iscrizione)" "message": "Promemoria d'Interazione (Iscrizione)"
}, },
"category_interaction_description": { "category_interaction_description": {
"message": "Quando nel punto centrale del contenuto è presente un breve promemoria per aggiunta di mi piace, iscrizione o seguito. Se dovesse risultare esteso o riguardare qualcosa di specifico, potrebbe essere un'autopromozione." "message": "Quando nel punto centrale del contenuto è presente un breve promemoria per like, iscrizione o follow. Se dovesse risultare esteso o riguardante qualcosa di specifico, potrebbe essere auto-promozione."
}, },
"category_interaction_guideline1": { "category_interaction_guideline1": {
"message": "Brevi promemoria per mi piace, iscrizioni o follow" "message": "Brevi promemoria per mi piace, iscrizioni o follow"
@ -607,16 +607,16 @@
"message": "Non per promozione generale, solo chiamata all'azione" "message": "Non per promozione generale, solo chiamata all'azione"
}, },
"category_interaction_short": { "category_interaction_short": {
"message": "Promemoria di Interazione" "message": "Promemoria d'Interazione"
}, },
"category_intro": { "category_intro": {
"message": "Animazione Interruzione/Introduzione" "message": "Intermezzo/Intro Animata"
}, },
"category_intro_description": { "category_intro_description": {
"message": "Un intervallo senza contenuto effettivo. Potrebbe essere una pausa, una schermata statica, un'animazione ripetuta. Non dovrebbe essere usato per transizioni contenenti informazioni." "message": "Un intervallo senza contenuto effettivo. Potrebbe essere una pausa, una schermata statica, un'animazione ripetuta. Non dovrebbe essere usato per transizioni contenenti informazioni."
}, },
"category_intro_short": { "category_intro_short": {
"message": "Interruzione" "message": "Intermezzo"
}, },
"category_intro_guideline1": { "category_intro_guideline1": {
"message": "Intervallo senza contenuto effettivo" "message": "Intervallo senza contenuto effettivo"
@ -655,10 +655,10 @@
"message": "Le scene riempitive sono aggiunte solo per riempire o per umorismo che non sono richieste per comprendere il contenuto principale del video. Questo non dovrebbe includere segmenti che forniscono contesto o dettagli di sfondo." "message": "Le scene riempitive sono aggiunte solo per riempire o per umorismo che non sono richieste per comprendere il contenuto principale del video. Questo non dovrebbe includere segmenti che forniscono contesto o dettagli di sfondo."
}, },
"category_filler_short": { "category_filler_short": {
"message": "Riempimento" "message": "Filler"
}, },
"category_filler_guideline1": { "category_filler_guideline1": {
"message": "Scene non correlate usate solo per riempimenti o umorismo" "message": "Scene non correlate usate solo per filler o umorismo"
}, },
"category_filler_guideline2": { "category_filler_guideline2": {
"message": "Distrazioni, blooper, replay" "message": "Distrazioni, blooper, replay"
@ -682,13 +682,13 @@
"message": "Sezioni senza musica in una performance dal vivo" "message": "Sezioni senza musica in una performance dal vivo"
}, },
"category_poi_highlight": { "category_poi_highlight": {
"message": "Evidenzia" "message": "Highlight"
}, },
"category_poi_highlight_description": { "category_poi_highlight_description": {
"message": "La parte del video che gran parte delle persone stanno cercando. Simile ai commenti \"Il video inizia a x\"." "message": "La parte del video che gran parte delle persone stanno cercando. Simile ai commenti \"Il video inizia a x\"."
}, },
"category_poi_highlight_guideline1": { "category_poi_highlight_guideline1": {
"message": "La sezione che la maggior parte delle persone sta cercando" "message": "La parte che la maggior parte delle persone sta cercando"
}, },
"category_poi_highlight_guideline2": { "category_poi_highlight_guideline2": {
"message": "Può ignorare il contesto" "message": "Può ignorare il contesto"
@ -816,7 +816,7 @@
"message": "Forza controllo canale prima di andare avanti" "message": "Forza controllo canale prima di andare avanti"
}, },
"whatForceChannelCheck": { "whatForceChannelCheck": {
"message": "Per impostazione predefinita, si salteranno subito i segmenti prima che si sappia anche che canale è. Per impostazione predefinita, alcuni segmenti all'inizio del video potrebbero essere saltati sui canali sulla whitelist. Abilitare questa opzione impedirà questo, ma fare saltare tutti hanno un leggero ritardo in quanto ottenere il channelID può richiedere un certo tempo. Questo ritardo potrebbe essere invisibile se si dispone di internet veloce." "message": "Di default, verranno saltati i segmenti subito, anche prima che si sappia il canale. Di default, alcuni segmenti all'inizio del video potrebbero essere saltati sui canali nella whitelist. L'attivazione di questa opzione eviterà che ciò accada, ma ogni salto avrà un leggero ritardo in quanto ottenere l'ID del canale può richiedere un certo tempo. Questo ritardo potrebbe essere impercettibile se si dispone di una connessione internet veloce."
}, },
"forceChannelCheckPopup": { "forceChannelCheckPopup": {
"message": "Considera l'Attivazione dell'opzione \"Forza la Verifica del Canale Prima del Salto\"" "message": "Considera l'Attivazione dell'opzione \"Forza la Verifica del Canale Prima del Salto\""

View file

@ -1 +1,25 @@
{} {
"fullName": {
"message": "SponsorBlock priekš YouTube - Izlaid sponsorus",
"description": "Name of the extension."
},
"Description": {
"message": "Izlaidiet sponsorus, abonēšanas lūgumus un vairāk, skatoties YouTube video. Ziņojiet par sponsoriem video, kurus jūs skatāties, lai ietaupītu citu laiku.",
"description": "Description of the extension."
},
"400": {
"message": "Serveris ziņo, ka šis pieprasījums ir nederīgs"
},
"429": {
"message": "Jūs esat aizsūtījis pārāk daudz sponsoru laika sprīžus šim video; vai esat pārliecināts, ka šeit ir tik daudz?"
},
"409": {
"message": "Šis jau ir ticis aizsūtīts iepriekš"
},
"channelWhitelisted": {
"message": "Kanāls iekļauts baltajā sarakstā!"
},
"Segment": {
"message": "segments"
}
}

View file

@ -53,7 +53,7 @@
"message": "Pomiń" "message": "Pomiń"
}, },
"unmute": { "unmute": {
"message": "Odcisz" "message": "Anuluj wyciszenie"
}, },
"paused": { "paused": {
"message": "Zatrzymany" "message": "Zatrzymany"
@ -246,16 +246,16 @@
"message": "Pełnowymiarowe powiadomienia o przewinięciu" "message": "Pełnowymiarowe powiadomienia o przewinięciu"
}, },
"noticeVisibilityMode1": { "noticeVisibilityMode1": {
"message": "Małe powiadomienia o automatycznym pomijaniu" "message": "Małe powiadomienia o automatycznym przewinięciu"
}, },
"noticeVisibilityMode2": { "noticeVisibilityMode2": {
"message": "Małe powiadomienia o przewinięciu" "message": "Małe powiadomienia o przewinięciu"
}, },
"noticeVisibilityMode3": { "noticeVisibilityMode3": {
"message": "Znikające powiadomienia o automatycznym pomijaniu" "message": "Półprzezroczyste powiadomienie o automatycznym przewinięciu"
}, },
"noticeVisibilityMode4": { "noticeVisibilityMode4": {
"message": "Znikające powiadomienia o pomijaniu" "message": "Półprzezroczyste powiadomienie dla wszystkich przewinięć"
}, },
"longDescription": { "longDescription": {
"message": "SponsorBlock pozwala pomijać sponsorów, intra, outra, przypomnienia o subskrypcjach i inne irytujące fragmenty filmów na YouTube. SponsorBlock jest opartym na crowdsourcingu rozszerzeniem do przeglądarki, które pozwala każdemu zgłosić początek i koniec segmentów sponsorowanych oraz innych segmentów w filmach na YouTube. Kiedy ktoś już zamieści te informacje, wszyscy pozostali z tym rozszerzeniem będą pomijać segment sponsorowany. Możesz również pomijać fragmenty teledysków bez muzyki.", "message": "SponsorBlock pozwala pomijać sponsorów, intra, outra, przypomnienia o subskrypcjach i inne irytujące fragmenty filmów na YouTube. SponsorBlock jest opartym na crowdsourcingu rozszerzeniem do przeglądarki, które pozwala każdemu zgłosić początek i koniec segmentów sponsorowanych oraz innych segmentów w filmach na YouTube. Kiedy ktoś już zamieści te informacje, wszyscy pozostali z tym rozszerzeniem będą pomijać segment sponsorowany. Możesz również pomijać fragmenty teledysków bez muzyki.",
@ -549,7 +549,7 @@
"message": "Zawiera płynne przejścia" "message": "Zawiera płynne przejścia"
}, },
"generic_guideline2": { "generic_guideline2": {
"message": "Odtwarza się, jakby nic nie zostało pominięte" "message": "Pominięcie bez zauważalnego przeskoku"
}, },
"category_sponsor": { "category_sponsor": {
"message": "Sponsor" "message": "Sponsor"
@ -649,7 +649,7 @@
"message": "Nie dla sekcji, które zawierają potrzebne informacje" "message": "Nie dla sekcji, które zawierają potrzebne informacje"
}, },
"category_filler": { "category_filler": {
"message": "Wypełniacz nietematyczny/Żart" "message": "Wypełniacz nietematyczny/żart"
}, },
"category_filler_description": { "category_filler_description": {
"message": "Sceny nietematyczne dodawane tylko jako wypełniacz lub dla humoru, które nie są wymagane do zrozumienia głównej treści filmu. Nie powinno to obejmować segmentów zawierających informacje kontekstowe lub szczegółowe." "message": "Sceny nietematyczne dodawane tylko jako wypełniacz lub dla humoru, które nie są wymagane do zrozumienia głównej treści filmu. Nie powinno to obejmować segmentów zawierających informacje kontekstowe lub szczegółowe."
@ -664,7 +664,7 @@
"message": "Rozpraszacze, wpadki, powtórki" "message": "Rozpraszacze, wpadki, powtórki"
}, },
"category_filler_guideline3": { "category_filler_guideline3": {
"message": "Nie dla scen wymaganych, by zrozumieć temat" "message": "Nie nadaje się do scen wymaganych do zrozumienia tematu"
}, },
"category_music_offtopic": { "category_music_offtopic": {
"message": "Muzyka: Sekcja niemuzyczna" "message": "Muzyka: Sekcja niemuzyczna"
@ -691,10 +691,10 @@
"message": "Część filmu, której szuka większość osób" "message": "Część filmu, której szuka większość osób"
}, },
"category_poi_highlight_guideline2": { "category_poi_highlight_guideline2": {
"message": "Może pomijać kontekst" "message": "Może pomóc pominąć kontekst"
}, },
"category_poi_highlight_guideline3": { "category_poi_highlight_guideline3": {
"message": "Może pomijać do tytułu lub miniaturki" "message": "Może pominąć do karty tytułowej lub miniaturki"
}, },
"category_livestream_messages": { "category_livestream_messages": {
"message": "Transmisja live: Dotacja/Czytanie wiadomości" "message": "Transmisja live: Dotacja/Czytanie wiadomości"
@ -737,7 +737,7 @@
"description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo" "description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo"
}, },
"previewColor": { "previewColor": {
"message": "Nieprzesłany kolor", "message": "Kolor nieprzesłanego segmentu",
"description": "Referring to submissions that have not been sent to the server yet." "description": "Referring to submissions that have not been sent to the server yet."
}, },
"seekBarColor": { "seekBarColor": {
@ -1007,7 +1007,7 @@
"description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)." "description": "Appears in Options as a tab header for advanced/niche options. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
}, },
"noticeVisibilityLabel": { "noticeVisibilityLabel": {
"message": "Pomiń wygląd wpisu", "message": "Wygląd okna pomijania",
"description": "Option label" "description": "Option label"
}, },
"unbind": { "unbind": {

View file

@ -239,6 +239,9 @@
"showSkipNotice": { "showSkipNotice": {
"message": "Показувати сповіщення після пропуску сегмента" "message": "Показувати сповіщення після пропуску сегмента"
}, },
"showCategoryGuidelines": {
"message": "Показати Довідку по Категоріях"
},
"noticeVisibilityMode0": { "noticeVisibilityMode0": {
"message": "Повнорозмірні сповіщення про пропуски" "message": "Повнорозмірні сповіщення про пропуски"
}, },

View file

@ -239,6 +239,9 @@
"showSkipNotice": { "showSkipNotice": {
"message": "Hiển thị thông báo sau khi bỏ qua phân đoạn" "message": "Hiển thị thông báo sau khi bỏ qua phân đoạn"
}, },
"showCategoryGuidelines": {
"message": "Hiển thị Danh mục Trợ giúp"
},
"noticeVisibilityMode0": { "noticeVisibilityMode0": {
"message": "Thông báo bỏ qua với kích thước đầy đủ" "message": "Thông báo bỏ qua với kích thước đầy đủ"
}, },
@ -542,18 +545,36 @@
"message": "đến", "message": "đến",
"description": "Used between segments. Example: 1:20 to 1:30" "description": "Used between segments. Example: 1:20 to 1:30"
}, },
"generic_guideline2": {
"message": "Chơi như thể không có gì bị bỏ qua"
},
"category_sponsor": { "category_sponsor": {
"message": "Nhà tài trợ" "message": "Nhà tài trợ"
}, },
"category_sponsor_description": { "category_sponsor_description": {
"message": "Nội dung được trả tiền để quảng cáo, giới thiệu và quảng cáo trực tiếp. Không phải là quảng cáo không trả công hay được đề cập miễn phí." "message": "Nội dung được trả tiền để quảng cáo, giới thiệu và quảng cáo trực tiếp. Không phải là quảng cáo không trả công hay được đề cập miễn phí."
}, },
"category_sponsor_guideline1": {
"message": "Quảng cáo trả phí"
},
"category_sponsor_guideline2": {
"message": "Không dành cho các khoản đóng góp"
},
"category_selfpromo": { "category_selfpromo": {
"message": "Quảng cáo không trả công/Tự quảng cáo" "message": "Quảng cáo không trả công/Tự quảng cáo"
}, },
"category_selfpromo_description": { "category_selfpromo_description": {
"message": "Tương tự như 'nhà tài trợ' ngoại trừ việc quảng cáo không được trả tiền hay tự quảng cáo. Điều này bao gồm các phần hàng hóa, đóng góp, hoặc thông tin về người mà họ hợp tác cùng." "message": "Tương tự như 'nhà tài trợ' ngoại trừ việc quảng cáo không được trả tiền hay tự quảng cáo. Điều này bao gồm các phần hàng hóa, đóng góp, hoặc thông tin về người mà họ hợp tác cùng."
}, },
"category_selfpromo_guideline1": {
"message": "Quyên góp, tư cách thành viên và hàng hóa tùy chỉnh"
},
"category_selfpromo_guideline2": {
"message": "Lời cảm ơn miễn phí không thêm vào video"
},
"category_selfpromo_guideline3": {
"message": "Không dành cho các sản phẩm và hàng hóa do công ty thiết kế"
},
"category_exclusive_access": { "category_exclusive_access": {
"message": "Truy cập riêng" "message": "Truy cập riêng"
}, },
@ -564,12 +585,24 @@
"message": "Video này giới thiệu sản phẩm, dịch vụ hoặc vị trí mà họ đã nhận được quyền truy cập miễn phí hoặc được trợ cấp", "message": "Video này giới thiệu sản phẩm, dịch vụ hoặc vị trí mà họ đã nhận được quyền truy cập miễn phí hoặc được trợ cấp",
"description": "Short description for this category" "description": "Short description for this category"
}, },
"category_exclusive_access_guideline1": {
"message": "Toàn bộ video giới thiệu nội dung nào đó có quyền truy cập miễn phí hoặc được trợ cấp"
},
"category_interaction": { "category_interaction": {
"message": "Nhắc tương tác (Đăng ký)" "message": "Nhắc tương tác (Đăng ký)"
}, },
"category_interaction_description": { "category_interaction_description": {
"message": "Nhắc nhở người xem Thích, Đăng ký hoặc Theo dõi. Nếu nó dài hoặc là một cái gì cụ thể, nó nên là danh mục \"Tự quảng cáo\"." "message": "Nhắc nhở người xem Thích, Đăng ký hoặc Theo dõi. Nếu nó dài hoặc là một cái gì cụ thể, nó nên là danh mục \"Tự quảng cáo\"."
}, },
"category_interaction_guideline1": {
"message": "Lời nhắc ngắn gọn để thích, đăng ký hoặc theo dõi"
},
"category_interaction_guideline2": {
"message": "Bao gồm lời nhắc gián tiếp để nhận xét"
},
"category_interaction_guideline3": {
"message": "Không dành cho quảng cáo chung, chỉ dành cho lời kêu gọi hành động"
},
"category_interaction_short": { "category_interaction_short": {
"message": "Nhắc nhở tương tác" "message": "Nhắc nhở tương tác"
}, },
@ -582,18 +615,36 @@
"category_intro_short": { "category_intro_short": {
"message": "Tạm ngừng" "message": "Tạm ngừng"
}, },
"category_intro_guideline1": {
"message": "Khoảng thời gian không có nội dung thực tế"
},
"category_intro_guideline2": {
"message": "Không dành cho chuyển tiếp với thông tin"
},
"category_outro": { "category_outro": {
"message": "Màn hình kết thúc/Danh đề" "message": "Màn hình kết thúc/Danh đề"
}, },
"category_outro_description": { "category_outro_description": {
"message": "Credits hoặc khi thẻ màn hình kết thúc của YouTube xuất hiện. Không dùng với những đoạn có thông tin." "message": "Credits hoặc khi thẻ màn hình kết thúc của YouTube xuất hiện. Không dùng với những đoạn có thông tin."
}, },
"category_outro_guideline1": {
"message": "Không bao gồm nội dung, ngay cả khi thẻ kết thúc ở trên màn hình"
},
"category_preview": { "category_preview": {
"message": "Xem trước/Tóm tắt" "message": "Xem trước/Tóm tắt"
}, },
"category_preview_description": { "category_preview_description": {
"message": "Tóm tắt nhanh về tập trước/tập sau trong 1 chuỗi video (series) dài (hoặc cũng có thể là tóm tắt trước về video sắp chiếu)." "message": "Tóm tắt nhanh về tập trước/tập sau trong 1 chuỗi video (series) dài (hoặc cũng có thể là tóm tắt trước về video sắp chiếu)."
}, },
"category_preview_guideline1": {
"message": "Các clip xuất hiện sau đó hoặc trong một video trong tương lai"
},
"category_preview_guideline2": {
"message": "Tóm tắt video trước đó"
},
"category_preview_guideline3": {
"message": "Không dành cho các phần thêm nội dung bổ sung"
},
"category_filler_description": { "category_filler_description": {
"message": "Tập hợp các cảnh không bắt buộc để xem trong video. Điều này không bao gồm các đoạn chứa nội dung hoặc nói về ngữ cảnh của video." "message": "Tập hợp các cảnh không bắt buộc để xem trong video. Điều này không bao gồm các đoạn chứa nội dung hoặc nói về ngữ cảnh của video."
}, },

View file

@ -26,7 +26,7 @@
transition: transform .1s cubic-bezier(0,0,0.2,1); transition: transform .1s cubic-bezier(0,0,0.2,1);
} }
.ytm-progress-bar > #previewbar { .progress-bar-line > #previewbar {
height: 3px; height: 3px;
} }
@ -182,6 +182,223 @@ div:hover > .sponsorBlockChapterBar {
right: var(--skip-notice-right); right: var(--skip-notice-right);
} }
.sponsorSkipNoticeParent {
position: absolute;
bottom: 100px;
right: 10px;
}
.sponsorSkipNoticeParent, .sponsorSkipNotice {
border-spacing: 5px 10px;
padding-left: 5px;
padding-right: 5px;
border-collapse: unset;
}
.sponsorSkipNoticeParent {
min-width: 350px;
max-width: 50%;
}
.sponsorSkipNotice {
width: 100%;
}
.sponsorSkipNoticeTableContainer {
color: white;
background-color: rgba(28, 28, 28, 0.9);
border-radius: 5px;
min-width: 100%;
}
.sponsorSkipNotice {
transition: all 0.1s ease-out;
}
.sponsorSkipNoticeLimitWidth {
max-width: calc(100% - 50px);
}
.sponsorSkipNotice .hidden {
display: none;
}
/* For Cloudtube */
.sponsorSkipNotice td, .sponsorSkipNotice table, .sponsorSkipNotice th {
border: none;
}
.sponsorSkipNoticeFadeIn {
animation: fadeIn 0.5s ease-out;
}
.sponsorSkipNoticeFaded {
opacity: 0.5;
}
.sponsorSkipNoticeFadeOut {
transition: opacity 3s cubic-bezier(0.55, 0.055, 0.675, 0.19);
opacity: 0 !important;
animation: none !important;
}
.sponsorSkipNotice .sponsorSkipNoticeTimeLeft {
color: #eeeeee;
border-radius: 4px;
padding: 2px 5px;
font-size: 12px;
display: flex;
align-items: center;
border: 1px solid #eeeeee;
}
.sponsorSkipNoticeTimeLeft img {
vertical-align: middle;
height: 13px;
padding-top: 7.8%;
padding-bottom: 7.8%;
}
/* if two are very close to eachother */
.secondSkipNotice {
bottom: 290px;
}
.noticeLeftIcon {
display: flex;
align-items: center;
}
.sponsorSkipNotice .sponsorSkipNoticeUnskipSection {
float: left;
border-left: 1px solid rgb(150, 150, 150);
}
.sponsorSkipNoticeButton {
background: none;
color: rgb(235, 235, 235);
border: none;
display: inline-block;
font-size: 13.3333px !important;
cursor: pointer;
margin-right: 10px;
padding: 2px 5px;
}
.sponsorSkipNoticeButton:hover {
background-color: rgba(235, 235, 235,0.2);
border-radius: 4px;
transition: background-color 0.4s;
}
.sponsorTimesVoteButtonsContainer {
float: left;
vertical-align:middle;
padding: 2px 5px;
margin-right: 4px;
}
.sponsorTimesVoteButtonsContainer div{
display: inline-block;
}
.sponsorSkipNoticeRightSection {
right: 0;
position: absolute;
float: right;
margin-right: 10px;
display: flex;
align-items: center;
}
.sponsorSkipNoticeRightButton {
margin-right: 0;
}
.sponsorSkipNoticeCloseButton {
height: 10px;
width: 10px;
box-sizing: unset;
padding: 2px 5px;
margin-left: 2px;
float: right;
}
.sponsorSkipNoticeCloseButton.biggerCloseButton {
padding: 20px;
}
.sponsorSkipMessage {
font-size: 14px;
font-weight: bold;
color: rgb(235, 235, 235);
margin-top: auto;
display: inline-block;
margin-right: 10px;
margin-bottom: auto;
}
.sponsorSkipInfo {
font-size: 10px;
color: #000000;
text-align: center;
margin-top: 0px;
}
#sponsorTimesThanksForVotingText {
font-size: 20px;
font-weight: bold;
color: #000000;
text-align: center;
margin-top: 0px;
margin-bottom: 0px;
}
#sponsorTimesThanksForVotingInfoText {
font-size: 12px;
font-weight: bold;
color: #000000;
text-align: center;
margin-top: 0px;
}
.sponsorTimesVoteButtonMessage {
float: left;
}
.sponsorTimesInfoMessage {
font-size: 13.3333px;
color: rgb(235, 235, 235);
overflow-wrap: anywhere;
}
.sb-guidelines-notice .sponsorTimesInfoMessage td {
padding-left: 5px;
padding-top: 2px;
padding-bottom: 2px;
font-size: 15px;
display: flex;
align-items: center;
}
.sponsorTimesInfoIcon { .sponsorTimesInfoIcon {
width: 30px; width: 30px;
padding-right: 10px; padding-right: 10px;
@ -348,6 +565,11 @@ input::-webkit-inner-spin-button {
padding: 3px; padding: 3px;
} }
.sponsorTimeEditSelector > option {
background-color: rgba(28, 28, 28, 0.9);
color: white;
}
/* Start SelectorComponent */ /* Start SelectorComponent */
.sbSelector { .sbSelector {
@ -401,17 +623,6 @@ input::-webkit-inner-spin-button {
opacity: 0.8; opacity: 0.8;
} }
.sbChatNotice iframe {
height: 32px;
cursor: pointer;
height: 100%;
}
.sbChatClose {
height: 14px;
cursor: pointer;
}
.skipButtonControlBarContainer { .skipButtonControlBarContainer {
cursor: pointer; cursor: pointer;
display: flex; display: flex;

View file

@ -19,6 +19,12 @@
<br/> <br/>
<div class="center">
__MSG_invidiousPermissionRefresh__
</div>
<br/>
<div class="center"> <div class="center">
<div id="acceptPermissionButton" class="option-button inline"> <div id="acceptPermissionButton" class="option-button inline">
__MSG_acceptPermission__ __MSG_acceptPermission__

View file

@ -125,7 +125,7 @@ chrome.runtime.onConnect.addListener((port) => {
chrome.runtime.onInstalled.addListener(function () { chrome.runtime.onInstalled.addListener(function () {
// This let's the config sync to run fully before checking. // This let's the config sync to run fully before checking.
// This is required on Firefox // This is required on Firefox
setTimeout(function() { setTimeout(async () => {
const userID = Config.config.userID; const userID = Config.config.userID;
// If there is no userID, then it is the first install. // If there is no userID, then it is the first install.
@ -141,6 +141,12 @@ chrome.runtime.onInstalled.addListener(function () {
// Don't show update notification // Don't show update notification
Config.config.categoryPillUpdate = true; Config.config.categoryPillUpdate = true;
} }
if (Config.config.supportInvidious) {
if (!(await utils.containsInvidiousPermission())) {
chrome.tabs.create({url: chrome.extension.getURL("/permissions/index.html")});
}
}
}, 1500); }, 1500);
}); });
@ -224,7 +230,7 @@ async function asyncRequestToServer(type: string, address: string, data = {}) {
async function sendRequestToCustomServer(type: string, url: string, data = {}) { async function sendRequestToCustomServer(type: string, url: string, data = {}) {
// If GET, convert JSON to parameters // If GET, convert JSON to parameters
if (type.toLowerCase() === "get") { if (type.toLowerCase() === "get") {
url = utils.objectToURI(url, data, true); url = GenericUtils.objectToURI(url, data, true);
data = null; data = null;
} }

View file

@ -36,12 +36,31 @@ class NoticeTextSelectionComponent extends React.Component<NoticeTextSelectionPr
: null} : null}
<span> <span>
{this.props.text} {this.getTextElements(this.props.text)}
</span> </span>
</td> </td>
</tr> </tr>
); );
} }
private getTextElements(text: string): Array<string | React.ReactElement> {
const elements: Array<string | React.ReactElement> = [];
const textParts = text.split(/(?=\s+)/);
for (const textPart of textParts) {
if (textPart.match(/^\s*http/)) {
elements.push(
<a href={textPart} target="_blank" rel="noreferrer">
{textPart}
</a>
);
} else {
elements.push(textPart);
}
}
return elements;
}
} }
export default NoticeTextSelectionComponent; export default NoticeTextSelectionComponent;

View file

@ -115,14 +115,6 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
style.marginTop = "15px"; style.marginTop = "15px";
} }
// This method is required to get !important
// https://stackoverflow.com/a/45669262/1985387
const oldYouTubeDarkStyles = (node) => {
if (node) {
node.style.setProperty("color", "black", "important");
node.style.setProperty("text-shadow", "none", "important");
}
};
// Create time display // Create time display
let timeDisplay: JSX.Element; let timeDisplay: JSX.Element;
const timeDisplayStyle: React.CSSProperties = {}; const timeDisplayStyle: React.CSSProperties = {};
@ -142,8 +134,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
</span> </span>
<input id={"submittingTime0" + this.idSuffix} <input id={"submittingTime0" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditInput" className="sponsorTimeEdit sponsorTimeEditInput"
ref={oldYouTubeDarkStyles}
type="text" type="text"
style={{color: "inherit", backgroundColor: "inherit"}}
value={this.state.sponsorTimeEdits[0]} value={this.state.sponsorTimeEdits[0]}
onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)} onChange={(e) => this.handleOnChange(0, e, sponsorTime, e.target.value)}
onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}> onWheel={(e) => this.changeTimesWhenScrolling(0, e, sponsorTime)}>
@ -157,8 +149,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
<input id={"submittingTime1" + this.idSuffix} <input id={"submittingTime1" + this.idSuffix}
className="sponsorTimeEdit sponsorTimeEditInput" className="sponsorTimeEdit sponsorTimeEditInput"
ref={oldYouTubeDarkStyles}
type="text" type="text"
style={{color: "inherit", backgroundColor: "inherit"}}
value={this.state.sponsorTimeEdits[1]} value={this.state.sponsorTimeEdits[1]}
onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)} onChange={(e) => this.handleOnChange(1, e, sponsorTime, e.target.value)}
onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}> onWheel={(e) => this.changeTimesWhenScrolling(1, e, sponsorTime)}>
@ -204,6 +196,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
className="sponsorTimeEditSelector sponsorTimeCategories" className="sponsorTimeEditSelector sponsorTimeCategories"
defaultValue={sponsorTime.category} defaultValue={sponsorTime.category}
ref={this.categoryOptionRef} ref={this.categoryOptionRef}
style={{color: "inherit", backgroundColor: "inherit"}}
onChange={(event) => this.categorySelectionChange(event)}> onChange={(event) => this.categorySelectionChange(event)}>
{this.getCategoryOptions()} {this.getCategoryOptions()}
</select> </select>
@ -227,6 +220,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
<select id={"sponsorTimeActionTypes" + this.idSuffix} <select id={"sponsorTimeActionTypes" + this.idSuffix}
className="sponsorTimeEditSelector sponsorTimeActionTypes" className="sponsorTimeEditSelector sponsorTimeActionTypes"
defaultValue={sponsorTime.actionType} defaultValue={sponsorTime.actionType}
style={{color: "inherit", backgroundColor: "inherit"}}
ref={this.actionTypeOptionRef} ref={this.actionTypeOptionRef}
onChange={(e) => this.actionTypeSelectionChange(e)}> onChange={(e) => this.actionTypeSelectionChange(e)}>
{this.getActionTypeOptions(sponsorTime)} {this.getActionTypeOptions(sponsorTime)}

View file

@ -116,10 +116,10 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
skipOptionSelected(event: React.ChangeEvent<HTMLSelectElement>): void { skipOptionSelected(event: React.ChangeEvent<HTMLSelectElement>): void {
let option: CategorySkipOption; let option: CategorySkipOption;
this.removeCurrentCategorySelection();
switch (event.target.value) { switch (event.target.value) {
case "disable": case "disable":
Config.config.categorySelections = Config.config.categorySelections.filter(
categorySelection => categorySelection.name !== this.props.category);
return; return;
case "showOverlay": case "showOverlay":
option = CategorySkipOption.ShowOverlay; option = CategorySkipOption.ShowOverlay;
@ -135,28 +135,17 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
break; break;
} }
const existingSelection = Config.config.categorySelections.find(selection => selection.name === this.props.category);
if (existingSelection) {
existingSelection.option = option;
} else {
Config.config.categorySelections.push({ Config.config.categorySelections.push({
name: this.props.category, name: this.props.category,
option: option option: option
}); });
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
} }
/** Removes this category from the config list of category selections */ Config.forceSyncUpdate("categorySelections");
removeCurrentCategorySelection(): void {
// Remove it if it exists
for (let i = 0; i < Config.config.categorySelections.length; i++) {
if (Config.config.categorySelections[i].name === this.props.category) {
Config.config.categorySelections.splice(i, 1);
// Forces the Proxy to send this to the chrome storage API
Config.config.categorySelections = Config.config.categorySelections;
break;
}
}
} }
getCategorySkipOptions(): JSX.Element[] { getCategorySkipOptions(): JSX.Element[] {

View file

@ -10,7 +10,6 @@ import SkipNotice from "./render/SkipNotice";
import SkipNoticeComponent from "./components/SkipNoticeComponent"; import SkipNoticeComponent from "./components/SkipNoticeComponent";
import SubmissionNotice from "./render/SubmissionNotice"; import SubmissionNotice from "./render/SubmissionNotice";
import { Message, MessageResponse, VoteResponse } from "./messageTypes"; import { Message, MessageResponse, VoteResponse } from "./messageTypes";
import * as Chat from "./js-components/chat";
import { SkipButtonControlBar } from "./js-components/skipButtonControlBar"; import { SkipButtonControlBar } from "./js-components/skipButtonControlBar";
import { getStartTimeFromUrl } from "./utils/urlParser"; import { getStartTimeFromUrl } from "./utils/urlParser";
import { findValidElement, getControls, getExistingChapters, getHashParams, isVisible } from "./utils/pageUtils"; import { findValidElement, getControls, getExistingChapters, getHashParams, isVisible } from "./utils/pageUtils";
@ -20,6 +19,7 @@ import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
import { logDebug } from "./utils/logger"; import { logDebug } from "./utils/logger";
import { importTimes } from "./utils/exporter"; import { importTimes } from "./utils/exporter";
import { openWarningDialog } from "./utils/warnings";
// Hack to get the CSS loaded on permission-based sites (Invidious) // Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS); utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@ -62,13 +62,14 @@ let sponsorSkipped: boolean[] = [];
let video: HTMLVideoElement; let video: HTMLVideoElement;
let videoMuted = false; // Has it been attempted to be muted let videoMuted = false; // Has it been attempted to be muted
let videoMutationObserver: MutationObserver = null; let videoMutationObserver: MutationObserver = null;
let waitingForNewVideo = false;
// List of videos that have had event listeners added to them // List of videos that have had event listeners added to them
const videosWithEventListeners: HTMLVideoElement[] = []; const videosWithEventListeners: HTMLVideoElement[] = [];
const controlsWithEventListeners: HTMLElement[] = [] const controlsWithEventListeners: HTMLElement[] = []
// This misleading variable name will be fixed soon // This misleading variable name will be fixed soon
let onInvidious; let onInvidious: boolean;
let onMobileYouTube; let onMobileYouTube: boolean;
//the video id of the last preview bar update //the video id of the last preview bar update
let lastPreviewBarUpdate; let lastPreviewBarUpdate;
@ -76,9 +77,6 @@ let lastPreviewBarUpdate;
// Is the video currently being switched // Is the video currently being switched
let switchingVideos = null; let switchingVideos = null;
// Made true every videoID change
let firstEvent = false;
// Used by the play and playing listeners to make sure two aren't // Used by the play and playing listeners to make sure two aren't
// called at the same time // called at the same time
let lastCheckTime = 0; let lastCheckTime = 0;
@ -102,7 +100,8 @@ const playerButtons: Record<string, {button: HTMLButtonElement, image: HTMLImage
// Direct Links after the config is loaded // Direct Links after the config is loaded
utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document))); utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYouTubeVideoID(document)));
// wait for hover preview to appear, and refresh attachments if ever found // wait for hover preview to appear, and refresh attachments if ever found
window.addEventListener("DOMContentLoaded", () => utils.waitForElement(".ytp-inline-preview-ui").then(() => refreshVideoAttachments())); utils.waitForElement(".ytp-inline-preview-ui").then(() => refreshVideoAttachments())
utils.waitForElement("a.ytp-title-link[data-sessionlink='feature=player-title']").then(() => videoIDChange(getYouTubeVideoID(document)).then())
addPageListeners(); addPageListeners();
addHotkeyListener(); addHotkeyListener();
@ -119,6 +118,9 @@ let submissionNotice: SubmissionNotice = null;
// If there is an advert playing (or about to be played), this is true // If there is an advert playing (or about to be played), this is true
let isAdPlaying = false; let isAdPlaying = false;
let lastResponseStatus: number;
let retryCount = 0;
// Contains all of the functions and variables needed by the skip notice // Contains all of the functions and variables needed by the skip notice
const skipNoticeContentContainer: ContentContainer = () => ({ const skipNoticeContentContainer: ContentContainer = () => ({
vote, vote,
@ -166,6 +168,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
//send the sponsor times along with if it's found //send the sponsor times along with if it's found
sendResponse({ sendResponse({
found: sponsorDataFound, found: sponsorDataFound,
status: lastResponseStatus,
sponsorTimes: sponsorTimes, sponsorTimes: sponsorTimes,
time: video.currentTime, time: video.currentTime,
onMobileYouTube onMobileYouTube
@ -206,8 +209,12 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
submitSponsorTimes(); submitSponsorTimes();
break; break;
case "refreshSegments": case "refreshSegments":
// update video on refresh if videoID invalid
if (!sponsorVideoID) videoIDChange(getYouTubeVideoID(document));
// fetch segments
sponsorsLookup(false).then(() => sendResponse({ sponsorsLookup(false).then(() => sendResponse({
found: sponsorDataFound, found: sponsorDataFound,
status: lastResponseStatus,
sponsorTimes: sponsorTimes, sponsorTimes: sponsorTimes,
time: video.currentTime, time: video.currentTime,
onMobileYouTube onMobileYouTube
@ -301,6 +308,7 @@ if (!Config.configSyncListeners.includes(contentConfigUpdateListener)) {
function resetValues() { function resetValues() {
lastCheckTime = 0; lastCheckTime = 0;
lastCheckVideoTime = -1; lastCheckVideoTime = -1;
retryCount = 0;
sponsorTimes = []; sponsorTimes = [];
existingChaptersImported = false; existingChaptersImported = false;
@ -330,8 +338,6 @@ function resetValues() {
logDebug("Setting switching videos to true (reset data)"); logDebug("Setting switching videos to true (reset data)");
} }
firstEvent = true;
// Reset advert playing flag // Reset advert playing flag
isAdPlaying = false; isAdPlaying = false;
@ -343,7 +349,7 @@ function resetValues() {
categoryPill?.setVisibility(false); categoryPill?.setVisibility(false);
} }
async function videoIDChange(id) { async function videoIDChange(id): Promise<void> {
//if the id has not changed return unless the video element has changed //if the id has not changed return unless the video element has changed
if (sponsorVideoID === id && (isVisible(video) || !video)) return; if (sponsorVideoID === id && (isVisible(video) || !video)) return;
@ -447,7 +453,7 @@ function createPreviewBar(): void {
isVisibleCheck: true isVisibleCheck: true
}, { }, {
// For new mobile YouTube (#1287) // For new mobile YouTube (#1287)
selector: ".ytm-progress-bar", selector: ".progress-bar-line",
isVisibleCheck: true isVisibleCheck: true
}, { }, {
// For Desktop YouTube // For Desktop YouTube
@ -527,6 +533,13 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
return; return;
} }
// ensure we are on the correct video
const newVideoID = getYouTubeVideoID(document);
if (newVideoID !== sponsorVideoID) {
videoIDChange(newVideoID);
return;
}
logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`); logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`);
if (!video) return; if (!video) return;
if (currentTime === undefined || currentTime === null) { if (currentTime === undefined || currentTime === null) {
@ -537,7 +550,16 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
updateActiveSegment(currentTime); updateActiveSegment(currentTime);
if (video.paused) return; if (video.paused) return;
if (videoMuted && !inMuteSegment(currentTime)) { const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
const currentSkip = skipInfo.array[skipInfo.index];
const skipTime: number[] = [currentSkip?.scheduledTime, skipInfo.array[skipInfo.endIndex]?.segment[1]];
const timeUntilSponsor = skipTime?.[0] - currentTime;
const videoID = sponsorVideoID;
const skipBuffer = 0.003;
if (videoMuted && !inMuteSegment(currentTime, skipInfo.index !== -1
&& timeUntilSponsor < skipBuffer && shouldAutoSkip(currentSkip))) {
video.muted = false; video.muted = false;
videoMuted = false; videoMuted = false;
@ -547,22 +569,15 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
} }
} }
logDebug(`Ready to start skipping: ${skipInfo.index} at ${currentTime}`);
if (skipInfo.index === -1) return;
if (Config.config.disableSkipping || channelWhitelisted || (channelIDInfo.status === ChannelIDStatus.Fetching && Config.config.forceChannelCheck)){ if (Config.config.disableSkipping || channelWhitelisted || (channelIDInfo.status === ChannelIDStatus.Fetching && Config.config.forceChannelCheck)){
return; return;
} }
if (incorrectVideoCheck()) return; if (incorrectVideoCheck()) return;
const skipInfo = getNextSkipIndex(currentTime, includeIntersectingSegments, includeNonIntersectingSegments);
logDebug(`Ready to start skipping: ${skipInfo.index} at ${currentTime}`);
if (skipInfo.index === -1) return;
const currentSkip = skipInfo.array[skipInfo.index];
const skipTime: number[] = [currentSkip.scheduledTime, skipInfo.array[skipInfo.endIndex].segment[1]];
const timeUntilSponsor = skipTime[0] - currentTime;
const videoID = sponsorVideoID;
// Find all indexes in between the start and end // Find all indexes in between the start and end
let skippingSegments = [skipInfo.array[skipInfo.index]]; let skippingSegments = [skipInfo.array[skipInfo.index]];
if (skipInfo.index !== skipInfo.endIndex) { if (skipInfo.index !== skipInfo.endIndex) {
@ -576,7 +591,11 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
} }
} }
const skipBuffer = 0.003; logDebug(`Next step in starting skipping: ${!shouldSkip(currentSkip)}, ${!sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)}`);
// Don't skip if this category should not be skipped
if (!shouldSkip(currentSkip) && !sponsorTimesSubmitting?.some((segment) => segment.segment === currentSkip.segment)) return;
const skippingFunction = (forceVideoTime?: number) => { const skippingFunction = (forceVideoTime?: number) => {
let forcedSkipTime: number = null; let forcedSkipTime: number = null;
let forcedIncludeIntersectingSegments = false; let forcedIncludeIntersectingSegments = false;
@ -594,6 +613,19 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
openNotice: skipInfo.openNotice openNotice: skipInfo.openNotice
}); });
// These are segments that start at the exact same time but need seperate notices
for (const extra of skipInfo.extraIndexes) {
const extraSkip = skipInfo.array[extra];
if (shouldSkip(extraSkip)) {
skipToTime({
v: video,
skipTime: [extraSkip.scheduledTime, extraSkip.segment[1]],
skippingSegments: [extraSkip],
openNotice: skipInfo.openNotice
});
}
}
if (utils.getCategorySelection(currentSkip.category)?.option === CategorySkipOption.ManualSkip if (utils.getCategorySelection(currentSkip.category)?.option === CategorySkipOption.ManualSkip
|| currentSkip.actionType === ActionType.Mute) { || currentSkip.actionType === ActionType.Mute) {
forcedSkipTime = skipTime[0] + 0.001; forcedSkipTime = skipTime[0] + 0.001;
@ -649,15 +681,17 @@ function getVirtualTime(): number {
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null); (performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
if ((lastTimeFromWaitingEvent || !utils.isFirefox()) if ((lastTimeFromWaitingEvent || !utils.isFirefox())
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6) { && !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6 && video.currentTime !== 0) {
return virtualTime; return virtualTime;
} else { } else {
return video.currentTime; return video.currentTime;
} }
} }
function inMuteSegment(currentTime: number): boolean { function inMuteSegment(currentTime: number, includeOverlap: boolean): boolean {
const checkFunction = (segment) => segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime; const checkFunction = (segment) => segment.actionType === ActionType.Mute
&& segment.segment[0] <= currentTime
&& (segment.segment[1] > currentTime || (includeOverlap && segment.segment[1] + 0.02 > currentTime));
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction); return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
} }
@ -695,11 +729,14 @@ function setupVideoMutationListener() {
}); });
} }
function refreshVideoAttachments() { async function refreshVideoAttachments(): Promise<void> {
const newVideo = findValidElement(document.querySelectorAll('video')) as HTMLVideoElement; if (waitingForNewVideo) return;
if (newVideo && newVideo !== video) {
video = newVideo;
waitingForNewVideo = true;
const newVideo = await utils.waitForElement("video", true) as HTMLVideoElement;
waitingForNewVideo = false;
video = newVideo;
if (!videosWithEventListeners.includes(video)) { if (!videosWithEventListeners.includes(video)) {
videosWithEventListeners.push(video); videosWithEventListeners.push(video);
@ -708,14 +745,14 @@ function refreshVideoAttachments() {
setupCategoryPill(); setupCategoryPill();
} }
// Create a new bar in the new video element
if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) { if (previewBar && !utils.findReferenceNode()?.contains(previewBar.container)) {
previewBar.remove(); previewBar.remove();
previewBar = null; previewBar = null;
createPreviewBar(); createPreviewBar();
} }
}
videoIDChange(getYouTubeVideoID(document));
} }
function setupVideoListeners() { function setupVideoListeners() {
@ -727,17 +764,18 @@ function setupVideoListeners() {
switchingVideos = false; switchingVideos = false;
let startedWaiting = false; let startedWaiting = false;
let lastPausedAtZero = true;
video.addEventListener('play', () => { video.addEventListener('play', () => {
// If it is not the first event, then the only way to get to 0 is if there is a seek event // If it is not the first event, then the only way to get to 0 is if there is a seek event
// This check makes sure that changing the video resolution doesn't cause the extension to think it // This check makes sure that changing the video resolution doesn't cause the extension to think it
// gone back to the begining // gone back to the begining
if (!firstEvent && video.currentTime === 0) return; if (video.readyState <= HTMLMediaElement.HAVE_CURRENT_DATA
firstEvent = false; && video.currentTime === 0) return;
updateVirtualTime(); updateVirtualTime();
if (switchingVideos) { if (switchingVideos || lastPausedAtZero) {
switchingVideos = false; switchingVideos = false;
logDebug("Setting switching videos to false"); logDebug("Setting switching videos to false");
@ -745,6 +783,8 @@ function setupVideoListeners() {
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors(); if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
} }
lastPausedAtZero = false;
// Check if an ad is playing // Check if an ad is playing
updateAdFlag(); updateAdFlag();
@ -760,6 +800,7 @@ function setupVideoListeners() {
}); });
video.addEventListener('playing', () => { video.addEventListener('playing', () => {
updateVirtualTime(); updateVirtualTime();
lastPausedAtZero = false;
if (startedWaiting) { if (startedWaiting) {
startedWaiting = false; startedWaiting = false;
@ -767,6 +808,14 @@ function setupVideoListeners() {
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`); || (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`);
} }
if (switchingVideos) {
switchingVideos = false;
logDebug("Setting switching videos to false");
// If already segments loaded before video, retry to skip starting segments
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
}
// Make sure it doesn't get double called with the play event // Make sure it doesn't get double called with the play event
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3 if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) { || (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)) {
@ -788,6 +837,10 @@ function setupVideoListeners() {
startSponsorSchedule(); startSponsorSchedule();
} else { } else {
updateActiveSegment(video.currentTime); updateActiveSegment(video.currentTime);
if (video.currentTime === 0) {
lastPausedAtZero = true;
}
} }
}); });
video.addEventListener('ratechange', () => startSponsorSchedule()); video.addEventListener('ratechange', () => startSponsorSchedule());
@ -867,7 +920,6 @@ async function sponsorsLookup(keepOldSubmissions = true) {
const hashParams = getHashParams(); const hashParams = getHashParams();
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment; if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
// Check for hashPrefix setting
const hashPrefix = (await utils.getHash(sponsorVideoID, 1)).slice(0, 4) as VideoID & HashedValue; const hashPrefix = (await utils.getHash(sponsorVideoID, 1)).slice(0, 4) as VideoID & HashedValue;
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories, categories,
@ -876,6 +928,9 @@ async function sponsorsLookup(keepOldSubmissions = true) {
...extraRequestData ...extraRequestData
}); });
// store last response status
lastResponseStatus = response?.status;
if (response?.ok) { if (response?.ok) {
const recievedSegments: SponsorTime[] = JSON.parse(response.responseText) const recievedSegments: SponsorTime[] = JSON.parse(response.responseText)
?.filter((video) => video.videoID === sponsorVideoID) ?.filter((video) => video.videoID === sponsorVideoID)
@ -887,7 +942,7 @@ async function sponsorsLookup(keepOldSubmissions = true) {
?.sort((a, b) => a.segment[0] - b.segment[0]); ?.sort((a, b) => a.segment[0] - b.segment[0]);
if (!recievedSegments || !recievedSegments.length) { if (!recievedSegments || !recievedSegments.length) {
// return if no video found // return if no video found
retryFetch(); retryFetch(404);
return; return;
} }
@ -949,8 +1004,8 @@ async function sponsorsLookup(keepOldSubmissions = true) {
//otherwise the listener can handle it //otherwise the listener can handle it
updatePreviewBar(); updatePreviewBar();
} }
} else if (response?.status === 404) { } else {
retryFetch(); retryFetch(lastResponseStatus);
} }
importExistingChapters(true); importExistingChapters(true);
@ -999,17 +1054,24 @@ async function lockedCategoriesLookup(): Promise<void> {
} }
} }
function retryFetch(): void { function retryFetch(errorCode: number): void {
if (!Config.config.refetchWhenNotFound) return; if (!Config.config.refetchWhenNotFound) return;
sponsorDataFound = false; sponsorDataFound = false;
if (errorCode !== 404 && retryCount > 1) {
// Too many errors (50x), give up
return;
}
retryCount++;
const delay = errorCode === 404 ? (10000 + Math.random() * 30000) : (2000 + Math.random() * 10000);
setTimeout(() => { setTimeout(() => {
if (sponsorVideoID && sponsorTimes?.length === 0 if (sponsorVideoID && sponsorTimes?.length === 0
|| sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) { || sponsorTimes.every((segment) => segment.source !== SponsorSourceType.Server)) {
sponsorsLookup(); sponsorsLookup();
} }
}, 10000 + Math.random() * 30000); }, delay);
} }
/** /**
@ -1073,28 +1135,29 @@ function startSkipScheduleCheckingForStartSponsors() {
} }
} }
function getYouTubeVideoID(document: Document): string | boolean { function getYouTubeVideoID(document: Document, url?: string): string | boolean {
const url = document.URL; url ||= document.URL;
// clips should never skip, going from clip to full video has no indications. // clips should never skip, going from clip to full video has no indications.
if (url.includes("youtube.com/clip/")) return false; if (url.includes("youtube.com/clip/")) return false;
// skip to document and don't hide if on /embed/ // skip to document and don't hide if on /embed/
if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(document, false); if (url.includes("/embed/") && url.includes("youtube.com")) return getYouTubeVideoIDFromDocument(false);
// skip to URL if matches youtube watch or invidious or matches youtube pattern // skip to URL if matches youtube watch or invidious or matches youtube pattern
if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url); if ((!url.includes("youtube.com")) || url.includes("/watch") || url.includes("/shorts/") || url.includes("playlist")) return getYouTubeVideoIDFromURL(url);
// skip to document if matches pattern // skip to document if matches pattern
if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument(document); if (url.includes("/channel/") || url.includes("/user/") || url.includes("/c/")) return getYouTubeVideoIDFromDocument();
// not sure, try URL then document // not sure, try URL then document
return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(document, false); return getYouTubeVideoIDFromURL(url) || getYouTubeVideoIDFromDocument(false);
} }
function getYouTubeVideoIDFromDocument(document: Document, hideIcon = true): string | boolean { function getYouTubeVideoIDFromDocument(hideIcon = true): string | boolean {
// get ID from document (channel trailer / embedded playlist) // get ID from document (channel trailer / embedded playlist)
const videoURL = document.querySelector("[data-sessionlink='feature=player-title']")?.getAttribute("href"); const element = video?.parentElement?.parentElement?.querySelector("a.ytp-title-link[data-sessionlink='feature=player-title']");
const videoURL = element?.getAttribute("href");
if (videoURL) { if (videoURL) {
onInvidious = hideIcon; onInvidious = hideIcon;
return getYouTubeVideoIDFromURL(videoURL); return getYouTubeVideoIDFromURL(videoURL);
} else { } else {
return false return false;
} }
} }
@ -1241,13 +1304,33 @@ async function whitelistCheck() {
* Returns info about the next upcoming sponsor skip * Returns info about the next upcoming sponsor skip
*/ */
function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean): function getNextSkipIndex(currentTime: number, includeIntersectingSegments: boolean, includeNonIntersectingSegments: boolean):
{array: ScheduledTime[], index: number, endIndex: number, openNotice: boolean} { {array: ScheduledTime[], index: number, endIndex: number, extraIndexes: number[], openNotice: boolean} {
const autoSkipSorter = (segment: ScheduledTime) => {
const skipOption = utils.getCategorySelection(segment.category)?.option;
if ((skipOption === CategorySkipOption.AutoSkip || shouldAutoSkip(segment))
&& segment.actionType === ActionType.Skip) {
return 0;
} else if (skipOption !== CategorySkipOption.ShowOverlay) {
return 1;
} else {
return 2;
}
}
const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } = const { includedTimes: submittedArray, scheduledTimes: sponsorStartTimes } =
getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments); getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments);
const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true); const { scheduledTimes: sponsorStartTimesAfterCurrentTime } = getStartTimes(sponsorTimes, includeIntersectingSegments, includeNonIntersectingSegments, currentTime, true);
const minSponsorTimeIndex = sponsorStartTimes.indexOf(Math.min(...sponsorStartTimesAfterCurrentTime)); // This is an array in-case multiple segments have the exact same start time
const minSponsorTimeIndexes = GenericUtils.indexesOf(sponsorStartTimes, Math.min(...sponsorStartTimesAfterCurrentTime));
// Find auto skipping segments if possible, sort by duration otherwise
const minSponsorTimeIndex = minSponsorTimeIndexes.sort(
(a, b) => ((autoSkipSorter(submittedArray[a]) - autoSkipSorter(submittedArray[b]))
|| (submittedArray[a].segment[1] - submittedArray[a].segment[0]) - (submittedArray[b].segment[1] - submittedArray[b].segment[0])))[0] ?? -1;
// Store extra indexes for the non-auto skipping segments if others occur at the exact same start time
const extraIndexes = minSponsorTimeIndexes.filter((i) => i !== minSponsorTimeIndex && autoSkipSorter(submittedArray[i]) !== 0);
const endTimeIndex = getLatestEndTimeIndex(submittedArray, minSponsorTimeIndex); const endTimeIndex = getLatestEndTimeIndex(submittedArray, minSponsorTimeIndex);
const { includedTimes: unsubmittedArray, scheduledTimes: unsubmittedSponsorStartTimes } = const { includedTimes: unsubmittedArray, scheduledTimes: unsubmittedSponsorStartTimes } =
@ -1263,6 +1346,7 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
array: submittedArray, array: submittedArray,
index: minSponsorTimeIndex, index: minSponsorTimeIndex,
endIndex: endTimeIndex, endIndex: endTimeIndex,
extraIndexes, // Segments at same time that need seperate notices
openNotice: true openNotice: true
}; };
} else { } else {
@ -1270,6 +1354,7 @@ function getNextSkipIndex(currentTime: number, includeIntersectingSegments: bool
array: unsubmittedArray, array: unsubmittedArray,
index: minUnsubmittedSponsorTimeIndex, index: minUnsubmittedSponsorTimeIndex,
endIndex: previewEndTimeIndex, endIndex: previewEndTimeIndex,
extraIndexes: [], // No manual things for unsubmitted
openNotice: false openNotice: false
}; };
} }
@ -1855,10 +1940,7 @@ async function vote(type: number, UUID: SegmentUUID, category?: Category, skipNo
skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category); skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category);
} else if (response.successType == -1) { } else if (response.successType == -1) {
if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a warning from a moderator.")) { if (response.statusCode === 403 && response.responseText.startsWith("Vote rejected due to a warning from a moderator.")) {
skipNotice.setNoticeInfoMessageWithOnClick.bind(skipNotice)(() => { openWarningDialog(skipNoticeContentContainer);
Chat.openWarningChat(response.responseText);
skipNotice.closeListener.call(skipNotice);
}, chrome.i18n.getMessage("voteRejectedWarning"));
} else { } else {
skipNotice.setNoticeInfoMessage.bind(skipNotice)(GenericUtils.getErrorMessage(response.statusCode, response.responseText)) skipNotice.setNoticeInfoMessage.bind(skipNotice)(GenericUtils.getErrorMessage(response.statusCode, response.responseText))
} }
@ -2046,7 +2128,7 @@ async function sendSubmitMessage() {
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg"); playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadFailedIconSponsorBlocker.svg");
if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) { if (response.status === 403 && response.responseText.startsWith("Submission rejected due to a warning from a moderator.")) {
Chat.openWarningChat(response.responseText); openWarningDialog(skipNoticeContentContainer);
} else { } else {
alert(GenericUtils.getErrorMessage(response.status, response.responseText)); alert(GenericUtils.getErrorMessage(response.status, response.responseText));
} }
@ -2272,7 +2354,8 @@ function checkForPreloadedSegment() {
const navigationApiAvailable = "navigation" in window; const navigationApiAvailable = "navigation" in window;
if (navigationApiAvailable) { if (navigationApiAvailable) {
// TODO: Remove type cast once type declarations are updated // TODO: Remove type cast once type declarations are updated
(window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", () => videoIDChange(getYouTubeVideoID(document))); (window as unknown as { navigation: EventTarget }).navigation.addEventListener("navigate", (e) =>
videoIDChange(getYouTubeVideoID(document, (e as unknown as Record<string, Record<string, string>>).destination.url)));
} }
// Record availability of Navigation API // Record availability of Navigation API

View file

@ -1,47 +0,0 @@
import Config from "../config";
import Utils from "../utils";
const utils = new Utils();
export interface ChatConfig {
displayName: string,
composerInitialValue?: string,
customDescription?: string
}
export function openChat(config: ChatConfig): void {
const chat = document.createElement("div");
chat.classList.add("sbChatNotice");
chat.style.zIndex = "2000";
const iframe= document.createElement("iframe");
iframe.src = "https://chat.sponsor.ajay.app/#" + utils.objectToURI("", config, false);
chat.appendChild(iframe);
const closeButton = document.createElement("img");
closeButton.classList.add("sbChatClose");
closeButton.src = chrome.extension.getURL("icons/close.png");
closeButton.addEventListener("click", () => {
chat.remove();
closeButton.remove();
});
chat.appendChild(closeButton);
const referenceNode = utils.findReferenceNode();
referenceNode.prepend(chat);
}
export async function openWarningChat(warningMessage: string): Promise<void> {
const warningReasonMatch = warningMessage.match(/Warning reason: '(.+)'/);
alert(chrome.i18n.getMessage("warningChatInfo") + `\n\n${warningReasonMatch ? ` Warning reason: ${warningReasonMatch[1]}` : ``}`);
const userNameData = await utils.asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID);
const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : "";
const publicUserID = await utils.getHash(Config.config.userID);
openChat({
displayName: `${userName ? userName : ``}${userName !== publicUserID ? ` | ${publicUserID}` : ``}`,
composerInitialValue: `I got a warning and confirm I [REMOVE THIS CAPITAL TEXT TO CONFIRM] reread the guidelines.` +
warningReasonMatch ? ` Warning reason: ${warningReasonMatch[1]}` : ``,
customDescription: chrome.i18n.getMessage("warningChatInfo")
});
}

View file

@ -73,6 +73,7 @@ export type Message = BaseMessage & (DefaultMessage | BoolValueMessage | IsInfoF
export interface IsInfoFoundMessageResponse { export interface IsInfoFoundMessageResponse {
found: boolean; found: boolean;
status: number;
sponsorTimes: SponsorTime[]; sponsorTimes: SponsorTime[];
time: number; time: number;
onMobileYouTube: boolean; onMobileYouTube: boolean;
@ -100,7 +101,7 @@ export type MessageResponse =
| GetChannelIDResponse | GetChannelIDResponse
| SponsorStartResponse | SponsorStartResponse
| IsChannelWhitelistedResponse | IsChannelWhitelistedResponse
| Record<string, never> | Record<string, never> // empty object response {}
| VoteResponse | VoteResponse
| ImportSegmentsResponse; | ImportSegmentsResponse;

View file

@ -452,13 +452,7 @@ function invidiousInstanceAddInit(element: HTMLElement, option: string) {
* @param option * @param option
*/ */
function invidiousInit(checkbox: HTMLInputElement, option: string) { function invidiousInit(checkbox: HTMLInputElement, option: string) {
let permissions = ["declarativeContent"]; utils.containsInvidiousPermission().then((result) => {
if (utils.isFirefox()) permissions = [];
chrome.permissions.contains({
origins: utils.getPermissionRegex(),
permissions: permissions
}, function (result) {
if (result != checkbox.checked) { if (result != checkbox.checked) {
Config.config[option] = result; Config.config[option] = result;
@ -474,22 +468,8 @@ function invidiousInit(checkbox: HTMLInputElement, option: string) {
* @param option * @param option
*/ */
async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise<void> { async function invidiousOnClick(checkbox: HTMLInputElement, option: string): Promise<void> {
return new Promise((resolve) => { const enabled = await utils.applyInvidiousPermissions(checkbox.checked, option);
if (checkbox.checked) { checkbox.checked = enabled;
utils.setupExtraSitePermissions(function (granted) {
if (!granted) {
Config.config[option] = false;
checkbox.checked = false;
} else {
checkbox.checked = true;
}
resolve();
});
} else {
utils.removeExtraSiteRegistration();
}
});
} }
/** /**
@ -598,8 +578,9 @@ async function setTextOption(option: string, element: HTMLElement, value: string
function downloadConfig() { function downloadConfig() {
const file = document.createElement("a"); const file = document.createElement("a");
const jsonData = JSON.parse(JSON.stringify(Config.cachedSyncConfig)); const jsonData = JSON.parse(JSON.stringify(Config.cachedSyncConfig));
file.setAttribute("href", "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonData))); const dateTimeString = new Date().toJSON().replace("T", "_").replace(/:/g, ".").replace(/.\d+Z/g, "")
file.setAttribute("download", "SponsorBlockConfig.json"); file.setAttribute("href", `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(jsonData))}`);
file.setAttribute("download", `SponsorBlockConfig_${dateTimeString}.json`);
document.body.append(file); document.body.append(file);
file.click(); file.click();
file.remove(); file.remove();

View file

@ -12,25 +12,17 @@ window.addEventListener('DOMContentLoaded', init);
async function init() { async function init() {
localizeHtmlPage(); localizeHtmlPage();
const domains = document.location.hash.replace("#", "").split(",");
const acceptButton = document.getElementById("acceptPermissionButton"); const acceptButton = document.getElementById("acceptPermissionButton");
acceptButton.addEventListener("click", () => { acceptButton.addEventListener("click", () => {
chrome.permissions.request({ utils.applyInvidiousPermissions(Config.config.supportInvidious).then((enabled) => {
origins: utils.getPermissionRegex(domains), Config.config.supportInvidious = enabled;
permissions: []
}, (granted) => { if (enabled) {
if (granted) {
alert(chrome.i18n.getMessage("permissionRequestSuccess")); alert(chrome.i18n.getMessage("permissionRequestSuccess"));
window.close();
Config.config.ytInfoPermissionGranted = true;
chrome.tabs.getCurrent((tab) => {
chrome.tabs.remove(tab.id);
});
} else { } else {
alert(chrome.i18n.getMessage("permissionRequestFailed")); alert(chrome.i18n.getMessage("permissionRequestFailed"));
} }
}); })
}); });
} }

View file

@ -415,8 +415,10 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
if (request.sponsorTimes) { if (request.sponsorTimes) {
displayDownloadedSponsorTimes(request.sponsorTimes, request.time); displayDownloadedSponsorTimes(request.sponsorTimes, request.time);
} }
} else { } else if (request.status == 404 || request.status == 200) {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404"); PageElements.videoFound.innerHTML = chrome.i18n.getMessage("sponsor404");
} else {
PageElements.videoFound.innerHTML = chrome.i18n.getMessage("connectionError") + request.status;
} }
} }
@ -664,7 +666,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
voteButtonsContainer.appendChild(upvoteButton); voteButtonsContainer.appendChild(upvoteButton);
voteButtonsContainer.appendChild(downvoteButton); voteButtonsContainer.appendChild(downvoteButton);
voteButtonsContainer.appendChild(uuidButton); voteButtonsContainer.appendChild(uuidButton);
if (downloadedTimes[i].actionType === ActionType.Skip if (downloadedTimes[i].actionType === ActionType.Skip || downloadedTimes[i].actionType === ActionType.Mute
&& [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) { && [SponsorHideType.Visible, SponsorHideType.Hidden].includes(downloadedTimes[i].hidden)) {
voteButtonsContainer.appendChild(hideButton); voteButtonsContainer.appendChild(hideButton);
} }

View file

@ -73,7 +73,13 @@ export default class GenericNotice {
hideRightInfo={options.hideRightInfo} hideRightInfo={options.hideRightInfo}
closeListener={() => this.close()} > closeListener={() => this.close()} >
{this.getMessageBox(this.idSuffix, options.textBoxes)} <tr id={"sponsorSkipNoticeMiddleRow" + this.idSuffix}
className="sponsorTimeMessagesRow"
style={{maxHeight: (this.contentContainer().v.offsetHeight - 200) + "px"}}>
<td style={{width: "100%"}}>
{this.getMessageBoxes(this.idSuffix, options.textBoxes)}
</td>
</tr>
<tr id={"sponsorSkipNoticeSpacer" + this.idSuffix} <tr id={"sponsorSkipNoticeSpacer" + this.idSuffix}
className="sponsorBlockSpacer"> className="sponsorBlockSpacer">
@ -90,7 +96,7 @@ export default class GenericNotice {
); );
} }
getMessageBox(idSuffix: string, textBoxes: TextBox[]): JSX.Element[] { getMessageBoxes(idSuffix: string, textBoxes: TextBox[]): JSX.Element[] {
if (textBoxes) { if (textBoxes) {
const result = []; const result = [];
for (let i = 0; i < textBoxes.length; i++) { for (let i = 0; i < textBoxes.length; i++) {

View file

@ -2,7 +2,7 @@ import Config, { VideoDownvotes } from "./config";
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue, VideoID, SponsorHideType } from "./types"; import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue, VideoID, SponsorHideType } from "./types";
import * as CompileConfig from "../config.json"; import * as CompileConfig from "../config.json";
import { findValidElementFromSelector } from "./utils/pageUtils"; import { findValidElement, findValidElementFromSelector } from "./utils/pageUtils";
import { GenericUtils } from "./utils/genericUtils"; import { GenericUtils } from "./utils/genericUtils";
export default class Utils { export default class Utils {
@ -22,8 +22,9 @@ export default class Utils {
]; ];
/* Used for waitForElement */ /* Used for waitForElement */
waitingMutationObserver:MutationObserver = null; creatingWaitingMutationObserver = false;
waitingElements: { selector: string, callback: (element: Element) => void }[] = []; waitingMutationObserver: MutationObserver = null;
waitingElements: { selector: string, visibleCheck: boolean, callback: (element: Element) => void }[] = [];
constructor(backgroundScriptContainer: BackgroundScriptContainer = null) { constructor(backgroundScriptContainer: BackgroundScriptContainer = null) {
this.backgroundScriptContainer = backgroundScriptContainer; this.backgroundScriptContainer = backgroundScriptContainer;
@ -34,18 +35,40 @@ export default class Utils {
} }
/* Uses a mutation observer to wait asynchronously */ /* Uses a mutation observer to wait asynchronously */
async waitForElement(selector: string): Promise<Element> { async waitForElement(selector: string, visibleCheck = false): Promise<Element> {
return await new Promise((resolve) => { return await new Promise((resolve) => {
const initialElement = this.getElement(selector, visibleCheck);
if (initialElement) {
resolve(initialElement);
return;
}
this.waitingElements.push({ this.waitingElements.push({
selector, selector,
visibleCheck,
callback: resolve callback: resolve
}); });
if (!this.creatingWaitingMutationObserver) {
this.creatingWaitingMutationObserver = true;
if (document.body) {
this.setupWaitingMutationListener();
} else {
window.addEventListener("DOMContentLoaded", () => {
this.setupWaitingMutationListener();
});
}
}
});
}
private setupWaitingMutationListener(): void {
if (!this.waitingMutationObserver) { if (!this.waitingMutationObserver) {
this.waitingMutationObserver = new MutationObserver(() => { this.waitingMutationObserver = new MutationObserver(() => {
const foundSelectors = []; const foundSelectors = [];
for (const { selector, callback } of this.waitingElements) { for (const { selector, visibleCheck, callback } of this.waitingElements) {
const element = document.querySelector(selector); const element = this.getElement(selector, visibleCheck);
if (element) { if (element) {
callback(element); callback(element);
foundSelectors.push(selector); foundSelectors.push(selector);
@ -57,6 +80,7 @@ export default class Utils {
if (this.waitingElements.length === 0) { if (this.waitingElements.length === 0) {
this.waitingMutationObserver.disconnect(); this.waitingMutationObserver.disconnect();
this.waitingMutationObserver = null; this.waitingMutationObserver = null;
this.creatingWaitingMutationObserver = false;
} }
}); });
@ -65,7 +89,10 @@ export default class Utils {
subtree: true subtree: true
}); });
} }
}); }
private getElement(selector: string, visibleCheck: boolean) {
return visibleCheck ? findValidElement(document.querySelectorAll(selector)) : document.querySelector(selector);
} }
containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> { containsPermission(permissions: chrome.permissions.Permissions): Promise<boolean> {
@ -183,6 +210,37 @@ export default class Utils {
}); });
} }
applyInvidiousPermissions(enable: boolean, option = "supportInvidious"): Promise<boolean> {
return new Promise((resolve) => {
if (enable) {
this.setupExtraSitePermissions((granted) => {
if (!granted) {
Config.config[option] = false;
}
resolve(granted);
});
} else {
this.removeExtraSiteRegistration();
resolve(false);
}
});
}
containsInvidiousPermission(): Promise<boolean> {
return new Promise((resolve) => {
let permissions = ["declarativeContent"];
if (this.isFirefox()) permissions = [];
chrome.permissions.contains({
origins: this.getPermissionRegex(),
permissions: permissions
}, function (result) {
resolve(result);
});
})
}
/** /**
* Merges any overlapping timestamp ranges into single segments and returns them as a new array. * Merges any overlapping timestamp ranges into single segments and returns them as a new array.
*/ */
@ -358,19 +416,6 @@ export default class Utils {
return referenceNode; return referenceNode;
} }
objectToURI<T>(url: string, data: T, includeQuestionMark: boolean): string {
let counter = 0;
for (const key in data) {
const seperator = (url.includes("?") || counter > 0) ? "&" : (includeQuestionMark ? "?" : "");
const value = (typeof(data[key]) === "string") ? data[key] as unknown as string : JSON.stringify(data[key]);
url += seperator + encodeURIComponent(key) + "=" + encodeURIComponent(value);
counter++;
}
return url;
}
isContentScript(): boolean { isContentScript(): boolean {
return window.location.protocol === "http:" || window.location.protocol === "https:"; return window.location.protocol === "http:" || window.location.protocol === "https:";
} }

View file

@ -108,6 +108,27 @@ function hexToRgb(hex: string): {r: number, g: number, b: number} {
} : null; } : null;
} }
/**
* List of all indexes that have the specified value
* https://stackoverflow.com/a/54954694/1985387
*/
function indexesOf<T>(array: T[], value: T): number[] {
return array.map((v, i) => v === value ? i : -1).filter(i => i !== -1);
}
function objectToURI<T>(url: string, data: T, includeQuestionMark: boolean): string {
let counter = 0;
for (const key in data) {
const seperator = (url.includes("?") || counter > 0) ? "&" : (includeQuestionMark ? "?" : "");
const value = (typeof(data[key]) === "string") ? data[key] as unknown as string : JSON.stringify(data[key]);
url += seperator + encodeURIComponent(key) + "=" + encodeURIComponent(value);
counter++;
}
return url;
}
function generateUserID(length = 36): string { function generateUserID(length = 36): string {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = ""; let result = "";
@ -132,5 +153,7 @@ export const GenericUtils = {
getFormattedTimeToSeconds, getFormattedTimeToSeconds,
getErrorMessage, getErrorMessage,
getLuminance, getLuminance,
generateUserID generateUserID,
indexesOf,
objectToURI
} }

66
src/utils/warnings.ts Normal file
View file

@ -0,0 +1,66 @@
import Config from "../config";
import GenericNotice, { NoticeOptions } from "../render/GenericNotice";
import { ContentContainer } from "../types";
import Utils from "../utils";
import { GenericUtils } from "./genericUtils";
const utils = new Utils();
export interface ChatConfig {
displayName: string,
composerInitialValue?: string,
customDescription?: string
}
export async function openWarningDialog(contentContainer: ContentContainer): Promise<void> {
const userInfo = await utils.asyncRequestToServer("GET", "/api/userInfo", {
userID: Config.config.userID,
values: ["warningReason"]
});
if (userInfo.ok) {
const warningReason = JSON.parse(userInfo.responseText)?.warningReason;
const userNameData = await utils.asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID);
const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : "";
const publicUserID = await utils.getHash(Config.config.userID);
let notice: GenericNotice = null;
const options: NoticeOptions = {
title: chrome.i18n.getMessage("warningTitle"),
textBoxes: [{
text: chrome.i18n.getMessage("warningChatInfo"),
icon: null
}, ...warningReason.split("\n").map((reason) => ({
text: reason,
icon: null
}))],
buttons: [{
name: chrome.i18n.getMessage("questionButton"),
listener: () => openChat({
displayName: `${userName ? userName : ``}${userName !== publicUserID ? ` | ${publicUserID}` : ``}`
})
},
{
name: chrome.i18n.getMessage("warningConfirmButton"),
listener: async () => {
const result = await utils.asyncRequestToServer("POST", "/api/warnUser", {
userID: Config.config.userID,
enabled: false
});
if (result.ok) {
notice?.close();
} else {
alert(`${chrome.i18n.getMessage("warningError")} ${result.status}`);
}
}
}],
timed: false
};
notice = new GenericNotice(contentContainer, "warningNotice", options);
}
}
export function openChat(config: ChatConfig): void {
window.open("https://chat.sponsor.ajay.app/#" + GenericUtils.objectToURI("", config, false));
}

View file

@ -1,15 +1,12 @@
/* eslint-disable @typescript-eslint/no-var-requires */
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
import webpack from "webpack" const webpack = require("webpack");
import path from "path" const path = require('path');
import { fileURLToPath } from "url" const CopyPlugin = require('copy-webpack-plugin');
import CopyPlugin from "copy-webpack-plugin" const BuildManifest = require('./webpack.manifest');
import BuildManifest from "./webpack.manifest.cjs"; const srcDir = '../src/';
const srcDir = "../src/"; const fs = require("fs");
import fs from "fs"; const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const edgeLanguages = [ const edgeLanguages = [
"de", "de",
@ -27,7 +24,7 @@ const edgeLanguages = [
"zh_CN" "zh_CN"
] ]
export default env => ({ module.exports = env => ({
entry: { entry: {
popup: path.join(__dirname, srcDir + 'popup.ts'), popup: path.join(__dirname, srcDir + 'popup.ts'),
background: path.join(__dirname, srcDir + 'background.ts'), background: path.join(__dirname, srcDir + 'background.ts'),

View file

@ -1,7 +1,8 @@
import { merge } from "webpack-merge"; /* eslint-disable @typescript-eslint/no-var-requires */
import common from './webpack.common.js'; const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
export default env => merge(common(env), { module.exports = env => merge(common(env), {
devtool: 'inline-source-map', devtool: 'inline-source-map',
mode: 'development' mode: 'development'
}); });

View file

@ -1,7 +1,8 @@
import { merge } from "webpack-merge"; /* eslint-disable @typescript-eslint/no-var-requires */
import common from './webpack.common.js'; const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
export default env => { module.exports = env => {
let mode = "production"; let mode = "production";
env.mode = mode; env.mode = mode;