aboutsummaryrefslogtreecommitdiffhomepage
path: root/src
diff options
context:
space:
mode:
authorAjay <[email protected]>2022-09-01 15:15:30 -0400
committerAjay <[email protected]>2022-09-01 15:15:30 -0400
commitc6e30236e973e7764582bf7a0b8f0f10bdc73fbb (patch)
tree52bded8a60ca682bfa81ce77bcd1486e8b911ee9 /src
parent34c4ecf9409eeb13b731c59e62e3d62b13e5b5c1 (diff)
downloadSponsorBlock-c6e30236e973e7764582bf7a0b8f0f10bdc73fbb.tar.gz
SponsorBlock-c6e30236e973e7764582bf7a0b8f0f10bdc73fbb.zip
Add license requirement
Diffstat (limited to 'src')
-rw-r--r--src/components/options/CategorySkipOptionsComponent.tsx40
-rw-r--r--src/components/options/ToggleOptionComponent.tsx7
-rw-r--r--src/config.ts18
-rw-r--r--src/content.ts2
-rw-r--r--src/popup.ts9
-rw-r--r--src/svg-icons/lock_svg.tsx22
-rw-r--r--src/upsell.ts71
-rw-r--r--src/utils/licenseKey.ts65
8 files changed, 223 insertions, 11 deletions
diff --git a/src/components/options/CategorySkipOptionsComponent.tsx b/src/components/options/CategorySkipOptionsComponent.tsx
index b298347e..338435a8 100644
--- a/src/components/options/CategorySkipOptionsComponent.tsx
+++ b/src/components/options/CategorySkipOptionsComponent.tsx
@@ -6,6 +6,8 @@ import { Category, CategorySkipOption } from "../../types";
import { getCategorySuffix } from "../../utils/categoryUtils";
import ToggleOptionComponent, { ToggleOptionProps } from "./ToggleOptionComponent";
+import { fetchingChaptersAllowed } from "../../utils/licenseKey";
+import LockSvg from "../../svg-icons/lock_svg";
export interface CategorySkipOptionsProps {
category: Category;
@@ -16,6 +18,7 @@ export interface CategorySkipOptionsProps {
export interface CategorySkipOptionsState {
color: string;
previewColor: string;
+ hideChapter: boolean;
}
class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsProps, CategorySkipOptionsState> {
@@ -28,7 +31,14 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
this.state = {
color: props.defaultColor || Config.config.barTypes[this.props.category]?.color,
previewColor: props.defaultPreviewColor || Config.config.barTypes["preview-" + this.props.category]?.color,
- }
+ hideChapter: true
+ };
+
+ fetchingChaptersAllowed().then((allowed) => {
+ this.setState({
+ hideChapter: !allowed
+ });
+ })
}
render(): React.ReactElement {
@@ -52,12 +62,25 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}
}
+ let extraClasses = "";
+ const disabled = this.props.category === "chapter" && this.state.hideChapter;
+ if (disabled) {
+ extraClasses += " disabled";
+
+ if (!Config.config.showUpsells) {
+ return <></>;
+ }
+ }
+
return (
<>
<tr id={this.props.category + "OptionsRow"}
- className="categoryTableElement">
+ className={`categoryTableElement${extraClasses}`} >
<td id={this.props.category + "OptionName"}
className="categoryTableLabel">
+ {disabled &&
+ <LockSvg className="upsellButton" onClick={() => chrome.tabs.create({url: chrome.runtime.getURL('upsell/index.html')})}/>
+ }
{chrome.i18n.getMessage("category_" + this.props.category)}
</td>
@@ -66,6 +89,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
<select
className="optionsSelector"
defaultValue={defaultOption}
+ disabled={disabled}
onChange={this.skipOptionSelected.bind(this)}>
{this.getCategorySkipOptions()}
</select>
@@ -77,6 +101,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
<input
className="categoryColorTextBox option-text-box"
type="color"
+ disabled={disabled}
onChange={(event) => this.setColorState(event, false)}
value={this.state.color} />
</td>
@@ -96,7 +121,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
</tr>
<tr id={this.props.category + "DescriptionRow"}
- className="small-description categoryTableDescription">
+ className={`small-description categoryTableDescription${extraClasses}`}>
<td
colSpan={2}>
{chrome.i18n.getMessage("category_" + this.props.category + "_description")}
@@ -107,7 +132,7 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
</td>
</tr>
- {this.getExtraOptionComponents(this.props.category)}
+ {this.getExtraOptionComponents(this.props.category, extraClasses, disabled)}
</>
);
@@ -191,15 +216,16 @@ class CategorySkipOptionsComponent extends React.Component<CategorySkipOptionsPr
}, 50);
}
- getExtraOptionComponents(category: string): JSX.Element[] {
+ getExtraOptionComponents(category: string, extraClasses: string, disabled: boolean): JSX.Element[] {
const result = [];
for (const option of this.getExtraOptions(category)) {
result.push(
- <tr key={option.configKey}>
+ <tr key={option.configKey} className={extraClasses}>
<td id={`${category}_${option.configKey}`} className="categoryExtraOptions">
<ToggleOptionComponent
configKey={option.configKey}
- label={option.label}
+ label={option.label}
+ disabled={disabled}
/>
</td>
</tr>
diff --git a/src/components/options/ToggleOptionComponent.tsx b/src/components/options/ToggleOptionComponent.tsx
index b67024f4..c6ea698d 100644
--- a/src/components/options/ToggleOptionComponent.tsx
+++ b/src/components/options/ToggleOptionComponent.tsx
@@ -5,6 +5,7 @@ import Config from "../../config";
export interface ToggleOptionProps {
configKey: string;
label: string;
+ disabled?: boolean;
}
export interface ToggleOptionState {
@@ -27,7 +28,11 @@ class ToggleOptionComponent extends React.Component<ToggleOptionProps, ToggleOpt
<div>
<div className="switch-container">
<label className="switch">
- <input id={this.props.configKey} type="checkbox" checked={this.state.enabled} onChange={(e) => this.clicked(e)}/>
+ <input id={this.props.configKey}
+ type="checkbox"
+ checked={this.state.enabled}
+ disabled={this.props.disabled}
+ onChange={(e) => this.clicked(e)}/>
<span className="slider round"></span>
</label>
<label className="switch-label" htmlFor={this.props.configKey}>
diff --git a/src/config.ts b/src/config.ts
index 60286f40..2fc48814 100644
--- a/src/config.ts
+++ b/src/config.ts
@@ -50,6 +50,7 @@ interface SBConfig {
allowExpirements: boolean,
showDonationLink: boolean,
showPopupDonationCount: number,
+ showUpsells: boolean,
donateClicked: number,
autoHideInfoButton: boolean,
autoSkipOnMusicVideos: boolean,
@@ -81,6 +82,13 @@ interface SBConfig {
// What categories should be skipped
categorySelections: CategorySelection[],
+ payments: {
+ licenseKey: string,
+ lastCheck: number,
+ freeAccess: boolean,
+ chaptersAllowed: boolean
+ }
+
// Preview bar
barTypes: {
"preview-chooseACategory": PreviewBarOption,
@@ -140,7 +148,7 @@ const Config: SBObject = {
permissions: {},
unsubmittedSegments: {},
defaultCategory: "chooseACategory" as Category,
- renderSegmentsAsChapters: true,
+ renderSegmentsAsChapters: false,
whitelistedChannels: [],
forceChannelCheck: false,
minutesSaved: 0,
@@ -176,6 +184,7 @@ const Config: SBObject = {
allowExpirements: true,
showDonationLink: true,
showPopupDonationCount: 0,
+ showUpsells: true,
donateClicked: 0,
autoHideInfoButton: true,
autoSkipOnMusicVideos: false,
@@ -211,6 +220,13 @@ const Config: SBObject = {
option: CategorySkipOption.ShowOverlay
}],
+ payments: {
+ licenseKey: null,
+ lastCheck: 0,
+ freeAccess: false,
+ chaptersAllowed: false
+ },
+
colorPalette: {
red: "#780303",
white: "#ffffff",
diff --git a/src/content.ts b/src/content.ts
index f1ce8c36..a1f22fbc 100644
--- a/src/content.ts
+++ b/src/content.ts
@@ -1026,7 +1026,7 @@ function importExistingChapters(wait: boolean) {
existingChaptersImported = true;
updatePreviewBar();
}
- });
+ }).catch(() => {}); // eslint-disable-line @typescript-eslint/no-empty-function
}
}
diff --git a/src/popup.ts b/src/popup.ts
index 9d60b058..60c875a1 100644
--- a/src/popup.ts
+++ b/src/popup.ts
@@ -255,7 +255,7 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
PageElements.showNoticeAgain.style.display = "unset";
}
- utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&value=vip&value=permissions&userID="
+ utils.sendRequestToServer("GET", "/api/userInfo?value=userName&value=viewCount&value=minutesSaved&value=vip&value=permissions&value=freeChaptersAccess&userID="
+ Config.config.userID, (res) => {
if (res.status === 200) {
const userInfo = JSON.parse(res.responseText);
@@ -286,6 +286,13 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
Config.config.isVip = userInfo.vip;
Config.config.permissions = userInfo.permissions;
+
+ if (userInfo.freeChaptersAccess) {
+ Config.config.payments.chaptersAllowed = userInfo.freeChaptersAccess;
+ Config.config.payments.freeAccess = userInfo.freeChaptersAccess;
+ Config.config.payments.lastCheck = Date.now();
+ Config.forceSyncUpdate("payments");
+ }
}
});
diff --git a/src/svg-icons/lock_svg.tsx b/src/svg-icons/lock_svg.tsx
new file mode 100644
index 00000000..ef077541
--- /dev/null
+++ b/src/svg-icons/lock_svg.tsx
@@ -0,0 +1,22 @@
+import * as React from "react";
+
+const lockSvg = ({
+ fill = "#fcba03",
+ className = "",
+ width = "20",
+ height = "20",
+ onClick
+}): JSX.Element => (
+ <svg
+ xmlns="http://www.w3.org/2000/svg"
+ height={width}
+ width={height}
+ className={className}
+ fill={fill}
+ onClick={onClick} >
+ <path
+ d="M5.5 18q-.625 0-1.062-.438Q4 17.125 4 16.5v-8q0-.625.438-1.062Q4.875 7 5.5 7H6V5q0-1.667 1.167-2.833Q8.333 1 10 1q1.667 0 2.833 1.167Q14 3.333 14 5v2h.5q.625 0 1.062.438Q16 7.875 16 8.5v8q0 .625-.438 1.062Q15.125 18 14.5 18Zm4.5-4q.625 0 1.062-.438.438-.437.438-1.062t-.438-1.062Q10.625 11 10 11t-1.062.438Q8.5 11.875 8.5 12.5t.438 1.062Q9.375 14 10 14ZM7.5 7h5V5q0-1.042-.729-1.771Q11.042 2.5 10 2.5q-1.042 0-1.771.729Q7.5 3.958 7.5 5Z"/>
+ </svg>
+);
+
+export default lockSvg;
diff --git a/src/upsell.ts b/src/upsell.ts
new file mode 100644
index 00000000..faa94a1d
--- /dev/null
+++ b/src/upsell.ts
@@ -0,0 +1,71 @@
+import Config from "./config";
+import { checkLicenseKey } from "./utils/licenseKey";
+import { localizeHtmlPage } from "./utils/pageUtils";
+
+import * as countries from "../public/res/countries.json";
+
+// This is needed, if Config is not imported before Utils, things break.
+// Probably due to cyclic dependencies
+Config.config;
+
+window.addEventListener('DOMContentLoaded', init);
+
+async function init() {
+ localizeHtmlPage();
+
+ const cantAfford = document.getElementById("cantAfford");
+ const cantAffordTexts = chrome.i18n.getMessage("cantAfford").split(/{|}/);
+ cantAfford.appendChild(document.createTextNode(cantAffordTexts[0]));
+ const discountButton = document.createElement("span");
+ discountButton.id = "discountButton";
+ discountButton.innerText = cantAffordTexts[1];
+ cantAfford.appendChild(discountButton);
+ cantAfford.appendChild(document.createTextNode(cantAffordTexts[2]));
+
+ const redeemButton = document.getElementById("redeemButton") as HTMLInputElement;
+ redeemButton.addEventListener("click", async () => {
+ const licenseKey = redeemButton.value;
+
+ if (await checkLicenseKey(licenseKey)) {
+ Config.config.payments.licenseKey = licenseKey;
+ Config.forceSyncUpdate("payments");
+
+ alert(chrome.i18n.getMessage("redeemSuccess"));
+ } else {
+ alert(chrome.i18n.getMessage("redeemFailed"));
+ }
+ });
+
+ discountButton.addEventListener("click", async () => {
+ const subsidizedSection = document.getElementById("subsidizedPrice");
+ subsidizedSection.classList.remove("hidden");
+
+ const oldSelector = document.getElementById("countrySelector");
+ if (oldSelector) oldSelector.remove();
+ const countrySelector = document.createElement("select");
+ countrySelector.id = "countrySelector";
+ countrySelector.className = "optionsSelector";
+ const defaultOption = document.createElement("option");
+ defaultOption.innerText = chrome.i18n.getMessage("chooseACountry");
+ countrySelector.appendChild(defaultOption);
+
+ for (const country of Object.keys(countries)) {
+ const option = document.createElement("option");
+ option.value = country;
+ option.innerText = country;
+ countrySelector.appendChild(option);
+ }
+
+ countrySelector.addEventListener("change", () => {
+ if (countries[countrySelector.value]?.allowed) {
+ document.getElementById("subsidizedLink").classList.remove("hidden");
+ document.getElementById("noSubsidizedLink").classList.add("hidden");
+ } else {
+ document.getElementById("subsidizedLink").classList.add("hidden");
+ document.getElementById("noSubsidizedLink").classList.remove("hidden");
+ }
+ });
+
+ subsidizedSection.appendChild(countrySelector);
+ });
+} \ No newline at end of file
diff --git a/src/utils/licenseKey.ts b/src/utils/licenseKey.ts
new file mode 100644
index 00000000..53c54c76
--- /dev/null
+++ b/src/utils/licenseKey.ts
@@ -0,0 +1,65 @@
+import Config from "../config";
+import Utils from "../utils";
+import * as CompileConfig from "../../config.json";
+
+const utils = new Utils();
+
+export async function checkLicenseKey(licenseKey: string): Promise<boolean> {
+ const result = await utils.asyncRequestToServer("GET", "/api/verifyToken", {
+ licenseKey
+ });
+
+ try {
+ if (result.ok && JSON.parse(result.responseText).allowed) {
+ Config.config.payments.chaptersAllowed = true;
+ Config.config.payments.lastCheck = Date.now();
+ Config.forceSyncUpdate("payments");
+
+ return true;
+ }
+ } catch (e) { } //eslint-disable-line no-empty
+
+ return false
+}
+
+export async function fetchingChaptersAllowed(): Promise<boolean> {
+ if (Config.config.payments.freeAccess || CompileConfig["freeChapterAccess"]) {
+ return true;
+ }
+
+ //more than 14 days
+ if (Config.config.payments.licenseKey && Date.now() - Config.config.payments.lastCheck > 14 * 24 * 60 * 60 * 1000) {
+ const licensePromise = checkLicenseKey(Config.config.payments.licenseKey);
+
+ if (!Config.config.payments.chaptersAllowed) {
+ return licensePromise;
+ }
+ }
+
+ if (Config.config.payments.chaptersAllowed) return true;
+
+ if (Config.config.payments.lastCheck === 0) {
+ // Check for free access if no license key, and it is the first time
+ const result = await utils.asyncRequestToServer("GET", "/api/userInfo", {
+ value: "freeChaptersAccess",
+ userID: Config.config.userID
+ });
+
+ try {
+ if (result.ok) {
+ const userInfo = JSON.parse(result.responseText);
+
+ Config.config.payments.lastCheck = Date.now();
+ if (userInfo.freeChaptersAccess) {
+ Config.config.payments.freeAccess = true;
+ Config.config.payments.chaptersAllowed = true;
+ Config.forceSyncUpdate("payments");
+
+ return true;
+ }
+ }
+ } catch (e) { } //eslint-disable-line no-empty
+ }
+
+ return false;
+} \ No newline at end of file