diff options
m--------- | maze-utils | 0 | ||||
m--------- | public/_locales | 0 | ||||
-rw-r--r-- | public/content.css | 7 | ||||
-rw-r--r-- | src/components/SponsorTimeEditComponent.tsx | 6 | ||||
-rw-r--r-- | src/config.ts | 4 | ||||
-rw-r--r-- | src/content.ts | 57 | ||||
-rw-r--r-- | src/dearrowPromotion.ts | 71 | ||||
-rw-r--r-- | src/options.ts | 3 | ||||
-rw-r--r-- | src/popup.ts | 5 | ||||
-rw-r--r-- | src/utils.ts | 46 | ||||
-rw-r--r-- | src/utils/requests.ts | 47 | ||||
-rw-r--r-- | src/utils/videoLabels.ts | 3 | ||||
-rw-r--r-- | src/utils/warnings.ts | 9 |
13 files changed, 153 insertions, 105 deletions
diff --git a/maze-utils b/maze-utils -Subproject 92d368b051c1af360ab9db45215df274b71de8f +Subproject 6bdc9402c6cf2a8c97e53e1c360744e12afd919 diff --git a/public/_locales b/public/_locales -Subproject 6ff5f86e9aca64c2ad51aa13e39db1a040506f9 +Subproject 9ba877c006a9a78774e56713cc170f355d81a4a diff --git a/public/content.css b/public/content.css index 39051417..dc2b1ba1 100644 --- a/public/content.css +++ b/public/content.css @@ -742,6 +742,7 @@ input::-webkit-inner-spin-button { color: white; font-size: 12px; z-index: 10000; + font-weight: normal; } .sponsorBlockTooltip a { @@ -764,6 +765,12 @@ input::-webkit-inner-spin-button { right: 50%; } +.sponsorBlockTooltip.sbTriangle.sbTopTriangle::after { + bottom: 100%; + top: unset; + border-color: transparent transparent rgba(28, 28, 28, 0.7) transparent; +} + .sponsorBlockLockedColor { color: #ffc83d !important; } diff --git a/src/components/SponsorTimeEditComponent.tsx b/src/components/SponsorTimeEditComponent.tsx index 191f8ce7..1b470a78 100644 --- a/src/components/SponsorTimeEditComponent.tsx +++ b/src/components/SponsorTimeEditComponent.tsx @@ -2,14 +2,12 @@ import * as React from "react"; import * as CompileConfig from "../../config.json"; import Config from "../config"; import { ActionType, Category, ChannelIDStatus, ContentContainer, SponsorTime } from "../types"; -import Utils from "../utils"; import SubmissionNoticeComponent from "./SubmissionNoticeComponent"; import { RectangleTooltip } from "../render/RectangleTooltip"; import SelectorComponent, { SelectorOption } from "./SelectorComponent"; import { DEFAULT_CATEGORY } from "../utils/categoryUtils"; import { getFormattedTime, getFormattedTimeToSeconds } from "../../maze-utils/src/formating"; - -const utils = new Utils(); +import { asyncRequestToServer } from "../utils/requests"; export interface SponsorTimeEditProps { index: number; @@ -727,7 +725,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo if (this.props.contentContainer().channelIDInfo.status !== ChannelIDStatus.Found) return; this.fetchingSuggestions = true; - const result = await utils.asyncRequestToServer("GET", "/api/chapterNames", { + const result = await asyncRequestToServer("GET", "/api/chapterNames", { description, channelID: this.props.contentContainer().channelIDInfo.id }); diff --git a/src/config.ts b/src/config.ts index f13cd3ad..a753a36f 100644 --- a/src/config.ts +++ b/src/config.ts @@ -76,6 +76,7 @@ interface SBConfig { deArrowInstalled: boolean; showDeArrowPromotion: boolean; showDeArrowInSettings: boolean; + shownDeArrowPromotion: boolean; showZoomToFillError2: boolean; cleanPopup: boolean; @@ -318,8 +319,9 @@ const syncDefaults = { showSegmentFailedToFetchWarning: true, allowScrollingToEdit: true, deArrowInstalled: false, - showDeArrowPromotion: false, + showDeArrowPromotion: true, showDeArrowInSettings: true, + shownDeArrowPromotion: false, showZoomToFillError2: true, cleanPopup: false, diff --git a/src/content.ts b/src/content.ts index 9fbe4cb6..2804dc4c 100644 --- a/src/content.ts +++ b/src/content.ts @@ -28,7 +28,7 @@ import { getControls, getExistingChapters, getHashParams, isPlayingPlaylist, isV import { CategoryPill } from "./render/CategoryPill"; import { AnimationUtils } from "./utils/animationUtils"; import { GenericUtils } from "./utils/genericUtils"; -import { logDebug } from "./utils/logger"; +import { logDebug, logWarn } from "./utils/logger"; import { importTimes } from "./utils/exporter"; import { ChapterVote } from "./render/ChapterVote"; import { openWarningDialog } from "./utils/warnings"; @@ -36,17 +36,17 @@ import { isFirefoxOrSafari, waitFor } from "../maze-utils/src"; import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating"; import { getChannelIDInfo, getVideo, getIsAdPlaying, getIsLivePremiere, setIsAdPlaying, checkVideoIDChange, getVideoID, getYouTubeVideoID, setupVideoModule, checkIfNewVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video"; import { Keybind, StorageChangesObject, isSafari, keybindEquals } from "../maze-utils/src/config"; -import { findValidElement, waitForElement } from "../maze-utils/src/dom" +import { findValidElement } from "../maze-utils/src/dom" import { getHash, HashedValue } from "../maze-utils/src/hash"; import { generateUserID } from "../maze-utils/src/setup"; import { updateAll } from "../maze-utils/src/thumbnailManagement"; import { setupThumbnailListener } from "./utils/thumbnails"; import * as documentScript from "../dist/js/document.js"; -import { Tooltip } from "./render/Tooltip"; -import { isDeArrowInstalled } from "./utils/crossExtension"; import { runCompatibilityChecks } from "./utils/compatibility"; import { cleanPage } from "./utils/pageCleaner"; import { addCleanupListener } from "../maze-utils/src/cleanup"; +import { hideDeArrowPromotion, tryShowingDeArrowPromotion } from "./dearrowPromotion"; +import { asyncRequestToServer } from "./utils/requests"; cleanPage(); @@ -57,43 +57,6 @@ utils.wait(() => Config.isReady(), 5000, 10).then(() => { addCSS(); setCategoryColorCSSVariables(); - // DeArrow promotion - setTimeout(async () => { - if (document.URL === "https://www.youtube.com/" - && Config.config.showDeArrowPromotion - && Config.config.showUpsells - && Config.config.showNewFeaturePopups - && (Config.config.skipCount > 30 || !Config.config.trackViewCount) - && Math.random() < 0.05) { - - if (!await isDeArrowInstalled()) { - const element = await waitForElement("#contents") as HTMLElement; - if (element) { - Config.config.showDeArrowPromotion = false; - - new Tooltip({ - text: chrome.i18n.getMessage("DeArrowPromotionMessage2"), - linkOnClick: () => window.open("https://dearrow.ajay.app"), - referenceNode: element, - prependElement: element.firstElementChild as HTMLElement, - timeout: 15000, - positionRealtive: false, - containerAbsolute: true, - bottomOffset: "inherit", - topOffset: "-82px", - leftOffset: "0", - rightOffset: "0", - displayTriangle: false, - center: true, - opacity: 1 - }); - } - } else { - Config.config.showDeArrowPromotion = false; - } - } - }, 5000); - runCompatibilityChecks(); }); @@ -440,6 +403,8 @@ function resetValues() { for (let i = 0; i < skipNotices.length; i++) { skipNotices.pop()?.close(); } + + hideDeArrowPromotion(); } function videoIDChange(): void { @@ -480,6 +445,8 @@ function videoIDChange(): void { // Clear unsubmitted segments from the previous video sponsorTimesSubmitting = []; updateSponsorTimesSubmitting(); + + tryShowingDeArrowPromotion().catch(logWarn); } function handleMobileControlsMutations(): void { @@ -1112,7 +1079,7 @@ async function sponsorsLookup(keepOldSubmissions = true) { if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment; const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4) as VideoID & HashedValue; - const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { + const response = await asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { categories, actionTypes: getEnabledActionTypes(), userAgent: `${chrome.runtime.id}`, @@ -1252,7 +1219,7 @@ function getEnabledActionTypes(forceFullVideo = false): ActionType[] { async function lockedCategoriesLookup(): Promise<void> { const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4); - const response = await utils.asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix); + const response = await asyncRequestToServer("GET", "/api/lockCategories/" + hashPrefix); if (response.ok) { try { @@ -1646,7 +1613,7 @@ function sendTelemetryAndCount(skippingSegments: SponsorTime[], secondsSkipped: counted = true; } - if (fullSkip) utils.asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); + if (fullSkip) asyncRequestToServer("POST", "/api/viewedVideoSponsorTime?UUID=" + segment.UUID); } } } @@ -2282,7 +2249,7 @@ async function sendSubmitMessage() { } } - const response = await utils.asyncRequestToServer("POST", "/api/skipSegments", { + const response = await asyncRequestToServer("POST", "/api/skipSegments", { videoID: getVideoID(), userID: Config.config.userID, segments: sponsorTimesSubmitting, diff --git a/src/dearrowPromotion.ts b/src/dearrowPromotion.ts new file mode 100644 index 00000000..208c627f --- /dev/null +++ b/src/dearrowPromotion.ts @@ -0,0 +1,71 @@ +import { waitFor } from "../maze-utils/src"; +import { getYouTubeTitleNode } from "../maze-utils/src/elements"; +import { getHash } from "../maze-utils/src/hash"; +import { getVideoID, isOnInvidious, isOnMobileYouTube } from "../maze-utils/src/video"; +import Config from "./config"; +import { Tooltip } from "./render/Tooltip"; +import { isDeArrowInstalled } from "./utils/crossExtension"; +import { isVisible } from "./utils/pageUtils"; +import { asyncRequestToServer } from "./utils/requests"; + +let tooltip: Tooltip = null; +export async function tryShowingDeArrowPromotion() { + if (Config.config.showDeArrowPromotion + && !isOnMobileYouTube() + && !isOnInvidious() + && document.URL.includes("watch") + && Config.config.showUpsells + && Config.config.showNewFeaturePopups + && (Config.config.skipCount > 30 || !Config.config.trackViewCount)) { + + if (!await isDeArrowInstalled()) { + try { + const element = await waitFor(() => getYouTubeTitleNode(), 5000, 500, (e) => isVisible(e)) as HTMLElement; + if (element && element.innerText && badTitle(element.innerText)) { + const hashPrefix = (await getHash(getVideoID(), 1)).slice(0, 4); + const deArrowData = await asyncRequestToServer("GET", "/api/branding/" + hashPrefix); + if (!deArrowData.ok) return; + + const deArrowDataJson = JSON.parse(deArrowData.responseText); + const title = deArrowDataJson?.[getVideoID()]?.titles?.[0]; + if (title && title.title && (title.locked || title.votes > 0)) { + Config.config.showDeArrowPromotion = false; + + tooltip = new Tooltip({ + text: chrome.i18n.getMessage("DeArrowTitleReplacementSuggestion") + "\n\n" + title.title, + linkOnClick: () => { + window.open("https://dearrow.ajay.app"); + Config.config.shownDeArrowPromotion = true; + }, + referenceNode: element, + prependElement: element.firstElementChild as HTMLElement, + timeout: 15000, + positionRealtive: false, + containerAbsolute: true, + bottomOffset: "inherit", + topOffset: "55px", + leftOffset: "0", + rightOffset: "0", + topTriangle: true, + center: true, + opacity: 1 + }); + } + } + } catch { } // eslint-disable-line no-empty + } else { + Config.config.showDeArrowPromotion = false; + } + } +} + +/** + * Two upper case words (at least 2 letters long) + */ +function badTitle(title: string): boolean { + return !!title.match(/\p{Lu}{2,} \p{Lu}{2,}[.!? ]/u); +} + +export function hideDeArrowPromotion(): void { + if (tooltip) tooltip.close(); +}
\ No newline at end of file diff --git a/src/options.ts b/src/options.ts index 8acdd86e..6b088538 100644 --- a/src/options.ts +++ b/src/options.ts @@ -18,6 +18,7 @@ import { StorageChangesObject } from "../maze-utils/src/config"; import { getHash } from "../maze-utils/src/hash"; import { isFirefoxOrSafari } from "../maze-utils/src"; import { isDeArrowInstalled } from "./utils/crossExtension"; +import { asyncRequestToServer } from "./utils/requests"; const utils = new Utils(); let embed = false; @@ -567,7 +568,7 @@ function activatePrivateTextChange(element: HTMLElement) { switch (option) { case "userID": if (Config.config[option]) { - utils.asyncRequestToServer("GET", "/api/userInfo", { + asyncRequestToServer("GET", "/api/userInfo", { publicUserID: getHash(Config.config[option]), values: ["warnings", "banned"] }).then((result) => { diff --git a/src/popup.ts b/src/popup.ts index 41fb9210..21267afe 100644 --- a/src/popup.ts +++ b/src/popup.ts @@ -27,6 +27,7 @@ import GenericNotice from "./render/GenericNotice"; import { getErrorMessage, getFormattedTime } from "../maze-utils/src/formating"; import { StorageChangesObject } from "../maze-utils/src/config"; import { getHash } from "../maze-utils/src/hash"; +import { asyncRequestToServer, sendRequestToServer } from "./utils/requests"; const utils = new Utils(); @@ -295,7 +296,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { const values = ["userName", "viewCount", "minutesSaved", "vip", "permissions"]; - utils.asyncRequestToServer("GET", "/api/userInfo", { + asyncRequestToServer("GET", "/api/userInfo", { publicUserID: await getHash(Config.config.userID), values }).then((res) => { @@ -818,7 +819,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> { PageElements.setUsernameStatus.style.display = "unset"; PageElements.setUsernameStatus.innerText = chrome.i18n.getMessage("Loading"); - utils.sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) { + sendRequestToServer("POST", "/api/setUsername?userID=" + Config.config.userID + "&username=" + PageElements.usernameInput.value, function (response) { if (response.status == 200) { //submitted PageElements.submitUsername.style.display = "none"; diff --git a/src/utils.ts b/src/utils.ts index 7b3a3841..c96a7772 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -2,10 +2,8 @@ import Config, { VideoDownvotes } from "./config"; import { CategorySelection, SponsorTime, BackgroundScriptContainer, Registration, VideoID, SponsorHideType, CategorySkipOption } from "./types"; import { getHash, HashedValue } from "../maze-utils/src/hash"; -import * as CompileConfig from "../config.json"; import { isFirefoxOrSafari, waitFor } from "../maze-utils/src"; import { findValidElementFromSelector } from "../maze-utils/src/dom"; -import { FetchResponse, sendRequestToCustomServer } from "../maze-utils/src/background-request-proxy" import { isSafari } from "../maze-utils/src/config"; export default class Utils { @@ -240,50 +238,6 @@ export default class Utils { return permissionRegex; } - /** - * Sends a request to a custom server - * - * @param type The request type. "GET", "POST", etc. - * @param address The address to add to the SponsorBlock server address - * @param callback - */ - asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> { - return sendRequestToCustomServer(type, url, data); - } - - /** - * Sends a request to the SponsorBlock server with address added as a query - * - * @param type The request type. "GET", "POST", etc. - * @param address The address to add to the SponsorBlock server address - * @param callback - */ - async asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> { - const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; - - return await (this.asyncRequestToCustomServer(type, serverAddress + address, data)); - } - - /** - * Sends a request to the SponsorBlock server with address added as a query - * - * @param type The request type. "GET", "POST", etc. - * @param address The address to add to the SponsorBlock server address - * @param callback - */ - sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void): void { - const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; - - // Ask the background script to do the work - chrome.runtime.sendMessage({ - message: "sendRequest", - type, - url: serverAddress + address - }, (response) => { - callback(response); - }); - } - findReferenceNode(): HTMLElement { const selectors = [ "#player-container-id", // Mobile YouTube diff --git a/src/utils/requests.ts b/src/utils/requests.ts new file mode 100644 index 00000000..8c160eb0 --- /dev/null +++ b/src/utils/requests.ts @@ -0,0 +1,47 @@ +import Config from "../config"; +import * as CompileConfig from "../../config.json"; +import { FetchResponse, sendRequestToCustomServer } from "../../maze-utils/src/background-request-proxy"; + +/** + * Sends a request to a custom server + * + * @param type The request type. "GET", "POST", etc. + * @param address The address to add to the SponsorBlock server address + * @param callback + */ +export function asyncRequestToCustomServer(type: string, url: string, data = {}): Promise<FetchResponse> { + return sendRequestToCustomServer(type, url, data); +} + +/** + * Sends a request to the SponsorBlock server with address added as a query + * + * @param type The request type. "GET", "POST", etc. + * @param address The address to add to the SponsorBlock server address + * @param callback + */ +export async function asyncRequestToServer(type: string, address: string, data = {}): Promise<FetchResponse> { + const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; + + return await (asyncRequestToCustomServer(type, serverAddress + address, data)); +} + +/** + * Sends a request to the SponsorBlock server with address added as a query + * + * @param type The request type. "GET", "POST", etc. + * @param address The address to add to the SponsorBlock server address + * @param callback + */ +export function sendRequestToServer(type: string, address: string, callback?: (response: FetchResponse) => void): void { + const serverAddress = Config.config.testingServer ? CompileConfig.testingServerAddress : Config.config.serverAddress; + + // Ask the background script to do the work + chrome.runtime.sendMessage({ + message: "sendRequest", + type, + url: serverAddress + address + }, (response) => { + callback(response); + }); +}
\ No newline at end of file diff --git a/src/utils/videoLabels.ts b/src/utils/videoLabels.ts index 731bfd33..82af788c 100644 --- a/src/utils/videoLabels.ts +++ b/src/utils/videoLabels.ts @@ -2,6 +2,7 @@ import { Category, CategorySkipOption, VideoID } from "../types"; import { getHash } from "../../maze-utils/src/hash"; import Utils from "../utils"; import { logWarn } from "./logger"; +import { asyncRequestToServer } from "./requests"; const utils = new Utils(); @@ -20,7 +21,7 @@ async function getLabelHashBlock(hashPrefix: string): Promise<LabelCacheEntry | return cachedEntry; } - const response = await utils.asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`); + const response = await asyncRequestToServer("GET", `/api/videoLabels/${hashPrefix}`); if (response.status !== 200) { // No video labels or server down labelCache[hashPrefix] = { diff --git a/src/utils/warnings.ts b/src/utils/warnings.ts index 72062699..e35c703c 100644 --- a/src/utils/warnings.ts +++ b/src/utils/warnings.ts @@ -3,8 +3,7 @@ import { getHash } from "../../maze-utils/src/hash"; import Config from "../config"; import GenericNotice, { NoticeOptions } from "../render/GenericNotice"; import { ContentContainer } from "../types"; -import Utils from "../utils"; -const utils = new Utils(); +import { asyncRequestToServer } from "./requests"; export interface ChatConfig { displayName: string; @@ -13,14 +12,14 @@ export interface ChatConfig { } export async function openWarningDialog(contentContainer: ContentContainer): Promise<void> { - const userInfo = await utils.asyncRequestToServer("GET", "/api/userInfo", { + const userInfo = await asyncRequestToServer("GET", "/api/userInfo", { publicUserID: await getHash(Config.config.userID), values: ["warningReason"] }); if (userInfo.ok) { const warningReason = JSON.parse(userInfo.responseText)?.warningReason; - const userNameData = await utils.asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID); + const userNameData = await asyncRequestToServer("GET", "/api/getUsername?userID=" + Config.config.userID); const userName = userNameData.ok ? JSON.parse(userNameData.responseText).userName : ""; const publicUserID = await getHash(Config.config.userID); @@ -43,7 +42,7 @@ export async function openWarningDialog(contentContainer: ContentContainer): Pro { name: chrome.i18n.getMessage("warningConfirmButton"), listener: async () => { - const result = await utils.asyncRequestToServer("POST", "/api/warnUser", { + const result = await asyncRequestToServer("POST", "/api/warnUser", { userID: Config.config.userID, enabled: false }); |