Compare commits

..

47 Commits
4.3.1 ... 4.4.5

Author SHA1 Message Date
Ajay
c0f8f5e1d0 bump version 2022-05-25 14:16:45 -04:00
Ajay Ramachandran
e948e1e569 Merge pull request #1331 from mchangrh/updateDependencies
update dependencies & bump minimum node version
2022-05-25 00:01:10 -04:00
Ajay
5289e62d43 Use virtual time for determining whether to skip and increase skip buffer
Potentially fix recent issue with skips not triggering
2022-05-24 23:54:05 -04:00
Ajay
eda7aac5ef Add time to log messages 2022-05-24 23:31:43 -04:00
Michael C
d9f4ab0d04 specify jest as cjs, change eslintrc to .json 2022-05-24 21:23:05 -04:00
Michael C
2665327729 update dependencies & bump minimum node version (#1329)
- bump all dependences
- add github-actions reporter to jest
- convert (most) webpack configs to ESM
  - manifest.cjs cannot be converted since there is no native .json import until node 17
2022-05-24 21:14:36 -04:00
Ajay
4738c1897f bump version 2022-05-24 14:05:13 -04:00
Ajay
cfbb194a61 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-05-24 14:04:54 -04:00
Ajay
9ad21e8ea1 Fix channel id regex 2022-05-24 14:04:53 -04:00
Ajay Ramachandran
941fc2985d Merge pull request #1238 from AlecRust/fix-unused-vars-lint
Fix @typescript-eslint/no-unused-vars lint warnings
2022-05-24 13:17:27 -04:00
Ajay Ramachandran
e2da071761 bump version 2022-05-24 13:16:04 -04:00
Ajay Ramachandran
c20c9ac64a Merge pull request #1220 from tadwohlrapp/patch-1
Fix minor typos and inconsistencies
2022-05-24 13:12:20 -04:00
Ajay Ramachandran
2d59f3825c New translations messages.json (Swedish) (#1326) 2022-05-24 13:09:34 -04:00
Ajay Ramachandran
fb8b7a7d19 Merge pull request #1327 from Argn0/branch1
Add reset settings option
2022-05-24 12:56:50 -04:00
Ajay
d992cc7a3c Don't reset everything 2022-05-24 12:55:17 -04:00
Ajay
f8fecf5174 Remove redundant explanation 2022-05-24 12:50:39 -04:00
Argn0
3007cddce9 add reset settings option 2022-05-24 10:04:56 +02:00
Ajay
2b3c812f8a Some more debug logs 2022-05-23 21:41:35 -04:00
Ajay
61535fac95 Add more logging for catching skip issues 2022-05-23 01:14:46 -04:00
Ajay
4182595436 Don't stop skipping if channel id fetch fails 2022-05-21 16:54:02 -04:00
Ajay
97e30e4001 bump version 2022-05-20 04:19:20 -04:00
Ajay
6763fd3b4b Allow more channel IDs
Help with #753
2022-05-20 04:01:50 -04:00
Ajay
a39ec76340 Fix skip to highlight option on mobile 2022-05-19 19:28:26 -04:00
Ajay
f68282decc Add more verbose logging to hidden variable 2022-05-19 19:22:59 -04:00
Ajay
160de56a71 Fix icon on android firefox 2022-05-19 13:07:44 -04:00
Ajay
bd4fbcc93d bump version 2022-05-18 15:44:08 -04:00
Ajay
f1839eef94 Added another warning related to buffering 2022-05-18 15:43:51 -04:00
Ajay
3e3abf36e5 Fix unsafe navigation 2022-05-17 22:39:49 -04:00
Ajay
393861dcd3 bump version 2022-05-17 14:16:19 -04:00
Ajay Ramachandran
b5d6b2d3fd New Crowdin updates (#1309) 2022-05-17 14:16:04 -04:00
Ajay Ramachandran
a0c82dc734 Merge pull request #1315 from mchangrh/invidiousProgressBar
shortcut creating preview bar on invidious
2022-05-17 14:15:44 -04:00
Ajay
e4be99c3d7 Clean up isVisible shortcut 2022-05-17 14:11:07 -04:00
Michael C
3da6a57e42 shorcut creating preview bar on invidious 2022-05-17 14:10:57 -04:00
Ajay Ramachandran
7ce9442153 Merge pull request #1314 from mchangrh/mobileChapters
add selector for mobile YT chapters
2022-05-17 14:02:19 -04:00
Ajay
773da185ee Fix visual issues on mobile preview bar 2022-05-17 13:55:39 -04:00
Ajay
bf3dcd97b8 Fix time without skips 2022-05-17 13:50:30 -04:00
Ajay
793c16a21b Add warning messages to catch weird not skipping bug 2022-05-17 13:33:26 -04:00
Ajay
52dc697635 Merge branch 'master' of https://github.com/ajayyy/SponsorBlock 2022-05-16 14:33:02 -04:00
Ajay
36689ed886 Don't use hide buttons for auto hiding skipt to highlight
Fixes #1318
2022-05-16 14:32:59 -04:00
Michael C
4d4689a190 add selector for mobile YT chapters #1287 2022-05-10 20:51:32 -04:00
Florian Wendelborn
78aaffb8d8 Add iOS info (#1307) 2022-05-07 01:08:25 -04:00
Ajay
b82e43bb93 bump version 2022-05-06 14:14:48 -04:00
Ajay
8eef3a3ce5 Revert "Refresh segments when submitting"
Fixes #1295
2022-05-06 14:11:11 -04:00
Ajay
063845d513 bump version 2022-05-05 09:03:55 -04:00
Ajay
7353a2ac0d Fix extra requests being made 2022-05-05 09:03:37 -04:00
Alec Rust
463ce258bf Fix @typescript-eslint/no-unused-vars lint warnings 2022-04-23 15:44:21 +01:00
Tad Wohlrapp
a64deb2e18 Fix minor typos and inconsistencies 2022-03-08 19:57:09 +01:00
25 changed files with 4359 additions and 2944 deletions

View File

@@ -1,34 +0,0 @@
module.exports = {
env: {
browser: true,
es2021: true,
node: true,
jest: true,
jasmine: true,
},
extends: [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended",
],
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: ["react", "@typescript-eslint"],
rules: {
// TODO: Remove warn rules when not needed anymore
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
"react/prop-types": [2, { ignore: ['children'] }]
},
settings: {
react: {
version: "detect",
},
},
};

33
.eslintrc.json Normal file
View File

@@ -0,0 +1,33 @@
{
"env": {
"browser": true,
"es2021": true,
"node": true,
"jest": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": ["react", "@typescript-eslint"],
"rules": {
"@typescript-eslint/no-unused-vars": "error",
"no-self-assign": "off",
"@typescript-eslint/no-empty-interface": "off",
"react/prop-types": [2, { "ignore": ["children"] }]
},
"settings": {
"react": {
"version": "detect"
}
}
}

View File

@@ -13,7 +13,7 @@
<a href="https://addons.mozilla.org/addon/sponsorblock/?src=external-github">Firefox</a> |
<a href="https://github.com/ajayyy/SponsorBlock/wiki/Android">Android</a> |
<a href="https://github.com/ajayyy/SponsorBlock/wiki/Edge">Edge</a> |
<a href="https://github.com/ajayyy/SponsorBlock/wiki/Safari">Safari for MacOS</a> |
<a href="https://github.com/ajayyy/SponsorBlock/wiki/Safari">Safari for MacOS and iOS</a> |
<a href="https://sponsor.ajay.app">Website</a> |
<a href="https://sponsor.ajay.app/stats">Stats</a>
</p>

View File

@@ -5,4 +5,5 @@ module.exports = {
"transform": {
"^.+\\.ts$": "ts-jest"
},
"reporters": ["github-actions"]
};

View File

@@ -1,7 +1,7 @@
{
"name": "__MSG_fullName__",
"short_name": "SponsorBlock",
"version": "4.3.1",
"version": "4.4.5",
"default_locale": "en",
"description": "__MSG_Description__",
"homepage_url": "https://sponsor.ajay.app",
@@ -64,7 +64,12 @@
],
"browser_action": {
"default_title": "SponsorBlock",
"default_popup": "popup.html"
"default_popup": "popup.html",
"default_icon": {
"16": "icons/IconSponsorBlocker16px.png",
"32": "icons/IconSponsorBlocker32px.png",
"64": "icons/LogoSponsorBlocker64px.png"
}
},
"background": {
"scripts":[

6152
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,39 +3,40 @@
"version": "1.0.0",
"description": "",
"main": "background.js",
"type": "module",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@types/chrome": "^0.0.180",
"@types/chrome": "^0.0.188",
"@types/firefox-webext-browser": "^94.0.1",
"@types/jest": "^27.4.1",
"@types/jest": "^27.5.1",
"@types/react": "^17.0.43",
"@types/react-dom": "^17.0.14",
"@types/selenium-webdriver": "^4.0.18",
"@types/selenium-webdriver": "^4.1.0",
"@types/wicg-mediasession": "^1.1.3",
"@typescript-eslint/eslint-plugin": "^5.17.0",
"@typescript-eslint/parser": "^5.17.0",
"chromedriver": "^100.0.0",
"concurrently": "^7.0.0",
"copy-webpack-plugin": "^10.2.4",
"eslint": "^8.12.0",
"eslint-plugin-react": "^7.29.4",
"fork-ts-checker-webpack-plugin": "^7.2.1",
"jest": "^27.5.1",
"@typescript-eslint/eslint-plugin": "^5.26.0",
"@typescript-eslint/parser": "^5.26.0",
"chromedriver": "^101.0.0",
"concurrently": "^7.2.1",
"copy-webpack-plugin": "^11.0.0",
"eslint": "^8.16.0",
"eslint-plugin-react": "^7.30.0",
"fork-ts-checker-webpack-plugin": "^7.2.11",
"jest": "^28.1.0",
"rimraf": "^3.0.2",
"schema-utils": "^4.0.0",
"selenium-webdriver": "^4.1.1",
"selenium-webdriver": "^4.1.2",
"speed-measure-webpack-plugin": "^1.5.0",
"ts-jest": "^27.1.4",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "4.6",
"ts-jest": "^28.0.3",
"ts-loader": "^9.3.0",
"ts-node": "^10.8.0",
"typescript": "4.7",
"web-ext": "^6.8.0",
"webpack": "^5.64.4",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2",
"webpack-merge": "^4.2.2"
"webpack-merge": "^5.8.0"
},
"scripts": {
"web-run": "npm run web-run:chrome",
@@ -65,7 +66,7 @@
"lint:fix": "eslint src --fix"
},
"engines": {
"node": ">=12.20.0"
"node": ">=16"
},
"funding": [
{

View File

@@ -162,7 +162,10 @@
"message": "ব্যবহারকারীর নাম দিন"
},
"copyPublicID": {
"message": "পাবলিক ইউজারআইডি কপি করুন"
"message": "পাবলিক ইউজার আইডি কপি করুন"
},
"copySegmentID": {
"message": "অংশের আইডি কপি করুন"
},
"discordAdvert": {
"message": "পরামর্শ এবং প্রতিক্রিয়া জানাতে অফিসিয়াল ডিসকর্ড সার্ভারে যোগ দিন!"
@@ -218,9 +221,15 @@
"trackDownvotesWarning": {
"message": "সতর্কীকরণ: এটি বন্ধ করলে পূর্বে সংরক্ষিত সব ডাউনভোট মুছে যাবে"
},
"enableQueryByHashPrefix": {
"message": "হ্যাশের প্রিফিক্স দিয়ে খুজুন "
},
"whatQueryByHashPrefix": {
"message": "সার্ভার থেকে videoID দিয়ে অংশ অনুরোধ করার পরিবর্তে videoID এর হ্যাশ এর প্রথম অক্ষর পাঠানো হয়। এই সার্ভার সমতুল্য হ্যাশ এর সকল ভিডিও এর তথ্য ফেরত পাঠাবে।"
},
"enableRefetchWhenNotFound": {
"message": "নতুন ভিডিওতে আবার অংশটি যোগার করুন"
},
"whatRefetchWhenNotFound": {
"message": "যদি ভিডিওটি নতুন হয়, এবং কোন অংশ পাওয়া না যায়, আপনার দেখার সময় কয়েক মিনিট পর পরই এটি তথ্য আনতে থাকবে।"
},
@@ -230,6 +239,21 @@
"showSkipNotice": {
"message": "একটি অংশ এড়ানোর পরে নোটিস প্রদর্শন করুন"
},
"noticeVisibilityMode0": {
"message": "পূর্ণ আকারের স্কিপ নোটিস"
},
"noticeVisibilityMode1": {
"message": "স্বয়ংক্রিয় স্কিপের জন্য ক্ষুদ্র আকারের স্কিপ নোটিস"
},
"noticeVisibilityMode2": {
"message": "সব ক্ষুদ্র আকারের স্কিপ নোটিস"
},
"noticeVisibilityMode3": {
"message": "স্বয়ংক্রিয় স্কিপের জন্য অনুজ্বল স্কিপ নোটিস"
},
"noticeVisibilityMode4": {
"message": "সব অনুজ্বল স্কিপ নোটিস"
},
"longDescription": {
"message": "SponsorBlock আপনাকে YouTube ভিডিওসমূহের স্পন্সর বার্তা, সূচনাবার্তা, সমাপ্তিবার্তা, সাবস্ক্রাইব করার জন্য স্মরণ করানো, এবং অন্যান্য বিবিধ বিরক্তিকর অংশ এড়িয়ে যেতে সাহায্য করে। SponsorBlock জনসংগৃহীত তথ্যসম্বলিত একটি ব্রাউজার এক্সটেনশন যা যে কাউকে একটি ভিডিওর স্পন্সর বার্তা এবং অন্যান্য অংশের শুরু এবং শেষ সময় সাবমিট করতে দেয়। যখন কেউ একজন এই তথ্য সাবমিট করে, এই এক্সটেনশন ব্যবহারকারী সবাই ঐ স্পন্সর বার্তা সম্বলিত অংশ এড়িয়ে যাবে। মিউজিক ভিডিও এর মিউজিক বহির্ভুত অংশও আপনি এড়িয়ে যেতে পারেন।",
"description": "Full description of the extension on the store pages."
@@ -246,6 +270,10 @@
"message": "নোটিসটি আপগ্রেড করা হয়েছে!",
"description": "The first line of the message displayed after the notice was upgraded."
},
"noticeUpdate2": {
"message": "আপনি যদি এখনও এটি পছন্দ না করেন তবে কখনই দেখাবে না বোতামটি চাপুন।",
"description": "The second line of the message displayed after the notice was upgraded."
},
"setSkipShortcut": {
"message": "সেগমেন্ট এড়িয়ে যান",
"description": "Keybind label"
@@ -257,5 +285,667 @@
"setSubmitKeybind": {
"message": "সেগমেন্ট জমা দিন",
"description": "Keybind label"
},
"keybindDescription": {
"message": "এটি টাইপ করে একটি কী নির্বাচন করুন এবং আপনি যে কোনও সংশোধক কীগুলি ব্যবহার করতে চান তা চয়ন করুন।"
},
"0": {
"message": "সংযোগের সময়সীমা অতিক্রান্ত। আপনার ইন্টারনেট সংযোগ যাচাই করুন. যদি আপনার ইন্টারনেট কাজ করে থাকে তবে সার্ভারটি সম্ভবত ওভারলোডেড বা ডাউন।"
},
"disableSkipping": {
"message": "স্কিপিং চালু করা হয়েছে"
},
"enableSkipping": {
"message": "স্কিপিং বন্ধ করা হয়েছে"
},
"yourWork": {
"message": "আপনার কাজ",
"description": "Used to describe the section that will show you the statistics from your submissions."
},
"502": {
"message": "সার্ভারটি সম্ভবত ওভারলোডেড। কয়েক সেকেন্ডের মধ্যে আবার চেষ্টা করুন।"
},
"errorCode": {
"message": "ইরোর কোডঃ "
},
"skip": {
"message": "এড়িয়ে যান"
},
"mute": {
"message": "নিঃশব্দ করুন"
},
"full": {
"message": "সমপূর্ণ ভিডিও",
"description": "Used for the name of the option to label an entire video as sponsor or self promotion."
},
"skip_category": {
"message": "{0} এড়িয়ে যান?"
},
"mute_category": {
"message": "{0} নিঃশব্দ করুন?"
},
"skip_to_category": {
"message": "{0} তে চলে যান?",
"description": "Used for skipping to things (Skip to Highlight)"
},
"skipped": {
"message": "{0} এড়িয়ে যাওয়া হয়েছে",
"description": "Example: Sponsor Skipped"
},
"muted": {
"message": "{0} নিঃশব্দ করা হয়েছে",
"description": "Example: Sponsor Muted"
},
"skipped_to_category": {
"message": "{0} তে চলে যাওয়া হয়েছে",
"description": "Used for skipping to things (Skipped to Highlight)"
},
"disableAutoSkip": {
"message": "স্বয়ংক্রিয়ভাবে স্কিপ করা বন্ধ করুন"
},
"enableAutoSkip": {
"message": "স্বয়ংক্রিয়ভাবে স্কিপ করা চালু করুন"
},
"audioNotification": {
"message": "স্কিপের জন্য অডিও নোটিস"
},
"audioNotificationDescription": {
"message": "যখনই কোনও অংশ এড়িয়ে যাওয়া হয় তখন একটি শব্দ বাজাবে। যদি বন্ধ করা হয় (বা অটো স্কিপ বন্ধ থাকে) তবে কোনও শব্দ বাজানো হবে না।"
},
"showTimeWithSkips": {
"message": "স্কিপগুলি সরানো সহ সময় দেখান"
},
"showTimeWithSkipsDescription": {
"message": "এই সময়টি সময় বারের নীচে বর্তমান সময়ের পাশের বন্ধনীগুলিতে উপস্থিত হয়। এটি মোট ভিডিও সময়কাল যে কোনও বিভাগকে বিয়োগ করে তা দেখায়। এর মধ্যে কেবল \"সময় বার এ দেখান\" হিসাবে চিহ্নিত বিভাগগুলি অন্তর্ভুক্ত রয়েছে।"
},
"youHaveSkipped": {
"message": "আপনি এড়িয়েছেন "
},
"minLower": {
"message": "মিনিট"
},
"minsLower": {
"message": "মিনিট"
},
"hourLower": {
"message": "ঘন্টা"
},
"hoursLower": {
"message": "ঘন্টা"
},
"youHaveSavedTime": {
"message": "আপনি মানুষকে রক্ষা করেছেন",
"description": "You've saved people from 887,362 segments (236d 15h 5.3 minutes of their lives)."
},
"youHaveSavedTimeEnd": {
"message": "তাদের জীবন থেকে",
"description": "You've saved people from 887,362 segments (236d 15h 5.3 minutes of their lives)."
},
"statusReminder": {
"message": "সার্ভারের স্ট্যাটাস এর জন্য status.sponsor.ajay.app দেখুন করুন।"
},
"changeUserID": {
"message": "আপনার ইউজার আইডি ইম্পোর্ট/এক্সপোর্ট করুন"
},
"whatChangeUserID": {
"message": "এটি ব্যক্তিগত রাখা উচিত। এটি একটি পাসওয়ার্ডের মতো এবং কারও সাথে ভাগ করা উচিত নয়। কারও যদি এটি থাকে তবে তারা আপনার ছদ্মবেশ ধারণ করতে পারে। আপনি যদি আপনার পাবলিক ইউজারআইডি খুঁজছেন তবে পপআপে ক্লিপবোর্ড আইকনটি ক্লিক করুন।"
},
"setUserID": {
"message": "ইউজার আইডি দিন"
},
"userIDChangeWarning": {
"message": "সতর্কতা: ইউজারআইডি পরিবর্তন করা চিরস্থায়ী। আপনি কি নিশ্চিত যে আপনি এটি করতে চান? আপনার পুরানোটিকে সাবধানতার সার্থে ব্যাকআপ করার বিষয়টি নিশ্চিত করুন।"
},
"createdBy": {
"message": "সৃষ্টি করেছেন"
},
"supportOtherSites": {
"message": "এটি ৩য় পক্ষের ইউটইউব সাইট সাপোর্ট করে"
},
"supportOtherSitesDescription": {
"message": "তৃতীয় পক্ষের ইউটিউব ক্লায়েন্টদের সমর্থন করুন। সমর্থন সক্ষম করতে, আপনাকে অবশ্যই অতিরিক্ত অনুমতিগুলি গ্রহণ করতে হবে। এটি ক্রোম এবং অন্যান্য ক্রোমিয়াম ভেরিয়েন্টগুলিতে ছদ্মবেশে কাজ করে না।",
"description": "This replaces the 'supports Invidious' option because it now works on other YouTube sites such as Cloudtube"
},
"supportedSites": {
"message": "সমর্থিত সাইটসমুহ:"
},
"optionsInfo": {
"message": "Indivious সমর্থন সক্ষম করুন, অটোস্কিপ বন্ধ করুন, বোতামগুলি লুকান এবং আরও অনেক কিছু করুন।"
},
"addInvidiousInstance": {
"message": "তৃতীয় পক্ষের ক্লায়েন্ট যুক্ত করুন"
},
"addInvidiousInstanceDescription": {
"message": "একটি কাস্টম উদাহরণ যুক্ত করুন। এটি অবশ্যই ডোমেন দিয়ে ফর্ম্যাট করা উচিত। উদাহরণ: invidious.ajay.app"
},
"add": {
"message": "যোগ করুন"
},
"addInvidiousInstanceError": {
"message": "এটি একটি অবৈধ ডোমেন। এটিতে কেবল ডোমেন অংশ অন্তর্ভুক্ত করা উচিত। উদাহরণ: invidious.ajay.app"
},
"resetInvidiousInstance": {
"message": "Invidious Instance এর তালিকা পুনরায় সেট করুন"
},
"resetInvidiousInstanceAlert": {
"message": "আপনি এখন Invidious Instance এর তালিকা পুনরায় আগের মত করে দিবেন"
},
"currentInstances": {
"message": "বর্তমান Instance এর তালিকা"
},
"minDuration": {
"message": "সর্বনিম্ন দৈর্ঘ্য (সেকেন্ড):"
},
"minDurationDescription": {
"message": "সেট করা মান (সেকেন্ডে) থেকে ছোট সেগমেন্টগুলি প্লেয়ারে এড়িয়ে যাওয়া হবে বা দেখানো হবে না"
},
"skipNoticeDuration": {
"message": "নোটিশ প্রদর্শন করার দৈর্ঘ্য (সেকেন্ড):"
},
"skipNoticeDurationDescription": {
"message": "স্কিপ নোটিশটি কমপক্ষে এত সেকেন্ডের জন্য স্ক্রিনে থাকবে। নিজে এড়িয়ে যাওয়ার জন্য, এটি দীর্ঘকাল ধরে দৃশ্যমান হতে পারে।"
},
"shortCheck": {
"message": "নিম্নলিখিত সাবমিশনটি আপনার ন্যূনতম সময়কাল অপশনের চেয়ে কম। এর মানে এই হতে পারে যে এটি ইতিমধ্যে জমা দেওয়া হয়েছে, এবং এই অপশনের কারণে উপেক্ষা করা হচ্ছে। আপনি কি জমা দিতে চান?"
},
"liveOrPremiere": {
"message": "একটি সক্রিয় লাইভস্ট্রিম বা প্রিমিয়ারে জমা দেওয়ার অনুমতি নেই। এটি শেষ না হওয়া পর্যন্ত অপেক্ষা করুন, তারপরে পৃষ্ঠাটি রিফ্রেশ করুন এবং অংশগুলি এখনও বৈধ কিনা তা যাচাই করুন।"
},
"showUploadButton": {
"message": "আপলোড করার বোতামটি দেখান"
},
"customServerAddress": {
"message": "স্পনসরব্লক সার্ভার ঠিকানা"
},
"customServerAddressDescription": {
"message": "এ ঠিকানা SponsorBlock সার্ভারে সাথে যোগাযোগ করতে ব্যবহার করে।\nআপনার নিজের সার্ভার না থাকলে এটি পরিবর্তন করবেন না।"
},
"save": {
"message": "সংরক্ষণ করুন"
},
"reset": {
"message": "পুনরায় সেট করুন"
},
"customAddressError": {
"message": "এই ঠিকানাটি সঠিক আকারে নেই। এটিতে http: // বা https: // শুরুতে এবং কোনও পিছনের স্ল্যাশ নেই তা নিশ্চিত করুন।"
},
"areYouSureReset": {
"message": "আপনি কি নিশ্চিতভাবে এটি মুছে ফেলতে চান??"
},
"mobileUpdateInfo": {
"message": "m.youtube.com এখন সাপর্টেড"
},
"exportOptions": {
"message": "সব অপশন ইম্পোর্ট/এক্সপোর্ট করুন"
},
"exportOptionsCopy": {
"message": "সম্পাদন/কপি করুন"
},
"exportOptionsDownload": {
"message": "ফাইল এ সেভ করুন"
},
"exportOptionsUpload": {
"message": "ফাইল থেকে লোড করুন"
},
"whatExportOptions": {
"message": "এটি আপনার সম্পূর্ণ কনফিগারেশন এতে আপনার ইউজারআইডি অন্তর্ভুক্ত রয়েছে, তাই বিজ্ঞতার সাথে শেয়ার করতে ভুলবেন না।."
},
"setOptions": {
"message": "অপশন সেট করুন"
},
"exportOptionsWarning": {
"message": "সতর্কতা: অপশনগুলি পরিবর্তন করা চিরস্থায়ী এবং আপনার ইনস্টলটি ভাঙতে পারে। আপনি কি নিশ্চিত যে আপনি এটি করতে চান? আপনার পুরানোটিকে সাবধানতার সার্থে ব্যাকআপ করার বিষয়টি নিশ্চিত করুন।"
},
"incorrectlyFormattedOptions": {
"message": "এই JSON সঠিকভাবে ফর্ম্যাট করা হয় নাই। আপনার অপশনগুলি পরিবর্তন করা হয়নি।"
},
"confirmNoticeTitle": {
"message": "অংশ জমা দিন"
},
"submit": {
"message": "জমা দিন"
},
"cancel": {
"message": "বাতিল করুন"
},
"delete": {
"message": "মুছে ফেলুন"
},
"preview": {
"message": "প্রিভিউ দেখুন"
},
"unsubmitted": {
"message": "জমাকৃত নয়"
},
"inspect": {
"message": "পরিদর্শন করুন"
},
"edit": {
"message": "সম্পাদন করুন"
},
"copyDebugInformation": {
"message": "ডিবাগ তথ্য ক্লিপবোর্ডে কপি করুন"
},
"copyDebugInformationFailed": {
"message": "ক্লিপবোর্ডে কপি করা যায় নি"
},
"copyDebugInformationOptions": {
"message": " কোনও বাগ উত্থাপন করার সময় / যখন কোনও ডেভেলপার এটির জন্য অনুরোধ করে তখন ক্লিপবোর্ডে তথ্য কপি করে। সংবেদনশীল তথ্য যেমন আপনার ইউজার আইডি, সাদা তালিকাভুক্ত চ্যানেল এবং কাস্টম সার্ভারের ঠিকানা সরানো হয়েছে। তবে এটিতে আপনার ব্যবহারকারীর, ব্রাউজার, অপারেটিং সিস্টেম এবং এক্সটেনশন সংস্করণ নম্বরের মতো তথ্য রয়েছে।"
},
"copyDebugInformationComplete": {
"message": "ডিবাগের তথ্য ক্লিপ বোর্ডে কপি করা হয়েছে। আপনি কোনও তথ্য দিতে অনিচ্ছুক হলে তা নির্দ্বিধায় অপসারণ করতে পারেন। এটি একটি টেক্সট ফাইলে সংরক্ষণ করুন বা বাগ প্রতিবেদনে পেস্ট করুন।"
},
"keyAlreadyUsed": {
"message": "এই শর্টকাটটি অন্য ক্রিয়ায় আবদ্ধ। দয়া করে একটি আলাদা নির্বাচন করুন।"
},
"to": {
"message": "থেকে",
"description": "Used between segments. Example: 1:20 to 1:30"
},
"category_sponsor": {
"message": "স্পন্সর"
},
"category_sponsor_description": {
"message": "পেইড প্রমোশন, পেইড রেফারেল এবং সরাসরি বিজ্ঞাপন। নিজের পছন্দসই কারণ/স্রষ্টা/ওয়েবসাইট/পণ্যগুলিতে স্ব-প্রচার বা বিনামূল্যে প্রচারের জন্য নয়।"
},
"category_selfpromo": {
"message": "বিনা অর্থপ্রাপ্ত/স্ব-প্রচার"
},
"category_selfpromo_description": {
"message": "অবৈতনিক বা স্ব -প্রচার ব্যতীত \"স্পনসর\" এর মতো। এর মধ্যে পণ্যদ্রব্য, অনুদান বা তারা কার সাথে সহযোগিতা করেছে সে সম্পর্কে তথ্য অন্তর্ভুক্ত রয়েছে।"
},
"category_exclusive_access": {
"message": "এক্সক্লুসিভ অ্যাক্সেস"
},
"category_exclusive_access_description": {
"message": "শুধুমাত্র পুরো ভিডিও লেবেল করার জন্য। যখন কোনও ভিডিও কোনও পণ্য, পরিষেবা বা অবস্থান প্রদর্শন করে যা তারা নিখরচায় বা ভর্তুকিযুক্ত অ্যাক্সেস পেয়েছে।"
},
"category_exclusive_access_pill": {
"message": "এই ভিডিওটি এমন একটি পণ্য, পরিষেবা বা অবস্থান প্রদর্শন করে যা তারা নিখরচায় বা ভর্তুকিযুক্ত অ্যাক্সেস পেয়েছে",
"description": "Short description for this category"
},
"category_interaction": {
"message": "ইন্টারঅ্যাকশন রেমাইন্ডার (সাবস্ক্রাইব)"
},
"category_interaction_description": {
"message": "যখন সামগ্রীর মাঝখানে তাদেরকে লাইক, সাবস্ক্রাইব বা ফলো করার জন্য একটি সংক্ষিপ্ত অনুস্মারক থাকে। যদি এটি দীর্ঘ বা নির্দিষ্ট কিছু সম্পর্কে হয় তবে পরিবর্তে এটি স্ব -প্রচারের অধীনে থাকা উচিত।"
},
"category_interaction_short": {
"message": "ইন্টারঅ্যাকশন রেমাইন্ডার"
},
"category_intro": {
"message": "ইন্টারমিশন/ইন্ট্র অ্যানিমেশন"
},
"category_intro_description": {
"message": "প্রকৃত বিষয়বস্তু ছাড়াই একটি বিরতি। বিরতি, স্থির ফ্রেম, অ্যানিমেশন পুনরাবৃত্তি হতে পারে। এটি তথ্যযুক্ত ট্রানজিশনের জন্য ব্যবহার করা উচিত নয়।"
},
"category_intro_short": {
"message": "ইন্টারমিশন"
},
"category_outro": {
"message": "এন্ডকার্ডস/ক্রেডিট"
},
"category_outro_description": {
"message": "ক্রেডিট বা যখন ইউটিউব এন্ডকার্ডগুলি উপস্থিত হয়। তথ্য সহ সিদ্ধান্তের জন্য নয়।"
},
"category_preview": {
"message": "প্রিভিউ/রিক্যাপ"
},
"category_preview_description": {
"message": "পূর্ববর্তী পর্বগুলির দ্রুত পুনরুদ্ধার, বা বর্তমান ভিডিওতে পরে কী ঘটছে তার পূর্বরূপ। একসাথে সম্পাদিত ক্লিপ এর জন্য, কথ্য সংক্ষিপ্তসার এর জন্য নয়।"
},
"category_filler": {
"message": "ফিলার ট্যানজেন্ট/জোকস"
},
"category_filler_description": {
"message": "স্পর্শকাতর দৃশ্য যেগুলি কেবল ফিলার বা হাস্যরসের জন্য যুক্ত হয়েছে যা ভিডিওর মূল বিষয়বস্তু বোঝার জন্য প্রয়োজন হয় না। এর মধ্যে প্রসঙ্গ বা পটভূমির বিশদ সরবরাহকারী বিভাগগুলি অন্তর্ভুক্ত করা উচিত নয়।"
},
"category_filler_short": {
"message": "ফিলার"
},
"category_music_offtopic": {
"message": "সঙ্গীত: অসঙ্গীত বিভাগ"
},
"category_music_offtopic_description": {
"message": "শুধুমাত্র সঙ্গীত ভিডিওতে ব্যবহারের জন্য। এটি কেবলমাত্র সংগীত ভিডিওর সেসব বিভাগের জন্য ব্যবহার করা উচিত যা ইতিমধ্যে অন্য কোনও বিভাগ দ্বারা আচ্ছাদিত নয়।"
},
"category_music_offtopic_short": {
"message": "মিউসিক নয়"
},
"category_poi_highlight": {
"message": "গুরুত্বপূর্ণ"
},
"category_poi_highlight_description": {
"message": "ভিডিওর অংশটি যা বেশিরভাগ লোকেরা খুঁজছেন। \"ভিডিওটি x এ শুরু হয়\" মন্তব্যের মতো।"
},
"category_livestream_messages": {
"message": "লাইভস্ট্রিম: অনুদান/বার্তা পাঠ"
},
"category_livestream_messages_short": {
"message": "বার্তা পাঠ"
},
"autoSkip": {
"message": "স্বয়ংক্রিয়ভাবে এড়িয়ে যান"
},
"manualSkip": {
"message": "নিজে এড়িয়ে যান"
},
"showOverlay": {
"message": "সময় বার এ দেখান"
},
"disable": {
"message": "নিষ্ক্রিয় করুন"
},
"autoSkip_POI": {
"message": "স্বয়ংক্রিয় ভাবে শুরুতে স্কিপ করুন"
},
"manualSkip_POI": {
"message": "ভিডিও লোড হলে জিজ্ঞেস করুন"
},
"showOverlay_POI": {
"message": "সময় বার এ দেখান"
},
"showOverlay_full": {
"message": "লেবেল দেখান"
},
"autoSkipOnMusicVideos": {
"message": "যখন অ-সংগীত বিভাগ থাকে তখন স্বয়ংক্রিয়ভাবে সমস্ত বিভাগগুলি এড়িয়ে যান"
},
"muteSegments": {
"message": "স্কিপের পরিবর্তে অডিও নিঃশব্দ এমন বিভাগগুলিকে দেখানোর অনুমতি দিন"
},
"fullVideoSegments": {
"message": "যখন কোনও ভিডিও সম্পূর্ণ বিজ্ঞাপন হয় তখন একটি আইকন দেখান",
"description": "Referring to the category pill that is now shown on videos that are entirely sponsor or entirely selfpromo"
},
"previewColor": {
"message": "জমাকৃত নয় এমন অংশের রঙ",
"description": "Referring to submissions that have not been sent to the server yet."
},
"seekBarColor": {
"message": "সময় বারের রঙ"
},
"category": {
"message": "বিভাগ"
},
"skipOption": {
"message": "সেগমেন্ট এড়িয়ে যাওয়ার অপশন",
"description": "Used on the options page to describe the ways to skip the segment (auto skip, manual, etc.)"
},
"enableTestingServer": {
"message": "বিটা টেস্টিং প্রোগ্রামে যোগদান করুন"
},
"whatEnableTestingServer": {
"message": "আপনার জমাকৃত অংশ এবং ভোটগুলি মূল সার্ভারের হিসাবে গণনা করা হবে না। কেবল পরীক্ষার জন্য এটি ব্যবহার করুন।"
},
"testingServerWarning": {
"message": "সমস্ত জমাকৃত অংশ এবং ভোট টেস্ট সার্ভারের সাথে সংযোগ করার সময় মূল সার্ভারের হিসাবে গণনা করা হবেনা। আপনি যখন সত্যিকারের অংশ ও ভোট জমা দিতে চান তখন এটি বন্ধ করার বিষয়টি নিশ্চিত করুন।"
},
"bracketNow": {
"message": "(এখন)"
},
"moreCategories": {
"message": "আরো বিভাগ"
},
"chooseACategory": {
"message": "বিভাগ নির্বাচন করুন"
},
"enableThisCategoryFirst": {
"message": "\"{0}\" বিভাগের অন্তর্ভুক্ত অংশ জমা দিতে, আপনাকে এটি অপশন এ গিয়ে চালু করতে হবে। আপনাকে এখন অপশন এ পাঠানো হবে।",
"description": "Used when submitting segments to only let them select a certain category if they have it enabled in the options."
},
"poiOnlyOneSegment": {
"message": "সতর্কতা: এই ধরণের বিভাগে একসময়ে সর্বাধিক একবার সক্রিয় থাকতে পারে। একাধিক জমা দেওয়া হলে যেকোন একটি এলোমেলোভাবে বেছে সেটি দেখানো হবে।"
},
"youMustSelectACategory": {
"message": "আপনাকে প্রত্যেকটি অংশের জন্য কমপক্ষে একটি করে ক্যাটাগরি সিলেক্ট করতে হবে!"
},
"bracketEnd": {
"message": "(শেষ)"
},
"hiddenDueToDownvote": {
"message": "লুক্কায়িতঃ ডাউনভোট"
},
"hiddenDueToDuration": {
"message": "লুক্কায়িতঃ খুব ছোট"
},
"manuallyHidden": {
"message": "নিজে লুক্কায়িত"
},
"channelDataNotFound": {
"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": "চ্যানেল আইডি এখনও লোড হয় না। আপনি যদি এম্বেডে থাকা ভিডিও দেখছেন তবে পরিবর্তে ইউটিউব হোমপেজটি ব্যবহার করার চেষ্টা করুন। এটি ইউটিউব লেআউটে পরিবর্তনের কারণেও হতে পারে, আপনার যদি মনেহয় এটি লেআউটে পরিবর্তনের কারণে হয়েছে তাহলে এখানে একটি মন্তব্য করুনঃ "
},
"videoInfoFetchFailed": {
"message": "দেখে মনে হচ্ছে যে কোনও কিছু স্পনসরব্লকের ভিডিওর ডেটা পাওয়ার ক্ষমতা অবরুদ্ধ করছে। আরও তথ্যের জন্য দয়া করে https://github.com/ajayy/sponsorblock/issues/741 দেখুন।"
},
"youtubePermissionRequest": {
"message": "দেখে মনে হচ্ছে স্পনসরব্লক YouTube API তে পৌঁছাতে অক্ষম। এটি ঠিক করতে, এরপর উপস্থিত হবে সেই অনুমতি প্রম্পটটি গ্রহণ করবেন, তারপর কয়েক সেকেন্ড অপেক্ষা করে পৃষ্ঠাটি পুনরায় লোড করুন।"
},
"acceptPermission": {
"message": "অনুমতি একসেপ্ট করুন"
},
"permissionRequestSuccess": {
"message": "অনুমতির অনুরোধ সফল হয়েছে!"
},
"permissionRequestFailed": {
"message": "অনুমতির অনুরোধ ব্যর্থ হয়েছে, আপনি কি ডেনাই ক্লিক করেছেন?"
},
"adblockerIssueWhitelist": {
"message": "আপনি যদি এটি সমাধান করতে অক্ষম হন তবে সেটিংস এ গিয়ে 'এড়িয়ে যাওয়ার আগে চ্যানেল চেক করুন' বন্ধ করুন, কারণ স্পনসরব্লক এই ভিডিওটির জন্য চ্যানেলের তথ্য পুনরুদ্ধার করতে অক্ষম"
},
"forceChannelCheck": {
"message": "এড়িয়ে যাওয়ার আগে চ্যানেল চেক করুন"
},
"whatForceChannelCheck": {
"message": "সাধারণত, এটি চ্যানেলটি কী তা জানার আগেই এটি এখনই বিভাগগুলি এড়িয়ে যাবে। সাধারণত, ভিডিওর শুরুতে কিছু বিভাগগুলি সাদা তালিকাভুক্ত চ্যানেলগুলিতে এড়িয়ে যেতে পারে। এই বিকল্পটি সক্ষম করা এটিকে প্রতিরোধ করবে তবে চ্যানেলআইডি পেতে কিছুটা সময় নিতে পারে বলে সমস্ত এড়িয়ে যাওয়া কিছুটা বিলম্বিত করে। আপনার যদি দ্রুত ইন্টারনেট থাকে তবে এই বিলম্বটি অদৃশ্য হতে পারে।"
},
"forceChannelCheckPopup": {
"message": "\"এড়িয়ে যাওয়ার আগে চ্যানেল চেক করুন\" সেটিংস টি চালু করার বিষয়টি বিবেচনা করুন"
},
"downvoteDescription": {
"message": "সময় ভুল দেওয়া হয়েছে"
},
"incorrectCategory": {
"message": "বিভাগ পরিবর্তন করুন"
},
"nonMusicCategoryOnMusic": {
"message": "এই ভিডিওটি সংগীত হিসাবে শ্রেণীবদ্ধ করা হয়েছে। আপনি কি নিশ্চিত যে এটি একটি স্পনসর আছে? যদি এটি আসলে একটি \"অ-সংগীত বিভাগ\" হয় তবে এক্সটেনশন এর অপশন এ যান এবং এই বিভাগটি চালু করুন। তারপরে, আপনি এই বিভাগটিকে স্পনসর পরিবর্তে \"অ-সংগীত\" হিসাবে জমা দিতে পারেন। আপনি বিভ্রান্ত হলে দয়া করে নিয়মকানুনপড়ুন।"
},
"multipleSegments": {
"message": "অনেকগুলো অংশ"
},
"guidelines": {
"message": "নিয়মকানুন"
},
"readTheGuidelines": {
"message": "নিয়মকানুন পড়ুন!!",
"description": "Show the first time they submit or if they are \"high risk\""
},
"categoryUpdate1": {
"message": "বিভাগ ফিচার এসেছে!"
},
"categoryUpdate2": {
"message": "ইন্ট্রোস, আউট্রোস, মার্চ ইত্যাদি এড়িয়ে যাওয়ার জন্য অপশন খুলুন।"
},
"help": {
"message": "সাহায্য"
},
"GotIt": {
"message": "বুঝেছি",
"description": "Used as the button to dismiss a tooltip"
},
"fullVideoTooltipWarning": {
"message": "এই বিভাগটি বিশাল। যদি পুরো ভিডিওটি একটি বিষয় নিয়ে হয় তবে \"Skip\" থেকে \"Full Video\" এ পরিবর্তন করুন। আরও তথ্যের জন্য নিয়মকানুন দেখুন।"
},
"categoryPillTitleText": {
"message": "এই পুরো ভিডিওটি এই বিভাগ হিসাবে লেবেলযুক্ত এবং পৃথক করা সম্ভব না কারন এটি খুব ঘন ঘন সংযুক্ত করা হয়েছে"
},
"experiementOptOut": {
"message": "ভবিষ্যতের সমস্ত পরীক্ষা-নিরীক্ষা পাওয়া থেকে বিরত থাকুন",
"description": "This is used in a popup about a new experiment to get a list of unlisted videos to back up since all unlisted videos uploaded before 2017 will be set to private."
},
"hideForever": {
"message": "চিরকালের জন্য এই বিষয়বস্তু লুকান"
},
"warningChatInfo": {
"message": "আপনি একটি সতর্কতা পেয়েছেন এবং অস্থায়ীভাবে বিভাগগুলি জমা দিতে পারবেন না। এর অর্থ হ'ল আমরা লক্ষ্য করেছি যে আপনি কিছু সাধারণ ভুল করছেন যা দূষিত নয়, দয়া করে কেবল নিশ্চিত করুন যে আপনি নিয়মগুলি বুঝতে পেরেছেন এবং আমরা সতর্কতাটি সরিয়ে দেব। আপনি আমাদের চ্যাটটি discord.gg/SponsorBlock or matrix.to/#/#sponsor:ajay.app ব্যবহার করে যোগ দিতে পারেন"
},
"voteRejectedWarning": {
"message": "একটি সতর্কতার কারণে ভোট প্রত্যাখ্যান করা হয়েছে। এটি সমাধানের জন্য চ্যাট খুলতে ক্লিক করুন বা আপনার এখন সময় না থাকলে পরে আবার ফিরে আসুন।",
"description": "This is an integrated chat panel that will appearing allowing them to talk to the Discord/Matrix chat without leaving their browser."
},
"Donate": {
"message": "অনুদান"
},
"considerDonating": {
"message": "অনুদানের মাধ্যমে ভবিষ্যতেে এর উন্নয়নের কাজে সাহায্য হবে"
},
"hideDonationLink": {
"message": "অনুদানের লিঙ্ক লুকান"
},
"darkModeOptionsPage": {
"message": "অপশন পেজে ডার্ক মোড "
},
"helpPageThanksForInstalling": {
"message": "স্পনসরব্লক ইনস্টল করার জন্য আপনাকে ধন্যবাদ."
},
"helpPageReviewOptions": {
"message": "নীচের অপশনগুলি পর্যালোচনা করুন"
},
"helpPageFeatureDisclaimer": {
"message": "অনেকগুলি বৈশিষ্ট্য সাধারণত অক্ষম থাকে। আপনি যদি ইন্ট্রোস, আউট্রোস এড়িয়ে যেতে চান, Invidious ইত্যাদি ব্যবহার করতে চান তবে সেগুলি নীচে সক্ষম করুন। আপনি UI উপাদানগুলিও লুকাতে/দেখাতে পারেন।"
},
"helpPageHowSkippingWorks": {
"message": "এটি কীভাবে কাজ করে"
},
"helpPageHowSkippingWorks1": {
"message": "ভিডিও এর অংশ ডাটাবেসে পাওয়া গেলে সেগুলি স্বয়ংক্রিয়ভাবে এড়িয়ে যাবে। তারা কীরকম তা পূর্বরূপ দেখতেআপনি এক্সটেনশন আইকনে ক্লিক করে পপআপটি খুলতে পারেন।"
},
"helpPageHowSkippingWorks2": {
"message": "আপনি যখনই কোনও বিভাগ এড়িয়ে যান, আপনি একটা নোটিস পাবেন। সময়টি যদি ভুল বলে মনে হয় তবে ডাউনভোটে ক্লিক করে ভোট দিন! আপনি পপআপ থেকেও ভোট দিতে পারেন।"
},
"Submitting": {
"message": "নতুন অংশ জমা দেওয়া"
},
"helpPageSubmitting1": {
"message": "জমা দেওয়া যায় পপআপে \"অংশ এখন শুরু হয়\" বোতামটি টিপ মেরে বা প্লেয়ারের বোতামগুলি সহ ভিডিও প্লেয়ারে টিপ মেরে করে।"
},
"helpPageSubmitting2": {
"message": "প্লে বোতামটি ক্লিক করা একটি বিভাগের শুরু নির্দেশ করে এবং স্টপ আইকনটি ক্লিক করা শেষটি নির্দেশ করে। আপনি জমা দেওয়ার আগে একাধিক স্পনসর প্রস্তুত করতে পারেন। আপলোড বোতামটি ক্লিক করা জমা দেওয়া হবে। আবর্জনায় ক্লিক করে বাদ দিতে পারেন এটি।"
},
"Editing": {
"message": "সম্পাদন করা"
},
"helpPageEditing1": {
"message": "যদি আপনি ভুল করে বসেন তবে আপনি উপড়ের তীর বোতামটি ক্লিক করার পরে আপনার বিভাগগুলি সম্পাদনা বা মুছতে পারেন।"
},
"helpPageTooSlow": {
"message": "গতি অত্যন্ত ধীর।"
},
"helpPageTooSlow1": {
"message": "আপনি যদি ব্যবহার করতে চান তাইলে হটকি রয়েছে। স্পনসর বিভাগের শুরু/শেষ নির্দেশ করতে সেমিকোলন কী টিপুন এবং জমা দেওয়ার জন্য অ্যাপোস্ট্রোফে ক্লিক করুন। এগুলি পরিবর্তন করা যেতে পারে। আপনি যদি QWERTY ব্যবহার না করেন তবে আপনার সম্ভবত কীবাইন্ডিং পরিবর্তন করা উচিত।"
},
"helpPageCopyOfDatabase": {
"message": "আমি কি ডাটাবেসের একটি অনুলিপি পেতে পারি? আপনি হারিয়ে গেলে হলে কি হবে?"
},
"helpPageCopyOfDatabase1": {
"message": "ডাটাবেসটি সবার জন্য প্রকাশিত এবং পাওয়া যাবে এখানেঃ "
},
"helpPageCopyOfDatabase2": {
"message": "এটির সোর্স কোড অবাধে উপলব্ধ। সুতরাং, যদি আমার কিছু ঘটেও যায় তবে আপনার জমাকৃত অংশগুলি হারিয়ে যাবে না।"
},
"helpPageNews": {
"message": "খবর এবং এটি কীভাবে তৈরি হয়"
},
"helpPageSourceCode": {
"message": "আমি সোর্স কোডটি কোথায় পেতে পারি?"
},
"Credits": {
"message": "কৃতিত্ব"
},
"LearnMore": {
"message": "আরও জানুন"
},
"CopyDownvoteButtonInfo": {
"message": "ডাউনভোট করে আপনার জন্য পুনরায় জমা দেওয়ার জন্য একটি স্থানীয় অনুলিপি তৈরি করে"
},
"OpenCategoryWikiPage": {
"message": "এই বিভাগের উইকি পাতা খুলুন."
},
"CopyAndDownvote": {
"message": "কপি এবং ডাউনভোট"
},
"ContinueVoting": {
"message": "ভোট করা চালিয়ে যান"
},
"ChangeCategoryTooltip": {
"message": "এটি তাৎক্ষনিকভাবে আপনার অংশে দেওয়া হবে"
},
"downvote": {
"message": "ডাউনভোট"
},
"upvote": {
"message": "আপভোট"
},
"hideSegment": {
"message": "অংশ আড়াল করুন"
},
"SponsorTimeEditScrollNewFeature": {
"message": "দ্রুত সময়টি পরিবর্কতন করতে সম্পাদনা বাক্সে ঘুরে দেখার সময় আপনার মাউস এর হুইয়িলটি ব্যবহার করুন। কন্ট্রল বা শিফট কী এর সংমিশ্রণগুলি পরিবর্তনগুলি আরো নিখুতভাবে টিউন করতে ব্যবহার করা যেতে পারে।"
},
"categoryPillNewFeature": {
"message": "নতুন! দেখুন যখন কোনও ভিডিও সম্পূর্ণ স্পনসর করা বা স্ব-প্রচার হয়"
},
"dayAbbreviation": {
"message": " দিন",
"description": "100d"
},
"hourAbbreviation": {
"message": " ঘণ্টা",
"description": "100h"
},
"optionsTabBehavior": {
"message": "আচরণ",
"description": "Appears in Options as a tab header for options related to categories and skipping behavior. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabInterface": {
"message": "ইন্টারফেস",
"description": "Appears in Options as a tab header for options related to GUI and sounds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabKeyBinds": {
"message": "কীবোর্ড শর্টকাট",
"description": "Appears in Options as a tab header for keybinds. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabBackup": {
"message": "ব্যাকআপ এবং পুনঃস্থাপন",
"description": "Appears in Options as a tab header for options related to saving/restoring your settings. To fit inside the button, it should not be longer than ~20-25 characters (depending on their width)."
},
"optionsTabAdvanced": {
"message": "বিবিধ",
"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": {
"message": "নোটিশ প্রদর্শন করা বন্ধ করুন",
"description": "Option label"
},
"unbind": {
"message": "বাদ দিন",
"description": "Unbind keyboard shortcut"
},
"notSet": {
"message": "নির্ধারণ করা হয়নি"
},
"change": {
"message": "বদল করুন"
},
"youtubeKeybindWarning": {
"message": "এটি একটি অন্তর্নির্মিত ইউটিউব শর্টকাট। আপনি কি নিশ্চিত যে আপনি এটি ব্যবহার করতে চান?"
},
"betaServerWarning": {
"message": "বেটা সার্ভার চালু করা হয়েছে!"
},
"openOptionsPage": {
"message": "বিকল্প পাতা খুলুন"
}
}

View File

@@ -101,7 +101,7 @@
"message": "It seems the server is down. Contact the dev immediately."
},
"connectionError": {
"message": "A connection error has occured. Error code: "
"message": "A connection error has occurred. Error code: "
},
"clearTimes": {
"message": "Clear Segments"
@@ -356,7 +356,7 @@
"message": "Show Time With Skips Removed"
},
"showTimeWithSkipsDescription": {
"message": "This time appears in brackets next to the current time on below the seekbar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seekbar\"."
"message": "This time appears in brackets next to the current time on below the Seek Bar. This shows the total video duration minus any segments. This includes segments marked as only \"Show In Seek Bar\"."
},
"youHaveSkipped": {
"message": "You've skipped "
@@ -410,7 +410,7 @@
"message": "Supported Sites: "
},
"optionsInfo": {
"message": "Enable Invidious support, disable autoskip, hide buttons and more."
"message": "Enable Invidious support, disable auto skip, hide buttons and more."
},
"addInvidiousInstance": {
"message": "Add 3rd-Party Client Instance"
@@ -499,7 +499,7 @@
"incorrectlyFormattedOptions": {
"message": "This JSON is not formatted correctly. Your options have not been changed."
},
"confirmNoticeTitle" : {
"confirmNoticeTitle": {
"message": "Submit Segment"
},
"submit": {
@@ -947,5 +947,11 @@
},
"openOptionsPage": {
"message": "Open options page"
},
"resetToDefault": {
"message": "Reset settings to default"
},
"confirmResetToDefault": {
"message": "Are you sure you want to reset all settings to their default values? This cannot be undone."
}
}

View File

@@ -164,6 +164,9 @@
"copyPublicID": {
"message": "Copiar ID Pública de Usuário"
},
"copySegmentID": {
"message": "Copiar ID do segmento"
},
"discordAdvert": {
"message": "Junte-se ao servidor do discord oficial para dar dicas e sugestões!"
},
@@ -879,6 +882,15 @@
"ChangeCategoryTooltip": {
"message": "Isto irá aplicar instantaneamente seus segmentos"
},
"downvote": {
"message": "Voto negativo"
},
"upvote": {
"message": "Voto positivo"
},
"hideSegment": {
"message": "Ocultar segmento"
},
"SponsorTimeEditScrollNewFeature": {
"message": "Use a roda do mouse enquanto mantêm o cursor sobre a caixa de edição para ajustar o tempo rapidamente. Combinações das teclas ctrl e shift podem ser usadas para refinar as mudanças."
},

View File

@@ -186,7 +186,7 @@
"message": "Detta döljer knapparna på YouTube-spelaren som du kan skicka in segment med som ska hoppas över."
},
"showSkipButton": {
"message": "Behåll knappen hoppa till markerat på spelaren"
"message": "Behåll knappen hoppa till höjdpunkt på spelaren"
},
"showInfoButton": {
"message": "Visa Infoknapp På YouTube-spelaren"
@@ -613,7 +613,7 @@
"message": "Icke-musik"
},
"category_poi_highlight": {
"message": "Markera"
"message": "Höjdpunkt"
},
"category_poi_highlight_description": {
"message": "Den del av videon som de flesta letar efter. Liknande kommentarer \"Video börjar på x\"."

View File

@@ -17,6 +17,10 @@
transition: transform .1s cubic-bezier(0,0,0.2,1);
}
.ytm-progress-bar > #previewbar {
height: 3px;
}
#previewbar.hovered {
transform: scaleY(1)
}
@@ -649,4 +653,6 @@ input::-webkit-inner-spin-button {
#sponsorBlockDurationAfterSkips.ytm-time-display {
padding-left: 4px;
margin: 0px;
color: #fff;
opacity: .7;
}

View File

@@ -368,6 +368,12 @@
</div>
</div>
<div data-type="button-press" data-sync="resetToDefault" data-confirm-message="confirmResetToDefault">
<div class="option-button trigger-button">
__MSG_resetToDefault__
</div>
</div>
</div>
<div id="advanced" class="option-group hidden">

View File

@@ -1,7 +1,7 @@
import * as React from "react";
import * as CompileConfig from "../../config.json";
import Config from "../config"
import { Category, ContentContainer, SponsorHideType, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
import { Category, ContentContainer, SponsorTime, NoticeVisbilityMode, ActionType, SponsorSourceType, SegmentUUID } from "../types";
import NoticeComponent from "./NoticeComponent";
import NoticeTextSelectionComponent from "./NoticeTextSectionComponent";
import Utils from "../utils";
@@ -72,7 +72,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
amountOfPreviousNotices: number;
showInSecondSlot: boolean;
idSuffix: string;
noticeRef: React.MutableRefObject<NoticeComponent>;
@@ -105,7 +105,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
if (this.segments.length > 1) {
this.segments.sort((a, b) => a.segment[0] - b.segment[0]);
}
// This is the suffix added at the end of every id
for (const segment of this.segments) {
this.idSuffix += segment.UUID;
@@ -168,7 +168,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
noticeStyle.transform = "scale(0.8) translate(10%, 10%)";
}
// If it started out as smaller, always keep the
// If it started out as smaller, always keep the
// skip button there
const showFirstSkipButton = this.props.smaller || this.segments[0].actionType === ActionType.Mute;
const firstColumn = showFirstSkipButton ? (
@@ -181,7 +181,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
showInSecondSlot={this.showInSecondSlot}
idSuffix={this.idSuffix}
fadeIn={true}
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
startFaded={Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAll
|| (Config.config.noticeVisibilityMode >= NoticeVisbilityMode.FadedForAutoSkip && this.autoSkip)}
timed={true}
maxCountdownTime={this.state.maxCountdownTime}
@@ -205,7 +205,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
key={0}>
{/* Vote Button Container */}
{!this.state.thanksForVotingText ?
{!this.state.thanksForVotingText ?
<td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix}
className="sponsorTimesVoteButtonsContainer">
@@ -268,7 +268,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
? this.getSkipButton(1) : null}
{/* Never show button */}
{!this.autoSkip || this.props.startReskip ? "" :
{!this.autoSkip || this.props.startReskip ? "" :
<td className="sponsorSkipNoticeRightSection"
key={1}>
<button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton"
@@ -343,7 +343,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
}
getSkipButton(buttonIndex: number): JSX.Element {
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
if (this.state.showSkipButton[buttonIndex] && (this.segments.length > 1
|| this.segments[0].actionType !== ActionType.Poi
|| this.props.unskipTime)) {
@@ -365,8 +365,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
className="sponsorSkipObject sponsorSkipNoticeButton"
style={style}
onClick={() => this.prepAction(buttonIndex === 1 ? SkipNoticeAction.Unskip1 : SkipNoticeAction.Unskip0)}>
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
+ (!forceSeek && this.state.showKeybindHint
{this.getSkipButtonText(buttonIndex, forceSeek ? ActionType.Skip : null)
+ (!forceSeek && this.state.showKeybindHint
? " (" + keybindToString(Config.config.skipKeybind) + ")" : "")}
</button>
</span>
@@ -379,7 +379,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
for (let i = 0; i < this.segments.length; i++) {
elements.push(
<button className="sponsorSkipObject sponsorSkipNoticeButton"
style={{opacity: this.getSubmissionChooserOpacity(i),
style={{opacity: this.getSubmissionChooserOpacity(i),
color: this.getSubmissionChooserColor(i)}}
onClick={() => this.performAction(i)}
key={"submission" + i + this.segments[i].category + this.idSuffix}>
@@ -404,7 +404,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
getSubmissionChooserColor(index: number): string {
const isDownvote = this.state.actionState == SkipNoticeAction.Downvote;
const isCopyDownvote = this.state.actionState == SkipNoticeAction.CopyDownvote;
const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote)
const shouldWarnUser = Config.config.isVip && (isDownvote || isCopyDownvote)
&& this.segments[index].locked === 1;
return shouldWarnUser ? this.lockedColor : this.unselectedColor;
@@ -480,8 +480,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
/**
* Performs the action from the current state
*
* @param index
*
* @param index
*/
performAction(index: number, action?: SkipNoticeAction): void {
switch (action ?? this.state.actionState) {
@@ -620,7 +620,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
// See if the title should be changed
if (!this.autoSkip) {
newState.noticeTitle = chrome.i18n.getMessage("noticeTitle");
}
}
//reset countdown
this.setState(newState, () => {
@@ -723,7 +723,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
messages
});
}
addVoteButtonInfo(message: string): void {
this.setState({
thanksForVotingText: message
@@ -786,7 +786,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: {
return chrome.i18n.getMessage("unmute");
}
case ActionType.Skip:
case ActionType.Skip:
default: {
return chrome.i18n.getMessage("unskip");
}
@@ -799,7 +799,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: {
return chrome.i18n.getMessage("mute");
}
case ActionType.Skip:
case ActionType.Skip:
default: {
return chrome.i18n.getMessage("reskip");
}
@@ -812,7 +812,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
case ActionType.Mute: {
return chrome.i18n.getMessage("mute");
}
case ActionType.Skip:
case ActionType.Skip:
default: {
return chrome.i18n.getMessage("skip");
}

View File

@@ -1,6 +1,4 @@
import * as React from "react";
import Config from "../config";
import { Category, SegmentUUID, SponsorTime } from "../types";
export interface TooltipProps {
text: string;

View File

@@ -1,6 +1,6 @@
import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { keybindEquals } from "./utils/configUtils";
interface SBConfig {
@@ -113,6 +113,7 @@ export interface SBObject {
local: SBStorage;
forceSyncUpdate(prop: string): void;
forceLocalUpdate(prop: string): void;
resetToDefault(): void;
}
const Config: SBObject = {
@@ -289,7 +290,8 @@ const Config: SBObject = {
config: null,
local: null,
forceSyncUpdate,
forceLocalUpdate
forceLocalUpdate,
resetToDefault
};
// Function setup
@@ -300,7 +302,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
for (const key in changes) {
Config.cachedSyncConfig[key] = changes[key].newValue;
}
for (const callback of Config.configSyncListeners) {
callback(changes);
}
@@ -310,7 +312,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
}
}
});
const syncHandler: ProxyHandler<SBConfig> = {
set<K extends keyof SBConfig>(obj: SBConfig, prop: K, value: SBConfig[K]) {
Config.cachedSyncConfig[prop] = value;
@@ -327,10 +329,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data;
},
deleteProperty(obj: SBConfig, prop: keyof SBConfig) {
chrome.storage.sync.remove(<string> prop);
return true;
}
@@ -352,10 +354,10 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data;
},
deleteProperty(obj: SBStorage, prop: keyof SBStorage) {
chrome.storage.local.remove(<string> prop);
return true;
}
@@ -379,7 +381,7 @@ function forceLocalUpdate(prop: string): void {
});
}
async function fetchConfig(): Promise<void> {
async function fetchConfig(): Promise<void> {
await Promise.all([new Promise<void>((resolve) => {
chrome.storage.sync.get(null, function(items) {
Config.cachedSyncConfig = <SBConfig> <unknown> items;
@@ -387,7 +389,7 @@ async function fetchConfig(): Promise<void> {
});
}), new Promise<void>((resolve) => {
chrome.storage.local.get(null, function(items) {
Config.cachedLocalStorage = <SBStorage> <unknown> items;
Config.cachedLocalStorage = <SBStorage> <unknown> items;
resolve();
});
})]);
@@ -431,9 +433,9 @@ function migrateOldSyncFormats(config: SBConfig) {
if (!config["autoSkipOnMusicVideosUpdate"]) {
config["autoSkipOnMusicVideosUpdate"] = true;
for (const selection of config.categorySelections) {
if (selection.name === "music_offtopic"
if (selection.name === "music_offtopic"
&& selection.option === CategorySkipOption.AutoSkip) {
config.autoSkipOnMusicVideos = true;
break;
}
@@ -522,6 +524,16 @@ function addDefaults() {
}
}
function resetToDefault() {
chrome.storage.sync.set({
...Config.syncDefaults,
userID: Config.config.userID,
minutesSaved: Config.config.minutesSaved,
skipCount: Config.config.skipCount,
sponsorTimesContributed: Config.config.sponsorTimesContributed
});
}
// Sync config
setupConfig();

View File

@@ -20,6 +20,7 @@ import { isSafari, keybindEquals } from "./utils/configUtils";
import { CategoryPill } from "./render/CategoryPill";
import { AnimationUtils } from "./utils/animationUtils";
import { GenericUtils } from "./utils/genericUtils";
import { logDebug } from "./utils/logger";
// Hack to get the CSS loaded on permission-based sites (Invidious)
utils.wait(() => Config.config !== null, 5000, 10).then(addCSS);
@@ -275,6 +276,7 @@ function resetValues() {
switchingVideos = false;
} else {
switchingVideos = true;
logDebug("Setting switching videos to true (reset data)");
}
firstEvent = true;
@@ -292,7 +294,7 @@ function resetValues() {
async function videoIDChange(id) {
//if the id has not changed return unless the video element has changed
if (sponsorVideoID === id && isVisible(video)) return;
if (sponsorVideoID === id && (isVisible(video) || !video)) return;
//set the global videoID
sponsorVideoID = id;
@@ -319,9 +321,6 @@ async function videoIDChange(id) {
}
}
// Get new video info
// getVideoInfo(); // Seems to have been replaced
// Update whitelist data when the video data is loaded
whitelistCheck();
@@ -391,18 +390,32 @@ function handleMobileControlsMutations(): void {
function createPreviewBar(): void {
if (previewBar !== null) return;
const progressElementSelectors = [
// For mobile YouTube
".progress-bar-background",
// For YouTube
".ytp-progress-bar-container",
".no-model.cue-range-markers",
// For Invidious/VideoJS
".vjs-progress-holder"
const progressElementOptions = [{
// For mobile YouTube
selector: ".progress-bar-background",
isVisibleCheck: true
}, {
// For new mobile YouTube (#1287)
selector: ".ytm-progress-bar",
isVisibleCheck: true
}, {
// For Desktop YouTube
selector: ".ytp-progress-bar-container",
isVisibleCheck: true
}, {
// For Desktop YouTube
selector: ".no-model.cue-range-marker",
isVisibleCheck: true
}, {
// For Invidious/VideoJS
selector: ".vjs-progress-holder",
isVisibleCheck: false
}
];
for (const selector of progressElementSelectors) {
const el = findValidElement(document.querySelectorAll(selector));
for (const option of progressElementOptions) {
const allElements = document.querySelectorAll(option.selector) as NodeListOf<HTMLElement>;
const el = option.isVisibleCheck ? findValidElement(allElements) : allElements[0];
if (el) {
previewBar = new PreviewBar(el, onMobileYouTube, onInvidious);
@@ -434,6 +447,8 @@ function videoOnReadyListener(): void {
}
function cancelSponsorSchedule(): void {
logDebug("Pausing skipping");
if (currentSkipSchedule !== null) {
clearTimeout(currentSkipSchedule);
currentSkipSchedule = null;
@@ -456,20 +471,16 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
// Reset lastCheckVideoTime
lastCheckVideoTime = -1;
lastCheckTime = 0;
logDebug("[SB] Ad playing, pausing skipping");
return;
}
logDebug(`Considering to start skipping: ${!video}, ${video?.paused}`);
if (!video || video.paused) return;
if (currentTime === undefined || currentTime === null) {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6){
currentTime = virtualTime;
} else {
currentTime = video.currentTime;
}
currentTime = getVirtualTime();
}
lastTimeFromWaitingEvent = null;
@@ -491,6 +502,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
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];
@@ -511,6 +523,8 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
}
}
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;
@@ -520,7 +534,7 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
let forcedIncludeNonIntersectingSegments = true;
if (incorrectVideoCheck(videoID, currentSkip)) return;
forceVideoTime ||= video.currentTime;
forceVideoTime ||= Math.max(video.currentTime, getVirtualTime());
if (forceVideoTime >= skipTime[0] && forceVideoTime < skipTime[1]) {
skipToTime({
@@ -548,9 +562,11 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
} else {
const delayTime = timeUntilSponsor * 1000 * (1 / video.playbackRate);
if (delayTime < 300) {
// For Firefox, use interval instead of timeout near the end to combat imprecise video time
// Use interval instead of timeout near the end to combat imprecise video time
const startIntervalTime = performance.now();
const startVideoTime = Math.max(currentTime, video.currentTime);
logDebug(`Starting setInterval skipping ${video.currentTime} to skip at ${skipTime[0]}`);
currentSkipInterval = setInterval(() => {
const intervalDuration = performance.now() - startIntervalTime;
if (intervalDuration >= delayTime || video.currentTime >= skipTime[0]) {
@@ -565,12 +581,26 @@ function startSponsorSchedule(includeIntersectingSegments = false, currentTime?:
}
}, 1);
} else {
logDebug(`Starting timeout to skip ${video.currentTime} to skip at ${skipTime[0]}`);
// Schedule for right before to be more precise than normal timeout
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 100));
currentSkipSchedule = setTimeout(skippingFunction, Math.max(0, delayTime - 150));
}
}
}
function getVirtualTime(): number {
const virtualTime = lastTimeFromWaitingEvent ?? (lastKnownVideoTime.videoTime ?
(performance.now() - lastKnownVideoTime.preciseTime) / 1000 + lastKnownVideoTime.videoTime : null);
if ((lastTimeFromWaitingEvent || !utils.isFirefox())
&& !isSafari() && virtualTime && Math.abs(virtualTime - video.currentTime) < 0.6) {
return virtualTime;
} else {
return video.currentTime;
}
}
function inMuteSegment(currentTime: number): boolean {
const checkFunction = (segment) => segment.actionType === ActionType.Mute && segment.segment[0] <= currentTime && segment.segment[1] > currentTime;
return sponsorTimes?.some(checkFunction) || sponsorTimesSubmitting.some(checkFunction);
@@ -641,6 +671,8 @@ function setupVideoListeners() {
if (!Config.config.disableSkipping) {
switchingVideos = false;
let startedWaiting = false;
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
// This check makes sure that changing the video resolution doesn't cause the extension to think it
@@ -652,6 +684,8 @@ function setupVideoListeners() {
if (switchingVideos) {
switchingVideos = false;
logDebug("Setting switching videos to false");
// If already segments loaded before video, retry to skip starting segments
if (sponsorTimes) startSkipScheduleCheckingForStartSponsors();
}
@@ -671,6 +705,12 @@ function setupVideoListeners() {
});
video.addEventListener('playing', () => {
updateVirtualTime();
if (startedWaiting) {
startedWaiting = false;
logDebug(`[SB] Playing event after buffering: ${Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
|| (lastCheckVideoTime !== video.currentTime && Date.now() - lastCheckTime > 2000)}`);
}
// Make sure it doesn't get double called with the play event
if (Math.abs(lastCheckVideoTime - video.currentTime) > 0.3
@@ -709,8 +749,13 @@ function setupVideoListeners() {
cancelSponsorSchedule();
};
video.addEventListener('pause', paused);
video.addEventListener('waiting', paused);
video.addEventListener('pause', () => paused());
video.addEventListener('waiting', () => {
logDebug("[SB] Not skipping due to buffering");
startedWaiting = true;
paused();
});
startSponsorSchedule();
}
@@ -899,12 +944,10 @@ function startSkipScheduleCheckingForStartSponsors() {
// See if there are any starting sponsors
let startingSegmentTime = getStartTimeFromUrl(document.URL) || -1;
let found = false;
let startingSegment: SponsorTime = null;
for (const time of sponsorTimes) {
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0];
startingSegment = time;
found = true;
break;
}
@@ -914,7 +957,6 @@ function startSkipScheduleCheckingForStartSponsors() {
if (time.segment[0] <= video.currentTime && time.segment[0] > startingSegmentTime && time.segment[1] > video.currentTime
&& time.actionType !== ActionType.Poi) {
startingSegmentTime = time.segment[0];
startingSegment = time;
found = true;
break;
}
@@ -952,26 +994,6 @@ function startSkipScheduleCheckingForStartSponsors() {
}
}
/**
* Get the video info for the current tab from YouTube
*
* TODO: Replace
*/
async function getVideoInfo(): Promise<void> {
const result = await utils.asyncRequestToCustomServer("GET", "https://www.youtube.com/get_video_info?video_id=" + sponsorVideoID + "&html5=1&c=TVHTML5&cver=7.20190319");
if (result.ok) {
const decodedData = decodeURIComponent(result.responseText).match(/player_response=([^&]*)/)[1];
if (!decodedData) {
console.error("[SB] Failed at getting video info from YouTube.");
console.error("[SB] Data returned from YouTube: " + result.responseText);
return;
}
videoInfo = JSON.parse(decodedData);
}
}
function getYouTubeVideoID(document: Document): string | boolean {
const url = document.URL;
// clips should never skip, going from clip to full video has no indications.
@@ -1106,7 +1128,7 @@ async function whitelistCheck() {
?? document.querySelector("a.ytp-title-channel-logo") // YouTube Embed
?? document.querySelector(".channel-profile #channel-name")?.parentElement.parentElement // Invidious
?? document.querySelector("a.slim-owner-icon-and-title")) // Mobile YouTube
?.getAttribute("href")?.match(/\/channel\/(UC[a-zA-Z0-9_-]{22})/)[1];
?.getAttribute("href")?.match(/(?:\/c\/|\/channel\/)(UC[a-zA-Z0-9_-]{22}|[a-zA-Z0-9_-]+)/)?.[1];
try {
await utils.wait(() => !!getChannelID(), 6000, 20);
@@ -1114,14 +1136,12 @@ async function whitelistCheck() {
channelIDInfo = {
status: ChannelIDStatus.Found,
id: getChannelID().match(/^\/?([^\s/]+)/)[0]
}
};
} catch (e) {
channelIDInfo = {
status: ChannelIDStatus.Failed,
id: null
}
return;
};
}
//see if this is a whitelisted channel
@@ -1911,8 +1931,6 @@ async function sendSubmitMessage() {
return;
}
sponsorsLookup();
// Add loading animation
playerButtons.submit.image.src = chrome.extension.getURL("icons/PlayerUploadIconSponsorBlocker.svg");
const stopAnimation = AnimationUtils.applyLoadingAnimation(playerButtons.submit.button, 1, () => updateEditButtonsOnPlayer());

View File

@@ -144,8 +144,10 @@ class PreviewBar {
this.parent = parent;
if (this.onMobileYouTube) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
if (parent.classList.contains("progress-bar-background")) {
parent.style.backgroundColor = "rgba(255, 255, 255, 0.3)";
parent.style.opacity = "1";
}
this.container.style.transform = "none";
} else if (!this.onInvidious) {

View File

@@ -2,10 +2,7 @@ import Config from "../config";
import { SponsorTime } from "../types";
import { getSkippingText } from "../utils/categoryUtils";
import { keybindToString } from "../utils/configUtils";
import Utils from "../utils";
import { AnimationUtils } from "../utils/animationUtils";
const utils = new Utils();
export interface SkipButtonControlBarProps {
skip: (segment: SponsorTime) => void;
@@ -53,7 +50,7 @@ export class SkipButtonControlBar {
this.skipIcon.id = "sbSkipIconControlBarImage";
this.textContainer = document.createElement("div");
this.container.appendChild(this.skipIcon);
this.container.appendChild(this.textContainer);
this.container.addEventListener("click", () => this.toggleSkip());
@@ -73,7 +70,7 @@ export class SkipButtonControlBar {
attachToPage(): void {
const mountingContainer = this.getMountingContainer();
this.chapterText = document.querySelector(".ytp-chapter-container");
if (mountingContainer && !mountingContainer.contains(this.container)) {
if (this.onMobileYouTube) {
mountingContainer.appendChild(this.container);
@@ -151,7 +148,7 @@ export class SkipButtonControlBar {
}
disableText(): void {
if (Config.config.hideVideoPlayerControls || Config.config.hideSkipButtonPlayerControls) {
if (Config.config.hideSkipButtonPlayerControls) {
this.disable();
return;
}
@@ -172,10 +169,10 @@ export class SkipButtonControlBar {
const overlay = document.getElementById("player-control-overlay");
if (overlay && this.enabled) {
if (overlay?.classList?.contains("pointer-events-off")) {
this.hideButton();
} else {
if (overlay?.classList?.contains("fadein")) {
this.showButton();
} else {
this.hideButton();
}
}
}
@@ -220,4 +217,3 @@ export class SkipButtonControlBar {
this.container.style.left = this.left + "px";
}
}

View File

@@ -232,12 +232,22 @@ async function init() {
}
case "button-press": {
const actionButton = optionsElements[i].querySelector(".trigger-button");
const confirmMessage = optionsElements[i].getAttribute("data-confirm-message");
switch(optionsElements[i].getAttribute("data-sync")) {
case "copyDebugInformation":
actionButton.addEventListener("click", copyDebugOutputToClipboard);
break;
}
actionButton.addEventListener("click", () => {
if (confirmMessage !== null && !confirm(chrome.i18n.getMessage(confirmMessage))) {
return;
}
switch (optionsElements[i].getAttribute("data-sync")) {
case "copyDebugInformation":
copyDebugOutputToClipboard();
break;
case "resetToDefault":
Config.resetToDefault();
window.location.reload();
break;
}
});
break;
}

12
src/utils/logger.ts Normal file
View File

@@ -0,0 +1,12 @@
window["SBLogs"] = {
debug: [],
warn: []
};
export function logDebug(message: string) {
window["SBLogs"].debug.push(`[${new Date().toISOString()}] ${message}`);
}
export function logWarn(message: string) {
window["SBLogs"].warn.push(`[${new Date().toISOString()}] ${message}`);
}

View File

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

View File

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

View File

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