diff options
author | Ajay <[email protected]> | 2022-09-01 15:15:30 -0400 |
---|---|---|
committer | Ajay <[email protected]> | 2022-09-01 15:15:30 -0400 |
commit | c6e30236e973e7764582bf7a0b8f0f10bdc73fbb (patch) | |
tree | 52bded8a60ca682bfa81ce77bcd1486e8b911ee9 | |
parent | 34c4ecf9409eeb13b731c59e62e3d62b13e5b5c1 (diff) | |
download | SponsorBlock-c6e30236e973e7764582bf7a0b8f0f10bdc73fbb.tar.gz SponsorBlock-c6e30236e973e7764582bf7a0b8f0f10bdc73fbb.zip |
Add license requirement
-rw-r--r-- | public/_locales/en/messages.json | 47 | ||||
-rw-r--r-- | public/options/options.css | 9 | ||||
-rw-r--r-- | public/options/options.html | 12 | ||||
-rw-r--r-- | public/res/countries.json | 1 | ||||
-rw-r--r-- | public/upsell/index.html | 94 | ||||
-rw-r--r-- | public/upsell/styles.css | 387 | ||||
-rw-r--r-- | src/components/options/CategorySkipOptionsComponent.tsx | 40 | ||||
-rw-r--r-- | src/components/options/ToggleOptionComponent.tsx | 7 | ||||
-rw-r--r-- | src/config.ts | 18 | ||||
-rw-r--r-- | src/content.ts | 2 | ||||
-rw-r--r-- | src/popup.ts | 9 | ||||
-rw-r--r-- | src/svg-icons/lock_svg.tsx | 22 | ||||
-rw-r--r-- | src/upsell.ts | 71 | ||||
-rw-r--r-- | src/utils/licenseKey.ts | 65 | ||||
-rw-r--r-- | webpack/webpack.common.js | 3 |
15 files changed, 775 insertions, 12 deletions
diff --git a/public/_locales/en/messages.json b/public/_locales/en/messages.json index ab6c4e4c..ae14284a 100644 --- a/public/_locales/en/messages.json +++ b/public/_locales/en/messages.json @@ -1103,5 +1103,52 @@ "Import": { "message": "Import", "description": "Button to initiate importing segments. Appears under the textbox where they paste in the data" + }, + "redeemSuccess": { + "message": "Reedem Successful!" + }, + "redeemFailed": { + "message": "License key is invalid" + }, + "hideUpsells": { + "message": "Hide options not available without extra payment" + }, + "chooseACountry": { + "message": "Choose a country" + }, + "noDiscount": { + "message": "You do not qualify for a discount" + }, + "discountLink": { + "message": "Discount Link (See the pink price)" + }, + "selectYourCountry": { + "message": "Select your country" + }, + "alreadyDonated": { + "message": "If you've donated any amount before now, you may redeem free access by emailing:", + "description": "After the colon is an email address" + }, + "cantAfford": { + "message": "If you can't afford to purchase a license, click {here} to see if you are eligible for a discount", + "description": "Keep the curly braces" + }, + "patreonSignIn": { + "message": "Sign in with Patreon" + }, + "redeem": { + "message": "Redeem" + }, + "joinOnPatreon": { + "message": "Subscribe on Patreon" + }, + "oneTimePurchase": { + "message": "One Time Purchase" + }, + "enterLicenseKey": { + "message": "Enter License Key" + }, + "chaptersPage1": { + "message": "SponsorBlock crowd-sourced chapters feature is only available to people who purchase a license, or for people who are granted access for free due their past contributions" } } diff --git a/public/options/options.css b/public/options/options.css index 36f4d5d7..2bd27161 100644 --- a/public/options/options.css +++ b/public/options/options.css @@ -317,6 +317,10 @@ input[type='number'] { color: grey; } +tr.disabled { + opacity: 0.3; +} + #options { height: 100vh; flex-basis: 80%; @@ -678,4 +682,9 @@ svg { #options > div { max-width: 100%; } +} + +.upsellButton { + cursor: pointer; + vertical-align: middle; }
\ No newline at end of file diff --git a/public/options/options.html b/public/options/options.html index 150212ce..765134ec 100644 --- a/public/options/options.html +++ b/public/options/options.html @@ -302,6 +302,18 @@ </div> </div> + <div data-type="toggle" data-toggle-type="reverse" data-sync="showUpsells" data-no-safari="true"> + <div class="switch-container"> + <label class="switch"> + <input id="showUpsell" type="checkbox" checked> + <span class="slider round"></span> + </label> + <label class="switch-label" for="showUpsells"> + __MSG_hideUpsells__ + </label> + </div> + </div> + </div> <div id="keybinds" class="option-group hidden"> diff --git a/public/res/countries.json b/public/res/countries.json new file mode 100644 index 00000000..33839f29 --- /dev/null +++ b/public/res/countries.json @@ -0,0 +1 @@ +{"Albania":{"allowed":true},"Algeria":{"allowed":true},"Angola":{"allowed":true},"Argentina":{"allowed":true},"Armenia":{"allowed":true},"Australia":{"allowed":false},"Austria":{"allowed":false},"Azerbaijan":{"allowed":true},"Bangladesh":{"allowed":true},"Belarus":{"allowed":true},"Belgium":{"allowed":false},"Belize":{"allowed":true},"Benin":{"allowed":true},"Bhutan":{"allowed":true},"Bolivia":{"allowed":true},"Bosnia and Herzegovina":{"allowed":true},"Botswana":{"allowed":true},"Brazil":{"allowed":true},"Bulgaria":{"allowed":true},"Burkina Faso":{"allowed":true},"Burundi":{"allowed":true},"Cameroon":{"allowed":true},"Canada":{"allowed":false},"Central African Republic":{"allowed":true},"Chad":{"allowed":true},"Chile":{"allowed":true},"China":{"allowed":true},"Colombia":{"allowed":true},"Comoros":{"allowed":true},"Costa Rica":{"allowed":true},"Croatia":{"allowed":true},"Cyprus":{"allowed":false},"Czech Republic":{"allowed":false},"Denmark":{"allowed":false},"Djibouti":{"allowed":true},"Dominican Republic":{"allowed":true},"DR Congo":{"allowed":true},"Ecuador":{"allowed":true},"Egypt":{"allowed":true},"El Salvador":{"allowed":true},"Estonia":{"allowed":false},"Eswatini":{"allowed":true},"Ethiopia":{"allowed":true},"Fiji":{"allowed":true},"Finland":{"allowed":false},"France":{"allowed":false},"Gabon":{"allowed":true},"Gambia":{"allowed":true},"Georgia":{"allowed":true},"Germany":{"allowed":false},"Ghana":{"allowed":true},"Greece":{"allowed":true},"Guatemala":{"allowed":true},"Guinea":{"allowed":true},"Guinea-Bissau":{"allowed":true},"Guyana":{"allowed":true},"Haiti":{"allowed":true},"Honduras":{"allowed":true},"Hungary":{"allowed":true},"Iceland":{"allowed":false},"India":{"allowed":true},"Iran":{"allowed":true},"Iraq":{"allowed":true},"Ireland":{"allowed":false},"Israel":{"allowed":false},"Italy":{"allowed":false},"Ivory Coast":{"allowed":true},"Jamaica":{"allowed":true},"Japan":{"allowed":false},"Jordan":{"allowed":true},"Kazakhstan":{"allowed":true},"Kenya":{"allowed":true},"Kiribati":{"allowed":true},"Kyrgyzstan":{"allowed":true},"Laos":{"allowed":true},"Latvia":{"allowed":true},"Lebanon":{"allowed":true},"Lesotho":{"allowed":true},"Liberia":{"allowed":true},"Lithuania":{"allowed":true},"Luxembourg":{"allowed":false},"Madagascar":{"allowed":true},"Malawi":{"allowed":true},"Malaysia":{"allowed":true},"Maldives":{"allowed":true},"Mali":{"allowed":true},"Malta":{"allowed":false},"Mauritania":{"allowed":true},"Mauritius":{"allowed":true},"Mexico":{"allowed":true},"Micronesia":{"allowed":true},"Moldova":{"allowed":true},"Mongolia":{"allowed":true},"Montenegro":{"allowed":true},"Morocco":{"allowed":true},"Mozambique":{"allowed":true},"Myanmar":{"allowed":true},"Namibia":{"allowed":true},"Nepal":{"allowed":true},"Netherlands":{"allowed":false},"Nicaragua":{"allowed":true},"Niger":{"allowed":true},"Nigeria":{"allowed":true},"North Macedonia":{"allowed":true},"Norway":{"allowed":false},"Pakistan":{"allowed":true},"Panama":{"allowed":true},"Papua New Guinea":{"allowed":true},"Paraguay":{"allowed":true},"Peru":{"allowed":true},"Philippines":{"allowed":true},"Poland":{"allowed":true},"Portugal":{"allowed":true},"Republic of the Congo":{"allowed":true},"Romania":{"allowed":true},"Russia":{"allowed":true},"Rwanda":{"allowed":true},"Saint Lucia":{"allowed":true},"Samoa":{"allowed":true},"Sao Tome and Principe":{"allowed":true},"Senegal":{"allowed":true},"Serbia":{"allowed":true},"Seychelles":{"allowed":true},"Sierra Leone":{"allowed":true},"Slovakia":{"allowed":true},"Slovenia":{"allowed":false},"Solomon Islands":{"allowed":true},"South Africa":{"allowed":true},"South Korea":{"allowed":false},"South Sudan":{"allowed":true},"Spain":{"allowed":false},"Sri Lanka":{"allowed":true},"Sudan":{"allowed":true},"Suriname":{"allowed":true},"Sweden":{"allowed":false},"Switzerland":{"allowed":false},"Syria":{"allowed":true},"Taiwan":{"allowed":false},"Tajikistan":{"allowed":true},"Tanzania":{"allowed":true},"Thailand":{"allowed":true},"Timor-Leste":{"allowed":true},"Togo":{"allowed":true},"Tonga":{"allowed":true},"Trinidad and Tobago":{"allowed":true},"Tunisia":{"allowed":true},"Turkey":{"allowed":true},"Turkmenistan":{"allowed":true},"Tuvalu":{"allowed":true},"Uganda":{"allowed":true},"Ukraine":{"allowed":true},"United Arab Emirates":{"allowed":false},"United Kingdom":{"allowed":false},"United States":{"allowed":false},"Uruguay":{"allowed":true},"Uzbekistan":{"allowed":true},"Vanuatu":{"allowed":true},"Venezuela":{"allowed":true},"Vietnam":{"allowed":true},"Yemen":{"allowed":true},"Zambia":{"allowed":true},"Zimbabwe":{"allowed":true}}
\ No newline at end of file diff --git a/public/upsell/index.html b/public/upsell/index.html new file mode 100644 index 00000000..1dbc21af --- /dev/null +++ b/public/upsell/index.html @@ -0,0 +1,94 @@ +<!DOCTYPE html> + +<head> + <title>Upsell - SponsorBlock</title> + <meta charset="utf-8"> + + <link href="styles.css" rel="stylesheet" /> + + <script src="../js/vendor.js"></script> + <script src="../js/upsell.js"></script> +</head> + +<body class="sponsorBlockPageBody"> + + <div id="title" class="titleBar"> + <img src="../icons/LogoSponsorBlocker256px.png" height="80" class="profilepic" /> + SponsorBlock + </div> + + <br /> + + <div class="center"> + <p> + __MSG_chaptersPage1__ + </p> + </div> + + <div class="center"> + <iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/H_mP7bpbA_c?modestbranding=1&rel=0" title="Demo Video" + frameborder="0" allow="autoplay; clipboard-write; encrypted-media; picture-in-picture" + allowfullscreen> + </iframe> + </div> + + + + <br /> + + <div class="center row-item"> + <a href="https://buy.ajay.app/l/sponsorblock" class="option-link side-by-side" target="_blank" rel="noreferrer"> + <div id="oneTimePurchase" class="option-button inline"> + __MSG_oneTimePurchase__ + </div> + </a> + + <a href="https://www.patreon.com/ajayyy" class="option-link side-by-side" target="_blank" rel="noreferrer"> + <div class="option-button side-by-side inline"> + __MSG_joinOnPatreon__ + </div> + </a> + </div> + + <div class="center row-item"> + <input id="redeemCodeInput" class="option-text-box" type="text" placeholder="__MSG_enterLicenseKey__"> + <div id="redeemButton" class="option-button inline"> + __MSG_redeem__ + </div> + </div> + + <div class="center row-item"> + <a href="https://www.patreon.com/oauth2/authorize?response_type=code&client_id=-W7ib8J-LB3jowb1fqE07A7RDUovy45_pOoWcjby6yr5upo6At8Jlg2BPhWDXO2k&redirect_uri=https%3A%2F%2Fsponsor.ajay.app%3A3000%2Fapi%2FgenerateToken%2Fpatreon" + class="option-link" target="_blank" rel="noreferrer"> + <div class="option-button inline"> + __MSG_patreonSignIn__ + </div> + </a> + </div> + + <div id="cantAfford" class="center"> + + </div> + + <div class="center"> + __MSG_alreadyDonated__ [email protected] + </div> + + <div id="subsidizedPrice" class="center hidden"> + __MSG_selectYourCountry__ + </div> + + <div id="subsidizedLink" class="center hidden"> + <a href="https://buy.ajay.app/l/sponsorblock/purchasing-power" class="option-link" target="_blank" + rel="noreferrer"> + <div class="option-button inline"> + __MSG_discountLink__ + </div> + </a> + </div> + + <div id="noSubsidizedLink" class="center hidden"> + __MSG_noDiscount__ + </div> + +</body>
\ No newline at end of file diff --git a/public/upsell/styles.css b/public/upsell/styles.css new file mode 100644 index 00000000..0c0d6b6b --- /dev/null +++ b/public/upsell/styles.css @@ -0,0 +1,387 @@ +/* Based on options page CSS */ +html { + color-scheme: dark; +} + +body { + font-family: sans-serif; +} + +.center { + text-align: center; +} + +.center p { + margin: auto; +} + +.inline { + display: inline-block; +} + +.bold { + font-weight: bold; +} + +.hidden { + display: none !important; +} + +.row-item { + margin-top: 10px; + margin-bottom: 10px; +} + +.keybind-status { + display: inline; +} + +.small-description { + color: white; + font-size: 13px; +} + +.medium-description { + color: white; + font-size: 15px; +} + +.option-text-box { + width: 300px; +} + +.option-button { + cursor: pointer; + + background-color: #c00000; + padding: 10px; + color: white; + border-radius: 5px; + font-size: 14px; + + width: max-content; +} + +.option-link { + text-decoration: none; +} + +.option-link.side-by-side { + padding: 50px; +} + +.option-button:hover { + background-color: #fc0303; +} + +.option-button.disabled { + cursor: default; + + background-color: #520000; + color: grey; +} + +#options { + max-width: 60%; + text-align: left; + display: inline-block; +} + +.switch-container:after { + content: attr(label-name); + position: absolute; + padding: 4px; + width: max-content; + + font-size: 14px; + color: white; +} + +.text-label-container { + font-size: 14px; + color: white; +} + +.switch { + position: relative; + display: inline-block; + width: 40px; + height: 24px; +} + +.switch input { + opacity: 0; + width: 0; + height: 0; +} + +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #707070; +} + +.animated * { + -webkit-transition: .4s; + transition: .4s; +} + +.slider:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 4px; + bottom: 4px; + background-color: white; +} + +.animated .slider:before { + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider { + background-color: #fc0303; +} + +input:checked + .slider:before { + -webkit-transform: translateX(16px); + -ms-transform: translateX(16px); + transform: translateX(16px); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + +.slider.round:before { + border-radius: 50%; +} + + +/* Boilerplate CSS from https://ajay.app */ + +body { + background-color: #333333; +} + +.projectPreview { + position: relative; +} + +.projectPreviewImage { + position: absolute; + left: -90px; + width: 80px; + top: 50%; + transform: translateY(-50%); +} + +.projectPreviewImageLarge { + position: absolute; + left: -210px; + width: 200px; + top: 50%; + transform: translateY(-20%); +} + +.projectPreviewImageLargeRight { + position: absolute; + right: -210px; + width: 200px; + top: 50%; + transform: translateY(-50%); +} + +.createdBy { + font-size: 14px; + text-align: center; + padding-top: 0px; + padding-bottom: 0px; + + display: inline-block; +} + +#title { + background-color: #636363; + + text-align: center; + vertical-align: middle; + + font-size: 50px; + color: #212121; + + padding: 20px; + + text-decoration: none; + + transition: font-size 1s; +} + +.subtitle { + font-size: 40px; + color: #dad8d8; + + padding-top: 10px; + + transition: font-size 0.4s; +} + +.subtitle:hover { + font-size: 45px; + + transition: font-size 0.4s; +} + +.profilepic { + background-color: #636363 !important; + vertical-align: middle; +} + +.profilepiccircle { + vertical-align: middle; + overflow: hidden; + border-radius: 50%; +} + +a { + text-decoration: underline; + color: inherit; +} + +.link { + padding: 20px; + + height: 80px; + + transition: height 0.2s; +} + +.link:hover { + height: 95px; + + transition: height 0.2s; +} + +#contact,.smalllink { + font-size: 25px; + color: #e8e8e8; + + text-align: center; + + padding: 10px; +} + +#contact { + text-decoration: none; +} + +p,li { + font-size: 20px; + color: #c4c4c4; + + padding: 10px; +} + +p,li,code,a { + max-width: 60%; + text-align: left; + overflow-wrap: break-word; +} + +@media screen and (orientation:portrait) { + p,li,code,a { + max-width: 100%; + } + + .projectPreviewImage { + position: unset; + width: 130px; + display: block; + margin: auto; + transform: none; + } +} + +.previewImage { + max-height: 200px; +} + +img { + max-width: 100%; + + text-align: center; +} + +#recentPostTitle { + font-size: 30px; + color: #dad8d8; +} + +#recentPostDate { + font-size: 15px; + color: #dad8d8; +} + +h1,h2,h3,h4,h5,h6 { + color: #dad8d8; +} + +svg { + text-decoration: none; +} + +.number-container:before { + content: attr(label-name); + padding-right: 4px; + width: max-content; + + font-size: 14px; + color: white; +} + +/* React styles */ + +.categoryTableElement { + font-size: 16px; + + color: white; +} + +.categoryTableElement > * { + padding-right: 15px; + padding-bottom: 15px; +} + +.optionsSelector { + background-color: #c00000; + color: white; + + border: none; + font-size: 14px; + padding: 5px; + border-radius: 5px; +} + +.categoryColorTextBox { + width: 60px; + + background: none; + border: none; +} + +#subsidizedPrice { + margin-top: 5px; + margin-bottom: 5px; +} + +#discountButton { + text-decoration: underline; + cursor: pointer; +}
\ No newline at end of file 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 diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js index 78015cbf..73c17807 100644 --- a/webpack/webpack.common.js +++ b/webpack/webpack.common.js @@ -31,7 +31,8 @@ module.exports = env => ({ content: path.join(__dirname, srcDir + 'content.ts'), options: path.join(__dirname, srcDir + 'options.ts'), help: path.join(__dirname, srcDir + 'help.ts'), - permissions: path.join(__dirname, srcDir + 'permissions.ts') + permissions: path.join(__dirname, srcDir + 'permissions.ts'), + upsell: path.join(__dirname, srcDir + 'upsell.ts') }, output: { path: path.join(__dirname, '../dist/js'), |