diff options
-rw-r--r-- | .github/workflows/release.yml | 39 | ||||
-rw-r--r-- | config.json.example | 2 | ||||
-rw-r--r-- | manifest/manifest.json | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | public/_locales/en/messages.json | 24 | ||||
-rw-r--r-- | public/content.css | 17 | ||||
-rw-r--r-- | public/popup.html | 2 | ||||
-rw-r--r-- | src/background.ts | 8 | ||||
-rw-r--r-- | src/components/SkipNoticeComponent.tsx | 174 | ||||
-rw-r--r-- | src/components/SponsorTimeEditComponent.tsx | 28 | ||||
-rw-r--r-- | src/components/SubmissionNoticeComponent.tsx | 19 | ||||
-rw-r--r-- | src/config.ts | 4 | ||||
-rw-r--r-- | src/content.ts | 61 | ||||
-rw-r--r-- | src/js-components/previewBar.ts | 86 | ||||
-rw-r--r-- | src/types.ts | 6 |
15 files changed, 407 insertions, 66 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2437a778..1043661f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,7 +28,7 @@ jobs: - run: mkdir ./builds - uses: montudor/[email protected] with: - args: zip -qq -r ./builds/ChromeExtension.zip ./dist + args: zip -qq -r ./builds/ChromeExtension.zip ./dist/* # Create Firefox artifacts - name: Create Firefox artifacts @@ -39,7 +39,7 @@ jobs: path: dist - uses: montudor/[email protected] with: - args: zip -qq -r ./builds/FirefoxExtension.zip ./dist + args: zip -qq -r ./builds/FirefoxExtension.zip ./dist/* # Create Beta artifacts (Builds with the name changed to beta) - name: Create Chrome Beta artifacts @@ -50,7 +50,7 @@ jobs: path: dist - uses: montudor/[email protected] with: - args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist + args: zip -qq -r ./builds/ChromeExtensionBeta.zip ./dist/* - name: Create Firefox Beta artifacts run: npm run build:firefox -- --env.stream=beta @@ -60,7 +60,24 @@ jobs: path: dist - uses: montudor/[email protected] with: - args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist + args: zip -qq -r ./builds/FirefoxExtensionBeta.zip ./dist/* + + # Create Firefox Signed Beta version + - name: Create Firefox Signed Beta artifacts + run: npm run web-sign + env: + WEB_EXT_API_KEY: ${{ secrets.WEB_EXT_API_KEY }} + WEB_EXT_API_SECRET: ${{ secrets.WEB_EXT_API_SECRET }} + - name: Install rename + run: sudo apt-get install rename + - name: Install signed file + run: cd ./web-ext-artifacts + run: rename 's/.*/FirefoxSignedInstaller.xpi/' * + run: cd .. + - uses: actions/upload-artifact@v1 + with: + name: FirefoxExtensionSigned.xpi + path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi # Upload each release asset - name: Upload to release @@ -73,8 +90,22 @@ jobs: - name: Upload to release uses: Shopify/upload-to-release@master with: + args: builds/ChromeExtensionBeta.zip + name: ChromeExtensionBeta.zip + path: ./builds/ChromeExtensionBeta.zip + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Upload to release + uses: Shopify/upload-to-release@master + with: args: builds/FirefoxExtension.zip name: FirefoxExtension.zip path: ./builds/FirefoxExtension.zip repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Upload to release + uses: Shopify/upload-to-release@master + with: + args: web-ext-artifacts/FirefoxSignedInstaller.xpi + name: FirefoxSignedInstaller.xpi + path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi + repo-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/config.json.example b/config.json.example index 0919d30e..aad02a3c 100644 --- a/config.json.example +++ b/config.json.example @@ -2,5 +2,5 @@ "serverAddress": "https://sponsor.ajay.app", "testingServerAddress": "https://sponsor.ajay.app/test", "serverAddressComment": "This specifies the default SponsorBlock server to conect to", - "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "offtopic"] + "categoryList": ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"] } diff --git a/manifest/manifest.json b/manifest/manifest.json index 5b302f1f..7a41a671 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_fullName__", "short_name": "__MSG_Name__", - "version": "1.2.27", + "version": "1.2.28", "default_locale": "en", "description": "__MSG_Description__", "content_scripts": [{ diff --git a/package.json b/package.json index c778277c..bccb8af8 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ }, "scripts": { "web-run": "npm run web-run:chrome", + "web-sign": "web-ext sign -s dist", "web-run:firefox": "cd dist && web-ext run --start-url https://addons.mozilla.org/firefox/addon/ublock-origin/", "web-run:chrome": "cd dist && web-ext run --start-url https://chrome.google.com/webstore/detail/ublock-origin/cjpalhdlnbpafiamejdnhcphjbkeiagm -t chromium", "build": "npm run build:chrome", diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index 086ce0d5..864b957b 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -351,7 +351,7 @@ "message": "Support Invidious" }, "supportInvidiousDescription": { - "message": "Invidious (invidio.us) is a third party YouTube client. To enable support, you must accept the extra permissions. This does NOT work in incongnito on Chrome and other Chromium variants." + "message": "Invidious (invidio.us) is a third party YouTube client. To enable support, you must accept the extra permissions. This does NOT work in incognito on Chrome and other Chromium variants." }, "optionsInfo": { "message": "Enable Invidious support, disable autoskip, hide buttons and more." @@ -490,19 +490,22 @@ "message": "Sponsor" }, "category_intro": { - "message": "Intro" + "message": "Intro Animation" }, "category_outro": { - "message": "Outro" + "message": "Endcards/Credits" }, "category_interaction": { - "message": "Interaction (Redundant Like, Subscribe, Follow, etc.)" + "message": "Interaction Reminder (Subscribe)" }, "category_selfpromo": { "message": "Self-Promotion and Merchandise" }, - "category_offtopic": { - "message": "Offtopic tangent (Subjective)" + "category_music_offtopic": { + "message": "Music: Non-Music Section" + }, + "category_livestream_messages": { + "message": "Livestream: Donation/Message Readings" }, "disable": { "message": "Disable" @@ -554,5 +557,14 @@ }, "forceChannelCheckPopup": { "message": "Consider Enabling Force Channel Check Before Skipping Sponsors" + }, + "downvoteDescription": { + "message": "Incorrect" + }, + "incorrectCategory": { + "message": "Wrong Category" + }, + "nonMusicCategoryOnMusic": { + "message": "This video is categorized as music. Are you sure you would like to submit segments with non-music categories? Unless this video is not actually music, you should not be submitting this segment. Please read the guidelines if you are confused." } } diff --git a/public/content.css b/public/content.css index 51213bd2..66838a9f 100644 --- a/public/content.css +++ b/public/content.css @@ -11,11 +11,28 @@ z-index: 40; } +.sbHidden { + display: none !important; +} + + .previewbar { display: inline-block; height: 100%; } +/* Preview Bar page hacks */ + +.sbTooltipTwoTitleThumbnailOffset { + bottom: -5px !important; +} + +.sbTooltipOneTitleThumbnailOffset { + bottom: 10px !important; +} + +/* */ + .popup { z-index: 10; width: 100%; diff --git a/public/popup.html b/public/popup.html index 3fdb3a73..bede3db5 100644 --- a/public/popup.html +++ b/public/popup.html @@ -135,7 +135,7 @@ <span id="sponsorTimesSkipsDoneDisplay" class="popupElement"> 0 </span> - <span id="sponsorTimesSkipsDoneEndWord" class="popupElement">__MSG_Segments__</span> (since February). + <span id="sponsorTimesSkipsDoneEndWord" class="popupElement">__MSG_Segments__</span> </div> <div id="sponsorTimeSavedContainer" class="popupElement" style="display: none"> diff --git a/src/background.ts b/src/background.ts index b204b64c..74382357 100644 --- a/src/background.ts +++ b/src/background.ts @@ -47,7 +47,7 @@ chrome.runtime.onMessage.addListener(function (request, sender, callback) { //this allows the callback to be called later return true; case "submitVote": - submitVote(request.type, request.UUID, callback); + submitVote(request.type, request.UUID, request.category, callback); //this allows the callback to be called later return true; @@ -147,7 +147,7 @@ function addSponsorTime(time, videoID, callback) { }); } -function submitVote(type, UUID, callback) { +function submitVote(type, UUID, category, callback) { let userID = Config.config.userID; if (userID == undefined || userID === "undefined") { @@ -156,8 +156,10 @@ function submitVote(type, UUID, callback) { Config.config.userID = userID; } + let typeSection = (type !== undefined) ? "&type=" + type : "&category=" + category; + //publish this vote - utils.sendRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + "&type=" + type, function(xmlhttp, error) { + utils.sendRequestToServer("POST", "/api/voteOnSponsorTime?UUID=" + UUID + "&userID=" + userID + typeSection, function(xmlhttp, error) { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { callback({ successType: 1 diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 8d53efbb..b4399e65 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,4 +1,5 @@ import * as React from "react"; +import * as CompileConfig from "../../config.json"; import Config from "../config" import { ContentContainer, SponsorHideType } from "../types"; @@ -19,16 +20,19 @@ export interface SkipNoticeProps { } export interface SkipNoticeState { - noticeTitle: string, + noticeTitle: string; - messages: string[], + messages: string[]; - countdownTime: number, + countdownTime: number; maxCountdownTime: () => number; - countdownText: string, + countdownText: string; - unskipText: string, - unskipCallback: () => void + unskipText: string; + unskipCallback: () => void; + + downvoting: boolean; + choosingCategory: boolean; } class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> { @@ -43,10 +47,15 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta idSuffix: any; noticeRef: React.MutableRefObject<NoticeComponent>; + categoryOptionRef: React.RefObject<HTMLSelectElement>; + + // Used to update on config change + configListener: () => void; constructor(props: SkipNoticeProps) { super(props); this.noticeRef = React.createRef(); + this.categoryOptionRef = React.createRef(); this.UUID = props.UUID; this.autoSkip = props.autoSkip; @@ -83,7 +92,10 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta countdownText: null, unskipText: chrome.i18n.getMessage("unskip"), - unskipCallback: this.unskip.bind(this) + unskipCallback: this.unskip.bind(this), + + downvoting: false, + choosingCategory: false } if (!this.autoSkip) { @@ -115,7 +127,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta timed={true} maxCountdownTime={this.state.maxCountdownTime} ref={this.noticeRef} - closeListener={this.props.closeListener}> + closeListener={() => this.closeListener()}> {(Config.config.audioNotificationOnSkip) && <audio ref={(source) => { this.audio = source; }}> <source src={chrome.extension.getURL("icons/beep.ogg")} type="audio/ogg"></source> @@ -124,7 +136,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta {/* Text Boxes */} {this.getMessageBoxes()} - {/* Last Row */} + {/* Bottom Row */} <tr id={"sponsorSkipNoticeSecondRow" + this.idSuffix}> {/* Vote Button Container */} @@ -145,7 +157,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta className="sponsorSkipObject voteButton" src={chrome.extension.getURL("icons/report.png")} title={chrome.i18n.getMessage("reportButtonInfo")} - onClick={() => this.contentContainer().vote(0, this.UUID, this)}> + onClick={() => this.adjustDownvotingState(true)}> </img> @@ -174,6 +186,54 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta } </tr> + {/* Downvote Options Row */} + {this.state.downvoting && + <tr id={"sponsorSkipNoticeDownvoteOptionsRow" + this.idSuffix}> + <td id={"sponsorTimesDownvoteOptionsContainer" + this.idSuffix}> + + {/* Normal downvote */} + <button className="sponsorSkipObject sponsorSkipNoticeButton" + onClick={() => this.contentContainer().vote(0, this.UUID, undefined, this)}> + {chrome.i18n.getMessage("downvoteDescription")} + </button> + + {/* Category vote */} + {Config.config.testingServer && + <button className="sponsorSkipObject sponsorSkipNoticeButton" + onClick={() => this.openCategoryChooser()}> + + {chrome.i18n.getMessage("incorrectCategory")} + </button> + } + </td> + + </tr> + } + + {/* Category Chooser Row */} + {this.state.choosingCategory && + <tr id={"sponsorSkipNoticeCategoryChooserRow" + this.idSuffix}> + <td> + {/* Category Selector */} + <select id={"sponsorTimeCategories" + this.idSuffix} + className="sponsorTimeCategories" + defaultValue={utils.getSponsorTimeFromUUID(this.props.contentContainer().sponsorTimes, this.props.UUID).category} + ref={this.categoryOptionRef} + onChange={this.categorySelectionChange.bind(this)}> + + {this.getCategoryOptions()} + </select> + + {/* Submit Button */} + <button className="sponsorSkipObject sponsorSkipNoticeButton" + onClick={() => this.contentContainer().vote(undefined, this.UUID, this.categoryOptionRef.current.value, this)}> + + {chrome.i18n.getMessage("submit")} + </button> + </td> + </tr> + } + </NoticeComponent> ); } @@ -202,6 +262,70 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta return elements; } + adjustDownvotingState(value: boolean) { + if (!value) this.clearConfigListener(); + + this.setState({ + downvoting: value, + choosingCategory: false + }); + } + + clearConfigListener() { + if (this.configListener) { + Config.configListeners.splice(Config.configListeners.indexOf(this.configListener), 1); + this.configListener = null; + } + } + + openCategoryChooser() { + // Add as a config listener + this.configListener = () => this.forceUpdate(); + Config.configListeners.push(this.configListener); + + this.setState({ + choosingCategory: true, + downvoting: false + }); + } + + getCategoryOptions() { + let elements = []; + + for (const category of Config.config.categorySelections) { + elements.push( + <option value={category.name} + key={category.name}> + {chrome.i18n.getMessage("category_" + category.name)} + </option> + ); + } + + if (elements.length < CompileConfig.categoryList.length) { + // Add show more button + elements.push( + <option value={"moreCategories"} + key={"moreCategories"}> + {chrome.i18n.getMessage("moreCategories")} + </option> + ); + } + + return elements; + } + + categorySelectionChange(event: React.ChangeEvent<HTMLSelectElement>) { + // See if show more categories was pressed + if (event.target.value === "moreCategories") { + // Open options page + chrome.runtime.sendMessage({"message": "openConfig"}); + + // Reset option to original + event.target.value = utils.getSponsorTimeFromUUID(this.props.contentContainer().sponsorTimes, this.props.UUID).category; + return; + } + } + unskip() { this.contentContainer().unskipSponsorTime(this.UUID); @@ -258,22 +382,22 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta } } - afterDownvote() { + afterDownvote(type: number, category: string) { this.addVoteButtonInfo(chrome.i18n.getMessage("voted")); this.setNoticeInfoMessage(chrome.i18n.getMessage("hitGoBack")); + + this.adjustDownvotingState(false); - //remove this sponsor from the sponsors looked up - //find which one it is - for (let i = 0; i < this.contentContainer().sponsorTimes.length; i++) { - if (this.contentContainer().sponsorTimes[i].UUID == this.UUID) { - //this one is the one to hide - - //add this as a hidden sponsorTime - this.contentContainer().sponsorTimes[i].hidden = SponsorHideType.Downvoted; - - this.contentContainer().updatePreviewBar(); - break; + // Change the sponsor locally + let sponsorTime = utils.getSponsorTimeFromUUID(this.contentContainer().sponsorTimes, this.UUID); + if (sponsorTime) { + if (type === 0) { + sponsorTime.hidden = SponsorHideType.Downvoted; + } else if (category) { + sponsorTime.category = category; } + + this.contentContainer().updatePreviewBar(); } } @@ -316,6 +440,12 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta //show button again document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix).style.removeProperty("display"); } + + closeListener() { + this.clearConfigListener(); + + this.props.closeListener(); + } } export default SkipNoticeComponent;
\ No newline at end of file diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 07f20cba..e8530dde 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -29,6 +29,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo categoryOptionRef: React.RefObject<HTMLSelectElement>; + configUpdateListener: () => void; + constructor(props: SponsorTimeEditProps) { super(props); @@ -49,7 +51,16 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo }); // Add as a config listener - Config.configListeners.push(this.configUpdate.bind(this)); + if (!this.configUpdateListener) { + this.configUpdateListener = () => this.configUpdate(); + Config.configListeners.push(this.configUpdate.bind(this)); + } + } + + componentWillUnmount() { + if (this.configUpdateListener) { + Config.configListeners.splice(Config.configListeners.indexOf(this.configUpdate.bind(this))); + } } render() { @@ -61,6 +72,15 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo style.marginTop = "15px"; } + // This method is required to get !important + // https://stackoverflow.com/a/45669262/1985387 + let oldYouTubeDarkStyles = (node) => { + if (node) { + node.style.setProperty("color", "black", "important"); + node.style.setProperty("text-shadow", "none", "important"); + } + }; + // Create time display let timeDisplay: JSX.Element; let sponsorTime = this.props.contentContainer().sponsorTimesSubmitting[this.props.index]; @@ -78,6 +98,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo <input id={"submittingTimeMinutes0" + this.idSuffix} className="sponsorTimeEdit sponsorTimeEditMinutes" + ref={oldYouTubeDarkStyles} type="number" value={this.state.sponsorTimeEdits[0][0]} onChange={(e) => { @@ -90,6 +111,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo <input id={"submittingTimeSeconds0" + this.idSuffix} className="sponsorTimeEdit sponsorTimeEditSeconds" + ref={oldYouTubeDarkStyles} type="number" value={this.state.sponsorTimeEdits[0][1]} onChange={(e) => { @@ -106,6 +128,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo <input id={"submittingTimeMinutes1" + this.idSuffix} className="sponsorTimeEdit sponsorTimeEditMinutes" + ref={oldYouTubeDarkStyles} type="text" value={this.state.sponsorTimeEdits[1][0]} onChange={(e) => { @@ -118,6 +141,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo <input id={"submittingTimeSeconds1" + this.idSuffix} className="sponsorTimeEdit sponsorTimeEditSeconds" + ref={oldYouTubeDarkStyles} type="text" value={this.state.sponsorTimeEdits[1][1]} onChange={(e) => { @@ -236,7 +260,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo } setTimeToNow(index: number) { - this.setTimeTo(index, this.props.contentContainer().v.currentTime); + this.setTimeTo(index, this.props.contentContainer().getRoughCurrentTime()); } setTimeToEnd() { diff --git a/src/components/SubmissionNoticeComponent.tsx b/src/components/SubmissionNoticeComponent.tsx index a9943479..de7d4df9 100644 --- a/src/components/SubmissionNoticeComponent.tsx +++ b/src/components/SubmissionNoticeComponent.tsx @@ -93,13 +93,6 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S <td className="sponsorSkipNoticeRightSection" style={{position: "relative"}}> - {/* Cancel Button */} - <button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton" - onClick={this.cancel.bind(this)}> - - {chrome.i18n.getMessage("cancel")} - </button> - {/* Submit Button */} <button className="sponsorSkipObject sponsorSkipNoticeButton sponsorSkipNoticeRightButton" onClick={this.submit.bind(this)}> @@ -167,6 +160,18 @@ class SubmissionNoticeComponent extends React.Component<SubmissionNoticeProps, S ref.current.saveEditTimes(); } + // Check if any non music categories are being used on a music video + if (this.contentContainer().videoInfo.microformat.playerMicroformatRenderer.category === "Music") { + let sponsorTimesSubmitting = this.props.contentContainer().sponsorTimesSubmitting; + for (const sponsorTime of sponsorTimesSubmitting) { + if (!sponsorTime.category.startsWith("music_")) { + if (!confirm(chrome.i18n.getMessage("nonMusicCategoryOnMusic"))) return; + + break; + } + } + } + this.props.callback(); this.cancel(); diff --git a/src/config.ts b/src/config.ts index 70263d5a..c6d285f2 100644 --- a/src/config.ts +++ b/src/config.ts @@ -123,7 +123,7 @@ var Config: SBObject = { hideUploadButtonPlayerControls: false, hideDiscordLaunches: 0, hideDiscordLink: false, - invidiousInstances: ["invidio.us", "invidiou.sh", "invidious.snopyta.org"], + invidiousInstances: ["invidio.us", "invidious.snopyta.org"], autoUpvote: true, supportInvidious: false, serverAddress: CompileConfig.serverAddress, @@ -254,7 +254,7 @@ async function migrateOldFormats() { // Channel URLS if (Config.config.whitelistedChannels.length > 0 && - (Config.config.whitelistedChannels[0].includes("/") || Config.config.whitelistedChannels[0] == null)) { + (Config.config.whitelistedChannels[0] == null || Config.config.whitelistedChannels[0].includes("/"))) { let newChannelList: string[] = []; for (const item of Config.config.whitelistedChannels) { if (item != null) { diff --git a/src/content.ts b/src/content.ts index 758a5bb4..42f8dc42 100644 --- a/src/content.ts +++ b/src/content.ts @@ -114,7 +114,9 @@ var skipNoticeContentContainer: ContentContainer = () => ({ sponsorSubmissionNotice: submissionNotice, resetSponsorSubmissionNotice, changeStartSponsorButton, - previewTime + previewTime, + videoInfo, + getRoughCurrentTime }); //get messages from the background script and the popup @@ -178,7 +180,7 @@ function messageListener(request: any, sender: any, sendResponse: (response: any return case "getCurrentTime": sendResponse({ - currentTime: video.currentTime + currentTime: getRoughCurrentTime() }); break; @@ -461,6 +463,7 @@ function cancelSponsorSchedule(): void { */ function startSponsorSchedule(includeIntersectingSegments: boolean = false, currentTime?: number): void { cancelSponsorSchedule(); + if (video.paused) return; if (Config.config.disableSkipping || channelWhitelisted || (channelID === null && Config.config.forceChannelCheck)){ @@ -478,6 +481,7 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr let currentSkip = skipInfo.array[skipInfo.index]; let skipTime: number[] = [currentSkip.segment[0], skipInfo.array[skipInfo.endIndex].segment[1]]; let timeUntilSponsor = skipTime[0] - currentTime; + let videoID = sponsorVideoID; // Don't skip if this category should not be skipped if (utils.getCategorySelection(currentSkip.category).option === CategorySkipOption.ShowOverlay) return; @@ -486,7 +490,7 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr let forcedSkipTime: number = null; let forcedIncludeIntersectingSegments = false; - if (incorrectVideoIDCheck()) return; + if (incorrectVideoIDCheck(videoID)) return; if (video.currentTime >= skipTime[0] && video.currentTime < skipTime[1]) { skipToTime(video, skipInfo.endIndex, skipInfo.array, skipInfo.openNotice); @@ -515,9 +519,9 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr * * TODO: Remove this bug catching if statement when the bug is found */ -function incorrectVideoIDCheck(): boolean { +function incorrectVideoIDCheck(videoID?: string): boolean { let currentVideoID = getYouTubeVideoID(document.URL); - if (currentVideoID !== sponsorVideoID) { + if (currentVideoID !== (videoID || sponsorVideoID)) { // Something has really gone wrong console.error("[SponsorBlock] The videoID recorded when trying to skip is different than what it should be."); console.error("[SponsorBlock] VideoID recorded: " + sponsorVideoID + ". Actual VideoID: " + currentVideoID); @@ -967,7 +971,7 @@ function skipToTime(v: HTMLVideoElement, index: number, sponsorTimes: SponsorTim //auto-upvote this sponsor if (Config.config.trackViewCount && autoSkip && Config.config.autoUpvote) { - vote(1, currentUUID, null); + vote(1, currentUUID); } } @@ -1117,6 +1121,36 @@ async function updateVisibilityOfPlayerControlsButton(): Promise<boolean> { return createdButtons; } +/** + * Used for submitting. This will use the HTML displayed number when required as the video's + * current time is out of date while scrubbing or at the end of the video. This is not needed + * for sponsor skipping as the video is not playing during these times. + */ +function getRoughCurrentTime(): number { + let htmlCurrentTimeString = document.querySelector(".ytp-time-current").textContent; + let htmlDurationString = document.querySelector(".ytp-time-duration").textContent; + + // Used to check if endscreen content is visible + let endScreenContent = document.querySelector(".ytp-endscreen-content"); + // Used to check autoplay display + let autoPlayDisplay: HTMLDivElement = document.querySelector(".ytp-upnext"); + + if (htmlCurrentTimeString == htmlDurationString + || endScreenContent.childElementCount > 0 || autoPlayDisplay.style.display !== "none") { + // At the end of the video + return video.duration; + } + + let htmlCurrentTimeSections = htmlCurrentTimeString.split(":")[0]; + let htmlCurrentTime: number = parseInt(htmlCurrentTimeSections[0]) * 60 + parseInt(htmlCurrentTimeSections[1]); + + if (Math.abs(video.currentTime - htmlCurrentTime) > 3) { + return htmlCurrentTime; + } else { + return video.currentTime; + } +} + function startSponsorClicked() { //it can't update to this info yet closeInfoMenu(); @@ -1126,11 +1160,11 @@ function startSponsorClicked() { //add to sponsorTimes if (sponsorTimesSubmitting.length > 0 && sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment.length < 2) { //it is an end time - sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = video.currentTime; + sponsorTimesSubmitting[sponsorTimesSubmitting.length - 1].segment[1] = getRoughCurrentTime(); } else { //it is a start time sponsorTimesSubmitting.push({ - segment: [video.currentTime], + segment: [getRoughCurrentTime()], UUID: null, // Default to sponsor category: "sponsor" @@ -1306,7 +1340,7 @@ function clearSponsorTimes() { } //if skipNotice is null, it will not affect the UI -function vote(type, UUID, skipNotice?: SkipNoticeComponent) { +function vote(type: number, UUID: string, category?: string, skipNotice?: SkipNoticeComponent) { if (skipNotice !== null && skipNotice !== undefined) { //add loading info skipNotice.addVoteButtonInfo.bind(skipNotice)("Loading...") @@ -1319,7 +1353,7 @@ function vote(type, UUID, skipNotice?: SkipNoticeComponent) { if (sponsorIndex == -1 || sponsorTimes[sponsorIndex].UUID === null) return; // See if the local time saved count and skip count should be saved - if (type == 0 && sponsorSkipped[sponsorIndex] || type == 1 && !sponsorSkipped[sponsorIndex]) { + if (type === 0 && sponsorSkipped[sponsorIndex] || type === 1 && !sponsorSkipped[sponsorIndex]) { let factor = 1; if (type == 0) { factor = -1; @@ -1336,15 +1370,16 @@ function vote(type, UUID, skipNotice?: SkipNoticeComponent) { chrome.runtime.sendMessage({ message: "submitVote", type: type, - UUID: UUID + UUID: UUID, + category: category }, function(response) { if (response != undefined) { //see if it was a success or failure if (skipNotice != null) { if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { //success (treat rate limits as a success) - if (type == 0) { - skipNotice.afterDownvote.bind(skipNotice)(); + if (type === 0 || category) { + skipNotice.afterDownvote.bind(skipNotice)(type, category); } } else if (response.successType == 0) { //failure: duplicate vote diff --git a/src/js-components/previewBar.ts b/src/js-components/previewBar.ts index 1faabbda..6793e42d 100644 --- a/src/js-components/previewBar.ts +++ b/src/js-components/previewBar.ts @@ -50,11 +50,11 @@ let barTypes = { color: "#bfbf35", opacity: "0.7" }, - "offtopic": { + "music_offtopic": { color: "#ff9900", opacity: "0.7" }, - "preview-offtopic": { + "preview-music_offtopic": { color: "#a6634a", opacity: "0.7" } @@ -65,6 +65,9 @@ class PreviewBar { parent: any; onMobileYouTube: boolean; + timestamps: number[][]; + types: string; + constructor(parent, onMobileYouTube) { this.container = document.createElement('ul'); this.container.id = 'previewbar'; @@ -73,6 +76,82 @@ class PreviewBar { this.onMobileYouTube = onMobileYouTube; this.updatePosition(parent); + + this.setupHoverText(); + } + + setupHoverText() { + let seekBar = document.querySelector(".ytp-progress-bar-container"); + + // Create label placeholder + let tooltipTextWrapper = document.querySelector(".ytp-tooltip-text-wrapper"); + let titleTooltip = document.querySelector(".ytp-tooltip-title"); + let categoryTooltip = document.createElement("div"); + categoryTooltip.className = "sbHidden ytp-tooltip-title"; + categoryTooltip.id = "sponsor-block-category-tooltip" + + tooltipTextWrapper.insertBefore(categoryTooltip, titleTooltip.nextSibling); + + let mouseOnSeekBar = false; + + seekBar.addEventListener("mouseenter", (event) => { + mouseOnSeekBar = true; + }); + + seekBar.addEventListener("mouseleave", (event) => { + mouseOnSeekBar = false; + categoryTooltip.classList.add("sbHidden"); + }); + + const observer = new MutationObserver(() => { + if (!mouseOnSeekBar) return; + + let tooltips = document.querySelectorAll(".ytp-tooltip-text"); + for (const tooltip of tooltips) { + let splitData = tooltip.textContent.split(":"); + if (splitData.length === 2 && !isNaN(parseInt(splitData[0])) && !isNaN(parseInt(splitData[1]))) { + // Add label + let timeInSeconds = parseInt(splitData[0]) * 60 + parseInt(splitData[1]); + + // Find category at that location + let category = null; + for (let i = 0; i < this.timestamps.length; i++) { + if (this.timestamps[i][0] < timeInSeconds && this.timestamps[i][1] > timeInSeconds){ + category = this.types[i]; + } + } + + if (category === null && !categoryTooltip.classList.contains("sbHidden")) { + categoryTooltip.classList.add("sbHidden"); + tooltipTextWrapper.classList.remove("sbTooltipTwoTitleThumbnailOffset"); + tooltipTextWrapper.classList.remove("sbTooltipOneTitleThumbnailOffset"); + } else if (category !== null) { + categoryTooltip.classList.remove("sbHidden"); + categoryTooltip.textContent = chrome.i18n.getMessage("category_" + category) + || (chrome.i18n.getMessage("preview") + " " + chrome.i18n.getMessage("category_" + category.split("preview-")[1])); + + // There is a title now + tooltip.classList.remove("ytp-tooltip-text-no-title"); + + // Add the correct offset for the number of titles there are + if (titleTooltip.textContent !== "") { + if (!tooltipTextWrapper.classList.contains("sbTooltipTwoTitleThumbnailOffset")) { + tooltipTextWrapper.classList.add("sbTooltipTwoTitleThumbnailOffset"); + } + } else if (!tooltipTextWrapper.classList.contains("sbTooltipOneTitleThumbnailOffset")) { + tooltipTextWrapper.classList.add("sbTooltipOneTitleThumbnailOffset"); + } + } + + break; + } + } + }); + + observer.observe(tooltipTextWrapper, { + childList: true, + subtree: true + }); } updatePosition(parent) { @@ -109,6 +188,9 @@ class PreviewBar { return; } + this.timestamps = timestamps; + this.types = types; + // to avoid rounding error resulting in width more than 100% duration = Math.floor(duration * 100) / 100; let width; diff --git a/src/types.ts b/src/types.ts index befa1fc0..fab2f1db 100644 --- a/src/types.ts +++ b/src/types.ts @@ -3,7 +3,7 @@ import SkipNoticeComponent from "./components/SkipNoticeComponent"; interface ContentContainer { (): { - vote: (type: any, UUID: any, skipNotice?: SkipNoticeComponent) => void, + vote: (type: any, UUID: any, category?: string, skipNotice?: SkipNoticeComponent) => void, dontShowNoticeAgain: () => void, unskipSponsorTime: (UUID: any) => void, sponsorTimes: SponsorTime[], @@ -16,7 +16,9 @@ interface ContentContainer { sponsorSubmissionNotice: SubmissionNotice, resetSponsorSubmissionNotice: () => void, changeStartSponsorButton: (showStartSponsor: any, uploadButtonVisible: any) => Promise<boolean>, - previewTime: (time: number) => void + previewTime: (time: number) => void, + videoInfo: any, + getRoughCurrentTime: () => number } } |