diff options
-rw-r--r-- | .github/workflows/release.yml | 50 | ||||
-rw-r--r-- | manifest/manifest.json | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | public/_locales/en/messages.json | 16 | ||||
-rw-r--r-- | public/icons/thumbs_down.svg | 58 | ||||
-rw-r--r-- | public/icons/thumbs_up.svg | 59 | ||||
-rw-r--r-- | public/options/options.html | 17 | ||||
-rw-r--r-- | src/components/SkipNoticeComponent.tsx | 274 | ||||
-rw-r--r-- | src/config.ts | 7 | ||||
-rw-r--r-- | src/content.ts | 83 | ||||
-rw-r--r-- | src/render/SkipNotice.tsx | 15 | ||||
-rw-r--r-- | src/types.ts | 4 |
12 files changed, 392 insertions, 197 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50ec9d43..5a5abfdf 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -49,6 +49,30 @@ jobs: - name: Zip Artifacts run: cd ./dist ; zip -r ../builds/ChromeExtensionBeta.zip * + # Upload each release asset + - name: Upload ChromeExtension to release + uses: Shopify/upload-to-release@master + with: + args: builds/ChromeExtension.zip + name: ChromeExtension.zip + path: ./builds/ChromeExtension.zip + repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: Upload ChromeExtensionBeta 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 FirefoxExtension 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 }} + + # Firefox Beta - name: Create Firefox Beta artifacts run: npm run build:firefox -- --env.stream=beta - uses: actions/upload-artifact@v1 @@ -66,36 +90,14 @@ jobs: WEB_EXT_API_SECRET: ${{ secrets.WEB_EXT_API_SECRET }} - name: Install rename run: sudo apt-get install rename - - name: Install signed file + - name: Rename signed file run: cd ./web-ext-artifacts ; rename 's/.*/FirefoxSignedInstaller.xpi/' * - uses: actions/upload-artifact@v1 with: name: FirefoxExtensionSigned.xpi path: ./web-ext-artifacts/FirefoxSignedInstaller.xpi - # Upload each release asset - - name: Upload to release - uses: Shopify/upload-to-release@master - with: - args: builds/ChromeExtension.zip - name: ChromeExtension.zip - path: ./builds/ChromeExtension.zip - repo-token: ${{ secrets.GITHUB_TOKEN }} - - 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 + - name: Upload FirefoxSignedInstaller.xpi to release uses: Shopify/upload-to-release@master with: args: web-ext-artifacts/FirefoxSignedInstaller.xpi diff --git a/manifest/manifest.json b/manifest/manifest.json index a64cbb13..f704bc77 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,7 +1,7 @@ { "name": "__MSG_fullName__", "short_name": "__MSG_Name__", - "version": "1.2.28.4", + "version": "1.2.29", "default_locale": "en", "description": "__MSG_Description__", "content_scripts": [{ @@ -30,6 +30,8 @@ "icons/PlayerUploadFailedIconSponsorBlocker256px.png", "icons/upvote.png", "icons/downvote.png", + "icons/thumbs_down.svg", + "icons/thumbs_up.svg", "icons/report.png", "icons/close.png", "icons/beep.ogg", diff --git a/package.json b/package.json index 88833083..6450ade6 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "scripts": { "web-run": "npm run web-run:chrome", - "web-sign": "web-ext sign -s dist", + "web-sign": "web-ext sign -s dist --id [email protected]", "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 b9859414..c8fa5bb9 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -35,11 +35,14 @@ "Segments": { "message": "sponsor segments" }, + "upvoteButtonInfo": { + "message": "Upvote this submission" + }, "reportButtonTitle": { "message": "Report" }, "reportButtonInfo": { - "message": "Report this sponsor submission as incorrect." + "message": "Report this submission as incorrect." }, "Dismiss": { "message": "Dismiss" @@ -105,7 +108,7 @@ "message": "You have already voted this way before." }, "serverDown": { - "message": "It seems the sever is down. Contact the dev immediately." + "message": "It seems the server is down. Contact the dev immediately." }, "connectionError": { "message": "A connection error has occured. Error code: " @@ -377,12 +380,6 @@ "currentInstances": { "message": "Current Instances:" }, - "enableAutoUpvote": { - "message": "Auto Upvote" - }, - "whatAutoUpvote": { - "message": "With this enabled, the extension will upvote all submissions you view if you do not report them. If the notice is disabled, this will not occur." - }, "minDuration": { "message": "Minimum duration (seconds):" }, @@ -566,5 +563,8 @@ }, "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." + }, + "multipleSegments": { + "message": "Multiple Segments" } } diff --git a/public/icons/thumbs_down.svg b/public/icons/thumbs_down.svg new file mode 100644 index 00000000..09b88432 --- /dev/null +++ b/public/icons/thumbs_down.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="24" + viewBox="0 0 24 24" + width="24" + version="1.1" + id="svg6" + sodipodi:docname="thumbs_down.svg" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="730" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="9.8333333" + inkscape:cx="12" + inkscape:cy="12" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + d="M0 0h24v24H0z" + fill="none" + id="path2" /> + <path + d="M15 3H6c-.83 0-1.54.5-1.84 1.22l-3.02 7.05c-.09.23-.14.47-.14.73v2c0 1.1.9 2 2 2h6.31l-.95 4.57-.03.32c0 .41.17.79.44 1.06L9.83 23l6.59-6.59c.36-.36.58-.86.58-1.41V5c0-1.1-.9-2-2-2zm4 0v12h4V3h-4z" + id="path4" + style="fill:#ffffff" /> +</svg> diff --git a/public/icons/thumbs_up.svg b/public/icons/thumbs_up.svg new file mode 100644 index 00000000..85e1b743 --- /dev/null +++ b/public/icons/thumbs_up.svg @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + viewBox="0 0 24 24" + fill="black" + width="18px" + height="18px" + version="1.1" + id="svg6" + sodipodi:docname="thumbs_up.svg" + inkscape:version="0.92.4 (5da689c313, 2019-01-14)"> + <metadata + id="metadata12"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs10" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="730" + inkscape:window-height="480" + id="namedview8" + showgrid="false" + inkscape:zoom="13.111111" + inkscape:cx="9" + inkscape:cy="9" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="0" + inkscape:current-layer="svg6" /> + <path + d="M0 0h24v24H0V0z" + fill="none" + id="path2" /> + <path + d="M1 21h4V9H1v12zm22-11c0-1.1-.9-2-2-2h-6.31l.95-4.57.03-.32c0-.41-.17-.79-.44-1.06L14.17 1 7.59 7.59C7.22 7.95 7 8.45 7 9v10c0 1.1.9 2 2 2h9c.83 0 1.54-.5 1.84-1.22l3.02-7.05c.09-.23.14-.47.14-.73v-2z" + id="path4" + style="fill:#ffffff" /> +</svg> diff --git a/public/options/options.html b/public/options/options.html index 0a93ad22..3cd69b01 100644 --- a/public/options/options.html +++ b/public/options/options.html @@ -237,23 +237,6 @@ <br/> <br/> - <div option-type="toggle" sync-option="autoUpvote"> - <label class="switch-container" label-name="__MSG_enableAutoUpvote__"> - <label class="switch"> - <input type="checkbox" checked> - <span class="slider round"></span> - </label> - </label> - - <br/> - <br/> - - <div class="small-description">__MSG_whatAutoUpvote__</div> - </div> - - <br/> - <br/> - <div option-type="toggle" sync-option="trackViewCount"> <label class="switch-container" label-name="__MSG_enableViewTracking__"> <label class="switch"> diff --git a/src/components/SkipNoticeComponent.tsx b/src/components/SkipNoticeComponent.tsx index 602b0ffc..7e6c4751 100644 --- a/src/components/SkipNoticeComponent.tsx +++ b/src/components/SkipNoticeComponent.tsx @@ -1,7 +1,7 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config" -import { ContentContainer, SponsorHideType } from "../types"; +import { ContentContainer, SponsorHideType, SponsorTime } from "../types"; import Utils from "../utils"; var utils = new Utils(); @@ -9,9 +9,17 @@ var utils = new Utils(); import NoticeComponent from "./NoticeComponent"; import NoticeTextSelectionComponent from "./NoticeTextSectionComponent"; +enum SkipNoticeAction { + None, + Upvote, + Downvote, + CategoryVote, + Unskip +} + +export interface SkipNoticeProps { + segments: SponsorTime[]; -export interface SkipNoticeProps { - UUID: string; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; @@ -29,14 +37,17 @@ export interface SkipNoticeState { countdownText: string; unskipText: string; - unskipCallback: () => void; + unskipCallback: (index: number) => void; downvoting: boolean; choosingCategory: boolean; + thanksForVotingText: boolean; //null until the voting buttons should be hidden + + actionState: SkipNoticeAction; } class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeState> { - UUID: string; + segments: SponsorTime[]; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: ContentContainer; @@ -57,22 +68,30 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta this.noticeRef = React.createRef(); this.categoryOptionRef = React.createRef(); - this.UUID = props.UUID; + this.segments = props.segments; this.autoSkip = props.autoSkip; this.contentContainer = props.contentContainer; this.audio = null; - - let noticeTitle = chrome.i18n.getMessage("category_" + this.getSponsorTime().category) + " " + chrome.i18n.getMessage("skipped"); - + + let categoryName = chrome.i18n.getMessage(this.segments.length > 1 ? "multipleSegments" : "category_" + this.segments[0].category); + let noticeTitle = categoryName + " " + chrome.i18n.getMessage("skipped"); if (!this.autoSkip) { - noticeTitle = chrome.i18n.getMessage("skip") + " " + chrome.i18n.getMessage("category_" + this.getSponsorTime().category) + "?"; + noticeTitle = chrome.i18n.getMessage("skip") + " " + categoryName + "?"; } //add notice this.amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length; + + // Sort segments + 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 - this.idSuffix = this.UUID + this.amountOfPreviousNotices; + for (const segment of this.segments) { + this.idSuffix += segment.UUID; + } + this.idSuffix += this.amountOfPreviousNotices; if (this.amountOfPreviousNotices > 0) { //another notice exists @@ -92,22 +111,21 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta countdownText: null, unskipText: chrome.i18n.getMessage("unskip"), - unskipCallback: this.unskip.bind(this), + unskipCallback: (index) => this.unskip(index), downvoting: false, - choosingCategory: false + choosingCategory: false, + thanksForVotingText: null, + + actionState: SkipNoticeAction.None } if (!this.autoSkip) { - Object.assign(this.state, this.getUnskippedModeInfo(chrome.i18n.getMessage("skip"))); + // Assume manual skip is only skipping 1 submission + Object.assign(this.state, this.getUnskippedModeInfo(0, chrome.i18n.getMessage("skip"))); } } - // Helper method - getSponsorTime() { - return utils.getSponsorTimeFromUUID(this.contentContainer().sponsorTimes, this.UUID); - } - componentDidMount() { if (Config.config.audioNotificationOnSkip && this.audio) { this.audio.volume = this.contentContainer().v.volume * 0.1; @@ -145,35 +163,46 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta <tr id={"sponsorSkipNoticeSecondRow" + this.idSuffix}> {/* Vote Button Container */} - <td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix} - className="sponsorTimesVoteButtonsContainer"> - - {/* Report Text */} - <span id={"sponsorTimesReportText" + this.idSuffix} - className="sponsorTimesInfoMessage sponsorTimesVoteButtonMessage" - title={chrome.i18n.getMessage("reportButtonInfo")} - style={{marginRight: "5px"}}> - - {chrome.i18n.getMessage("reportButtonTitle")} - </span> - - {/* Report Button */} - <img id={"sponsorTimesDownvoteButtonsContainer" + this.idSuffix} - className="sponsorSkipObject voteButton" - src={chrome.extension.getURL("icons/report.png")} - title={chrome.i18n.getMessage("reportButtonInfo")} - onClick={() => this.adjustDownvotingState(true)}> - - </img> + {!this.state.thanksForVotingText ? + <td id={"sponsorTimesVoteButtonsContainer" + this.idSuffix} + className="sponsorTimesVoteButtonsContainer"> + + {/* Upvote Button */} + <img id={"sponsorTimesDownvoteButtonsContainer" + this.idSuffix} + className="sponsorSkipObject voteButton" + style={{marginRight: "10px"}} + src={chrome.extension.getURL("icons/thumbs_up.svg")} + title={chrome.i18n.getMessage("upvoteButtonInfo")} + onClick={() => this.prepAction(SkipNoticeAction.Upvote)}> + + </img> + + {/* Report Button */} + <img id={"sponsorTimesDownvoteButtonsContainer" + this.idSuffix} + className="sponsorSkipObject voteButton" + src={chrome.extension.getURL("icons/thumbs_down.svg")} + title={chrome.i18n.getMessage("reportButtonInfo")} + onClick={() => this.adjustDownvotingState(true)}> + + </img> - </td> + </td> + + : + + <td id={"sponsorTimesVoteButtonInfoMessage" + this.idSuffix} + className="sponsorTimesInfoMessage sponsorTimesVoteButtonMessage" + style={{marginRight: "10px"}}> + {this.state.thanksForVotingText} + </td> + } {/* Unskip Button */} <td className="sponsorSkipNoticeUnskipSection"> <button id={"sponsorSkipUnskipButton" + this.idSuffix} className="sponsorSkipObject sponsorSkipNoticeButton" style={{marginLeft: "4px"}} - onClick={this.state.unskipCallback}> + onClick={() => this.prepAction(SkipNoticeAction.Unskip)}> {this.state.unskipText} </button> @@ -198,7 +227,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta {/* Normal downvote */} <button className="sponsorSkipObject sponsorSkipNoticeButton" - onClick={() => this.contentContainer().vote(0, this.UUID, undefined, this)}> + onClick={() => this.prepAction(SkipNoticeAction.Downvote)}> {chrome.i18n.getMessage("downvoteDescription")} </button> @@ -220,7 +249,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta {/* Category Selector */} <select id={"sponsorTimeCategories" + this.idSuffix} className="sponsorTimeCategories" - defaultValue={this.getSponsorTime().category} + defaultValue={this.segments[0].category} //Just default to the first segment, as we don't know which they'll choose ref={this.categoryOptionRef} onChange={this.categorySelectionChange.bind(this)}> @@ -228,11 +257,23 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta </select> {/* Submit Button */} - <button className="sponsorSkipObject sponsorSkipNoticeButton" - onClick={() => this.contentContainer().vote(undefined, this.UUID, this.categoryOptionRef.current.value, this)}> + {this.segments.length === 1 && + <button className="sponsorSkipObject sponsorSkipNoticeButton" + onClick={() => this.prepAction(SkipNoticeAction.CategoryVote)}> + + {chrome.i18n.getMessage("submit")} + </button> + } + + </td> + </tr> + } - {chrome.i18n.getMessage("submit")} - </button> + {/* Segment Chooser Row */} + {this.state.actionState !== SkipNoticeAction.None && + <tr id={"sponsorSkipNoticeSubmissionOptionsRow" + this.idSuffix}> + <td id={"sponsorTimesSubmissionOptionsContainer" + this.idSuffix}> + {this.getSubmissionChooser()} </td> </tr> } @@ -241,6 +282,32 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta ); } + getSubmissionChooser(): JSX.Element[] { + let elements: JSX.Element[] = []; + + for (let i = 0; i < this.segments.length; i++) { + elements.push( + <button className="sponsorSkipObject sponsorSkipNoticeButton" + onClick={() => this.performAction(i)} + key={"submission" + i + this.segments[i].category + this.idSuffix}> + {(i + 1) + ". " + chrome.i18n.getMessage("category_" + this.segments[i].category)} + </button> + ); + } + + return elements; + } + + prepAction(action: SkipNoticeAction) { + if (this.segments.length === 1) { + this.performAction(0, action); + } else { + this.setState({ + actionState: action + }); + } + } + getMessageBoxes(): JSX.Element[] | JSX.Element { if (this.state.messages.length === 0) { // Add a spacer if there is no text @@ -265,6 +332,34 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta return elements; } + /** + * Performs the action from the current state + * + * @param index + */ + performAction(index: number, action?: SkipNoticeAction) { + switch (action ?? this.state.actionState) { + case SkipNoticeAction.None: + break; + case SkipNoticeAction.Upvote: + this.contentContainer().vote(1, this.segments[index].UUID, undefined, this); + break; + case SkipNoticeAction.Downvote: + this.contentContainer().vote(0, this.segments[index].UUID, undefined, this); + break; + case SkipNoticeAction.CategoryVote: + this.contentContainer().vote(undefined, this.segments[index].UUID, this.categoryOptionRef.current.value, this) + break; + case SkipNoticeAction.Unskip: + this.state.unskipCallback(index); + break; + } + + this.setState({ + actionState: SkipNoticeAction.None + }); + } + adjustDownvotingState(value: boolean) { if (!value) this.clearConfigListener(); @@ -289,6 +384,11 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta this.setState({ choosingCategory: true, downvoting: false + }, () => { + if (this.segments.length > 1) { + // Use the action selectors as a submit button + this.prepAction(SkipNoticeAction.CategoryVote); + } }); } @@ -324,37 +424,38 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta chrome.runtime.sendMessage({"message": "openConfig"}); // Reset option to original - event.target.value = this.getSponsorTime().category; + event.target.value = this.segments[0].category; return; } } - unskip() { - this.contentContainer().unskipSponsorTime(this.UUID); + unskip(index: number) { + this.contentContainer().unskipSponsorTime(this.segments[index]); - this.unskippedMode(chrome.i18n.getMessage("reskip")); + this.unskippedMode(index, chrome.i18n.getMessage("reskip")); } /** Sets up notice to be not skipped yet */ - unskippedMode(buttonText: string) { + unskippedMode(index: number, buttonText: string) { //setup new callback and reset countdown - this.setState(this.getUnskippedModeInfo(buttonText), () => { + this.setState(this.getUnskippedModeInfo(index, buttonText), () => { this.noticeRef.current.resetCountdown(); }); } - getUnskippedModeInfo(buttonText: string) { + getUnskippedModeInfo(index: number, buttonText: string) { + let self = this; let maxCountdownTime = function() { - let sponsorTime = this.getSponsorTime(); - let duration = Math.round((sponsorTime.segment[1] - this.contentContainer().v.currentTime) * (1 / this.contentContainer().v.playbackRate)); + let sponsorTime = self.segments[index]; + let duration = Math.round((sponsorTime.segment[1] - self.contentContainer().v.currentTime) * (1 / self.contentContainer().v.playbackRate)); return Math.max(duration, 4); - }.bind(this); + }; return { unskipText: buttonText, - unskipCallback: this.reskip.bind(this), + unskipCallback: (index) => this.reskip(index), //change max duration to however much of the sponsor is left maxCountdownTime: maxCountdownTime, @@ -363,8 +464,8 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta } } - reskip() { - this.contentContainer().reskipSponsorTime(this.UUID); + reskip(index: number) { + this.contentContainer().reskipSponsorTime(this.segments[index]); //reset countdown this.setState({ @@ -380,24 +481,23 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta this.setState({ noticeTitle: chrome.i18n.getMessage("noticeTitle") }); - - if(Config.config.autoUpvote) this.contentContainer().vote(1, this.UUID); } } - afterDownvote(type: number, category: string) { + afterVote(segment: SponsorTime, type: number, category: string) { this.addVoteButtonInfo(chrome.i18n.getMessage("voted")); - this.setNoticeInfoMessage(chrome.i18n.getMessage("hitGoBack")); - this.adjustDownvotingState(false); + if (type === 0) { + this.setNoticeInfoMessage(chrome.i18n.getMessage("hitGoBack")); + this.adjustDownvotingState(false); + } // Change the sponsor locally - let sponsorTime = this.getSponsorTime(); - if (sponsorTime) { + if (segment) { if (type === 0) { - sponsorTime.hidden = SponsorHideType.Downvoted; + segment.hidden = SponsorHideType.Downvoted; } else if (category) { - sponsorTime.category = category; + segment.category = category; } this.contentContainer().updatePreviewBar(); @@ -407,41 +507,19 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta setNoticeInfoMessage(...messages: string[]) { this.setState({ messages - }) + }); } addVoteButtonInfo(message) { - this.resetVoteButtonInfo(); - - //hide report button and text for it - let downvoteButton = document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix); - if (downvoteButton != null) { - downvoteButton.style.display = "none"; - } - let downvoteButtonText = document.getElementById("sponsorTimesReportText" + this.idSuffix); - if (downvoteButtonText != null) { - downvoteButtonText.style.display = "none"; - } - - //add info - let thanksForVotingText = document.createElement("td"); - thanksForVotingText.id = "sponsorTimesVoteButtonInfoMessage" + this.idSuffix; - thanksForVotingText.className = "sponsorTimesInfoMessage sponsorTimesVoteButtonMessage"; - thanksForVotingText.innerText = message; - - //add element to div - document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).prepend(thanksForVotingText); + this.setState({ + thanksForVotingText: message + }); } resetVoteButtonInfo() { - let previousInfoMessage = document.getElementById("sponsorTimesVoteButtonInfoMessage" + this.idSuffix); - if (previousInfoMessage != null) { - //remove it - document.getElementById("sponsorSkipNoticeSecondRow" + this.idSuffix).removeChild(previousInfoMessage); - } - - //show button again - document.getElementById("sponsorTimesDownvoteButtonsContainer" + this.idSuffix).style.removeProperty("display"); + this.setState({ + thanksForVotingText: null + }); } closeListener() { diff --git a/src/config.ts b/src/config.ts index 7b8fe15b..d26859ec 100644 --- a/src/config.ts +++ b/src/config.ts @@ -24,7 +24,6 @@ interface SBConfig { hideDiscordLaunches: number, hideDiscordLink: boolean, invidiousInstances: string[], - autoUpvote: boolean, supportInvidious: boolean, serverAddress: string, minDuration: number, @@ -124,7 +123,6 @@ var Config: SBObject = { hideDiscordLaunches: 0, hideDiscordLink: false, invidiousInstances: ["invidio.us", "invidious.snopyta.org"], - autoUpvote: true, supportInvidious: false, serverAddress: CompileConfig.serverAddress, minDuration: 0, @@ -252,6 +250,11 @@ async function migrateOldFormats() { } } + // Auto vote removal + if (Config.config["autoUpvote"]) { + chrome.storage.sync.remove("autoUpvote"); + } + // Channel URLS if (Config.config.whitelistedChannels.length > 0 && (Config.config.whitelistedChannels[0] == null || Config.config.whitelistedChannels[0].includes("/"))) { diff --git a/src/content.ts b/src/content.ts index 50ec0ec8..bf47077b 100644 --- a/src/content.ts +++ b/src/content.ts @@ -78,12 +78,6 @@ utils.wait(() => Config.config !== null, 1000, 1).then(() => videoIDChange(getYo //this only happens if there is an error var sponsorLookupRetries = 0; -//the last time in the video a sponsor was skipped -//used for the go back button -var lastSponsorTimeSkipped: number = null; -//used for ratings -var lastSponsorTimeSkippedUUID: string = null; - //if showing the start sponsor button or the end sponsor button on the player var showingStartSponsor = true; @@ -496,6 +490,19 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr let timeUntilSponsor = skipTime[0] - currentTime; let videoID = sponsorVideoID; + // Find all indexes in between the start and end + let skippingSegments = [skipInfo.array[skipInfo.index]]; + if (skipInfo.index !== skipInfo.endIndex) { + skippingSegments = []; + + for (const segment of skipInfo.array) { + if (utils.getCategorySelection(segment.category).option === CategorySkipOption.AutoSkip && + segment.segment[0] >= skipTime[0] && segment.segment[1] <= skipTime[1]) { + skippingSegments.push(segment); + } + } + } + // Don't skip if this category should not be skipped if (utils.getCategorySelection(currentSkip.category).option === CategorySkipOption.ShowOverlay) return; @@ -506,7 +513,7 @@ function startSponsorSchedule(includeIntersectingSegments: boolean = false, curr if (incorrectVideoCheck(videoID, currentSkip)) return; if (video.currentTime >= skipTime[0] && video.currentTime < skipTime[1]) { - skipToTime(video, skipInfo.endIndex, skipInfo.array, skipInfo.openNotice); + skipToTime(video, skipTime, skippingSegments, skipInfo.openNotice); // TODO: Know the autoSkip settings for ALL items being skipped if (utils.getCategorySelection(currentSkip.category).option === CategorySkipOption.ManualSkip) { @@ -973,58 +980,58 @@ function previewTime(time: number) { } //skip from the start time to the end time for a certain index sponsor time -function skipToTime(v: HTMLVideoElement, index: number, sponsorTimes: SponsorTime[], openNotice: boolean) { - let autoSkip: boolean = utils.getCategorySelection(sponsorTimes[index].category).option === CategorySkipOption.AutoSkip; +function skipToTime(v: HTMLVideoElement, skipTime: number[], skippingSegments: SponsorTime[], openNotice: boolean) { + // There will only be one submission if it is manual skip + let autoSkip: boolean = utils.getCategorySelection(skippingSegments[0].category).option === CategorySkipOption.AutoSkip; if (autoSkip || previewResetter !== null) { - v.currentTime = sponsorTimes[index].segment[1]; + v.currentTime = skipTime[1]; } - lastSponsorTimeSkipped = sponsorTimes[index].segment[0]; - - let currentUUID: string = sponsorTimes[index].UUID; - lastSponsorTimeSkippedUUID = currentUUID; - if (openNotice) { //send out the message saying that a sponsor message was skipped if (!Config.config.dontShowNotice || !autoSkip) { - let skipNotice = new SkipNotice(currentUUID, autoSkip, skipNoticeContentContainer); - - //auto-upvote this sponsor - if (Config.config.trackViewCount && autoSkip && Config.config.autoUpvote) { - vote(1, currentUUID); - } + let skipNotice = new SkipNotice(skippingSegments, autoSkip, skipNoticeContentContainer); } //send telemetry that a this sponsor was skipped - if (Config.config.trackViewCount && !sponsorSkipped[index] && autoSkip) { - utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + currentUUID); + if (Config.config.trackViewCount && autoSkip) { + let alreadySkipped = false; + let isPreviewSegment = false; + + for (const segment of skippingSegments) { + let index = sponsorTimes.indexOf(segment); + if (index !== -1 && !sponsorSkipped[index]) { + utils.sendRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); + + sponsorSkipped[index] = true; + } else if (sponsorSkipped[index]) { + alreadySkipped = true; + } + if (index !== -1) isPreviewSegment = true; + } + // Count this as a skip - Config.config.minutesSaved = Config.config.minutesSaved + (sponsorTimes[index].segment[1] - sponsorTimes[index].segment[0]) / 60; - Config.config.skipCount = Config.config.skipCount + 1; - - sponsorSkipped[index] = true; + if (!alreadySkipped && !isPreviewSegment) { + Config.config.minutesSaved = Config.config.minutesSaved + (skipTime[1] - skipTime[0]) / 60; + Config.config.skipCount = Config.config.skipCount + 1; + } } } } -function unskipSponsorTime(UUID) { +function unskipSponsorTime(segment: SponsorTime) { if (sponsorTimes != null) { //add a tiny bit of time to make sure it is not skipped again - video.currentTime = utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[0] + 0.001; + video.currentTime = segment.segment[0] + 0.001; checkIfInsideSegment(); } } -function reskipSponsorTime(UUID) { - if (sponsorTimes != null) { - video.currentTime = utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[1]; - - // See if any skips need to be done if this is inside of another segment - startSponsorSchedule(true, utils.getSponsorTimeFromUUID(sponsorTimes, UUID).segment[1]); - } +function reskipSponsorTime(segment: SponsorTime) { + video.currentTime = segment.segment[1]; } /** @@ -1387,9 +1394,7 @@ function vote(type: number, UUID: string, category?: string, skipNotice?: SkipNo if (skipNotice != null) { if (response.successType == 1 || (response.successType == -1 && response.statusCode == 429)) { //success (treat rate limits as a success) - if (type === 0 || category) { - skipNotice.afterDownvote.bind(skipNotice)(type, category); - } + skipNotice.afterVote.bind(skipNotice)(utils.getSponsorTimeFromUUID(sponsorTimes, UUID), type, category); } else if (response.successType == 0) { //failure: duplicate vote skipNotice.setNoticeInfoMessage.bind(skipNotice)(chrome.i18n.getMessage("voteFail")) diff --git a/src/render/SkipNotice.tsx b/src/render/SkipNotice.tsx index 49265fb8..4b969098 100644 --- a/src/render/SkipNotice.tsx +++ b/src/render/SkipNotice.tsx @@ -2,17 +2,18 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import SkipNoticeComponent from "../components/SkipNoticeComponent"; +import { SponsorTime } from "../types"; class SkipNotice { - UUID: string; + segments: SponsorTime[]; autoSkip: boolean; // Contains functions and variables from the content script needed by the skip notice contentContainer: () => any; noticeElement: HTMLDivElement; - constructor(UUID: string, autoSkip: boolean = false, contentContainer) { - this.UUID = UUID; + constructor(segments: SponsorTime[], autoSkip: boolean = false, contentContainer) { + this.segments = segments; this.autoSkip = autoSkip; this.contentContainer = contentContainer; @@ -35,7 +36,11 @@ class SkipNotice { let amountOfPreviousNotices = document.getElementsByClassName("sponsorSkipNotice").length; //this is the suffix added at the end of every id - let idSuffix = this.UUID + amountOfPreviousNotices; + let idSuffix = ""; + for (const segment of this.segments) { + idSuffix += segment.UUID; + } + idSuffix += amountOfPreviousNotices; this.noticeElement = document.createElement("div"); this.noticeElement.id = "sponsorSkipNoticeContainer" + idSuffix; @@ -43,7 +48,7 @@ class SkipNotice { referenceNode.prepend(this.noticeElement); ReactDOM.render( - <SkipNoticeComponent UUID={UUID} + <SkipNoticeComponent segments={segments} autoSkip={autoSkip} contentContainer={contentContainer} closeListener={() => this.close()} />, diff --git a/src/types.ts b/src/types.ts index 0bf80bd9..002f4daf 100644 --- a/src/types.ts +++ b/src/types.ts @@ -5,12 +5,12 @@ interface ContentContainer { (): { vote: (type: any, UUID: any, category?: string, skipNotice?: SkipNoticeComponent) => void, dontShowNoticeAgain: () => void, - unskipSponsorTime: (UUID: any) => void, + unskipSponsorTime: (segment: SponsorTime) => void, sponsorTimes: SponsorTime[], sponsorTimesSubmitting: SponsorTime[], v: HTMLVideoElement, sponsorVideoID, - reskipSponsorTime: (UUID: any) => void, + reskipSponsorTime: (segment: SponsorTime) => void, updatePreviewBar: () => void, onMobileYouTube: boolean, sponsorSubmissionNotice: SubmissionNotice, |