Save downvotes and segment hides in local storage

This commit is contained in:
Ajay
2022-02-06 23:17:34 -05:00
parent 53d0ac8677
commit 4d60dec7f9
6 changed files with 119 additions and 36 deletions

View File

@@ -520,7 +520,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
const segmentTimes = Config.config.unsubmittedSegments[sponsorVideoID] || []; const segmentTimes = Config.config.unsubmittedSegments[sponsorVideoID] || [];
segmentTimes.push(sponsorTimesSubmitting); segmentTimes.push(sponsorTimesSubmitting);
Config.config.unsubmittedSegments[sponsorVideoID] = segmentTimes; Config.config.unsubmittedSegments[sponsorVideoID] = segmentTimes;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
this.props.contentContainer().sponsorTimesSubmitting.push(sponsorTimesSubmitting); this.props.contentContainer().sponsorTimesSubmitting.push(sponsorTimesSubmitting);
this.props.contentContainer().updatePreviewBar(); this.props.contentContainer().updatePreviewBar();

View File

@@ -510,7 +510,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
sponsorTimesSubmitting[this.props.index].actionType = this.getNextActionType(category, inputActionType); sponsorTimesSubmitting[this.props.index].actionType = this.getNextActionType(category, inputActionType);
Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID] = sponsorTimesSubmitting; Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID] = sponsorTimesSubmitting;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
this.props.contentContainer().updatePreviewBar(); this.props.contentContainer().updatePreviewBar();
@@ -561,7 +561,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
} else { } else {
delete Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID]; delete Config.config.unsubmittedSegments[this.props.contentContainer().sponsorVideoID];
} }
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
this.props.contentContainer().updatePreviewBar(); this.props.contentContainer().updatePreviewBar();

View File

@@ -1,6 +1,6 @@
import * as CompileConfig from "../config.json"; import * as CompileConfig from "../config.json";
import * as invidiousList from "../ci/invidiouslist.json"; import * as invidiousList from "../ci/invidiouslist.json";
import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind } from "./types"; import { Category, CategorySelection, CategorySkipOption, NoticeVisbilityMode, PreviewBarOption, SponsorTime, StorageChangesObject, UnEncodedSegmentTimes as UnencodedSegmentTimes, Keybind, HashedValue, VideoID, SponsorHideType } from "./types";
import { keybindEquals } from "./utils/configUtils"; import { keybindEquals } from "./utils/configUtils";
interface SBConfig { interface SBConfig {
@@ -96,17 +96,23 @@ interface SBConfig {
} }
} }
export type VideoDownvotes = { segments: { uuid: HashedValue, hidden: SponsorHideType }[] , lastAccess: number };
interface SBStorage { interface SBStorage {
/* VideoID prefixes to UUID prefixes */
downvotedSegments: Record<VideoID & HashedValue, VideoDownvotes>,
} }
export interface SBObject { export interface SBObject {
configSyncListeners: Array<(changes: StorageChangesObject) => unknown>; configSyncListeners: Array<(changes: StorageChangesObject) => unknown>;
defaults: SBConfig; syncDefaults: SBConfig;
localDefaults: SBStorage;
cachedSyncConfig: SBConfig; cachedSyncConfig: SBConfig;
cachedLocalStorage: SBStorage; cachedLocalStorage: SBStorage;
config: SBConfig; config: SBConfig;
local: SBStorage; local: SBStorage;
forceUpdate(prop: string): void; forceSyncUpdate(prop: string): void;
forceLocalUpdate(prop: string): void;
} }
const Config: SBObject = { const Config: SBObject = {
@@ -114,7 +120,7 @@ const Config: SBObject = {
* Callback function when an option is updated * Callback function when an option is updated
*/ */
configSyncListeners: [], configSyncListeners: [],
defaults: { syncDefaults: {
userID: null, userID: null,
isVip: false, isVip: false,
lastIsVipUpdate: 0, lastIsVipUpdate: 0,
@@ -275,11 +281,15 @@ const Config: SBObject = {
} }
} }
}, },
localDefaults: {
downvotedSegments: {}
},
cachedSyncConfig: null, cachedSyncConfig: null,
cachedLocalStorage: null, cachedLocalStorage: null,
config: null, config: null,
local: null, local: null,
forceUpdate forceSyncUpdate,
forceLocalUpdate
}; };
// Function setup // Function setup
@@ -343,7 +353,7 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
return obj[prop] || data; return obj[prop] || data;
}, },
deleteProperty(obj: SBConfig, prop: keyof SBStorage) { deleteProperty(obj: SBStorage, prop: keyof SBStorage) {
chrome.storage.local.remove(<string> prop); chrome.storage.local.remove(<string> prop);
return true; return true;
@@ -352,24 +362,35 @@ function configProxy(): { sync: SBConfig, local: SBStorage } {
}; };
return { return {
sync: new Proxy<SBConfig>({handler: syncHandler} as unknown as SBConfig, syncHandler), sync: new Proxy<SBConfig>({ handler: syncHandler } as unknown as SBConfig, syncHandler),
local: new Proxy<SBStorage>({handler: localHandler} as unknown as SBConfig, localHandler) local: new Proxy<SBStorage>({ handler: localHandler } as unknown as SBStorage, localHandler)
}; };
} }
function forceUpdate(prop: string): void { function forceSyncUpdate(prop: string): void {
chrome.storage.sync.set({ chrome.storage.sync.set({
[prop]: Config.cachedSyncConfig[prop] [prop]: Config.cachedSyncConfig[prop]
}); });
} }
function fetchConfig(): Promise<void> { function forceLocalUpdate(prop: string): void {
return new Promise((resolve) => { chrome.storage.local.set({
[prop]: Config.cachedLocalStorage[prop]
});
}
async function fetchConfig(): Promise<void> {
await Promise.all([new Promise<void>((resolve) => {
chrome.storage.sync.get(null, function(items) { chrome.storage.sync.get(null, function(items) {
Config.cachedSyncConfig = <SBConfig> <unknown> items; // Data is ready Config.cachedSyncConfig = <SBConfig> <unknown> items;
resolve(); resolve();
}); });
}), new Promise<void>((resolve) => {
chrome.storage.local.get(null, function(items) {
Config.cachedLocalStorage = <SBStorage> <unknown> items;
resolve();
}); });
})]);
} }
function migrateOldSyncFormats(config: SBConfig) { function migrateOldSyncFormats(config: SBConfig) {
@@ -477,17 +498,23 @@ async function setupConfig() {
// Add defaults // Add defaults
function addDefaults() { function addDefaults() {
for (const key in Config.defaults) { for (const key in Config.syncDefaults) {
if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig, key)) { if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig, key)) {
Config.cachedSyncConfig[key] = Config.defaults[key]; Config.cachedSyncConfig[key] = Config.syncDefaults[key];
} else if (key === "barTypes") { } else if (key === "barTypes") {
for (const key2 in Config.defaults[key]) { for (const key2 in Config.syncDefaults[key]) {
if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig[key], key2)) { if(!Object.prototype.hasOwnProperty.call(Config.cachedSyncConfig[key], key2)) {
Config.cachedSyncConfig[key][key2] = Config.defaults[key][key2]; Config.cachedSyncConfig[key][key2] = Config.syncDefaults[key][key2];
} }
} }
} }
} }
for (const key in Config.localDefaults) {
if(!Object.prototype.hasOwnProperty.call(Config.cachedLocalStorage, key)) {
Config.cachedLocalStorage[key] = Config.localDefaults[key];
}
}
} }
// Sync config // Sync config

View File

@@ -1,5 +1,5 @@
import Config from "./config"; import Config from "./config";
import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus, SponsorSourceType, SegmentUUID, Category, SkipToTimeParams, ToggleSkippable, ActionType, ScheduledTime } from "./types"; import { SponsorTime, CategorySkipOption, VideoID, SponsorHideType, VideoInfo, StorageChangesObject, ChannelIDInfo, ChannelIDStatus, SponsorSourceType, SegmentUUID, Category, SkipToTimeParams, ToggleSkippable, ActionType, ScheduledTime, HashedValue } from "./types";
import { ContentContainer, Keybind } from "./types"; import { ContentContainer, Keybind } from "./types";
import Utils from "./utils"; import Utils from "./utils";
@@ -209,6 +209,7 @@ function messageListener(request: Message, sender: unknown, sendResponse: (respo
return true; return true;
case "hideSegment": case "hideSegment":
utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID).hidden = request.type; utils.getSponsorTimeFromUUID(sponsorTimes, request.UUID).hidden = request.type;
utils.addHiddenSegment(sponsorVideoID, request.UUID, request.type);
updatePreviewBar(); updatePreviewBar();
break; break;
@@ -698,7 +699,7 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment; if (hashParams.requiredSegment) extraRequestData.requiredSegment = hashParams.requiredSegment;
// Check for hashPrefix setting // Check for hashPrefix setting
const hashPrefix = (await utils.getHash(id, 1)).slice(0, 4); const hashPrefix = (await utils.getHash(id, 1)).slice(0, 4) as VideoID & HashedValue;
const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, { const response = await utils.asyncRequestToServer('GET', "/api/skipSegments/" + hashPrefix, {
categories, categories,
actionTypes: getEnabledActionTypes(), actionTypes: getEnabledActionTypes(),
@@ -752,6 +753,18 @@ async function sponsorsLookup(id: string, keepOldSubmissions = true) {
} }
} }
// See if some segments should be hidden
const downvotedData = Config.local.downvotedSegments[hashPrefix];
if (downvotedData) {
for (const segment of sponsorTimes) {
const hashedUUID = await utils.getHash(segment.UUID, 1);
const segmentDownvoteData = downvotedData.segments.find((downvote) => downvote.uuid === hashedUUID);
if (segmentDownvoteData) {
segment.hidden = segmentDownvoteData.hidden;
}
}
}
startSkipScheduleCheckingForStartSponsors(); startSkipScheduleCheckingForStartSponsors();
//update the preview bar //update the preview bar
@@ -1516,7 +1529,7 @@ function startOrEndTimingNewSegment() {
// Save the newly created segment // Save the newly created segment
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting; Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
// Make sure they know if someone has already submitted something it while they were watching // Make sure they know if someone has already submitted something it while they were watching
sponsorsLookup(sponsorVideoID); sponsorsLookup(sponsorVideoID);
@@ -1539,7 +1552,7 @@ function cancelCreatingSegment() {
if (isSegmentCreationInProgress()) { if (isSegmentCreationInProgress()) {
sponsorTimesSubmitting.splice(sponsorTimesSubmitting.length - 1, 1); sponsorTimesSubmitting.splice(sponsorTimesSubmitting.length - 1, 1);
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting; Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
if (sponsorTimesSubmitting.length <= 0) resetSponsorSubmissionNotice(); if (sponsorTimesSubmitting.length <= 0) resetSponsorSubmissionNotice();
} }
@@ -1689,7 +1702,7 @@ function clearSponsorTimes() {
//clear the sponsor times //clear the sponsor times
delete Config.config.unsubmittedSegments[currentVideoID]; delete Config.config.unsubmittedSegments[currentVideoID];
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
//clear sponsor times submitting //clear sponsor times submitting
sponsorTimesSubmitting = []; sponsorTimesSubmitting = [];
@@ -1772,6 +1785,10 @@ async function voteAsync(type: number, UUID: SegmentUUID, category?: Category):
segment.hidden = SponsorHideType.Visible; segment.hidden = SponsorHideType.Visible;
} }
if (!category) {
utils.addHiddenSegment(sponsorVideoID, segment.UUID, segment.hidden);
}
updatePreviewBar(); updatePreviewBar();
} }
} }
@@ -1837,7 +1854,7 @@ async function sendSubmitMessage() {
//update sponsorTimes //update sponsorTimes
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting; Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
// Check to see if any of the submissions are below the minimum duration set // Check to see if any of the submissions are below the minimum duration set
if (Config.config.minDuration > 0) { if (Config.config.minDuration > 0) {
@@ -1865,7 +1882,7 @@ async function sendSubmitMessage() {
// Remove segments from storage since they've already been submitted // Remove segments from storage since they've already been submitted
delete Config.config.unsubmittedSegments[sponsorVideoID]; delete Config.config.unsubmittedSegments[sponsorVideoID];
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
const newSegments = sponsorTimesSubmitting; const newSegments = sponsorTimesSubmitting;
try { try {
@@ -1972,12 +1989,12 @@ function hotkeyListener(e: KeyboardEvent): void {
} }
//legacy - to preserve keybinds for skipKey, startSponsorKey and submitKey for people who set it before the update. (shouldn't be changed for future keybind options) //legacy - to preserve keybinds for skipKey, startSponsorKey and submitKey for people who set it before the update. (shouldn't be changed for future keybind options)
if (key.key == skipKey?.key && skipKey.code == null && !keybindEquals(Config.defaults.skipKeybind, skipKey)) { if (key.key == skipKey?.key && skipKey.code == null && !keybindEquals(Config.syncDefaults.skipKeybind, skipKey)) {
if (activeSkipKeybindElement) if (activeSkipKeybindElement)
activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement); activeSkipKeybindElement.toggleSkip.call(activeSkipKeybindElement);
} else if (key.key == startSponsorKey?.key && startSponsorKey.code == null && !keybindEquals(Config.defaults.startSponsorKeybind, startSponsorKey)) { } else if (key.key == startSponsorKey?.key && startSponsorKey.code == null && !keybindEquals(Config.syncDefaults.startSponsorKeybind, startSponsorKey)) {
startOrEndTimingNewSegment(); startOrEndTimingNewSegment();
} else if (key.key == submitKey?.key && submitKey.code == null && !keybindEquals(Config.defaults.submitKeybind, submitKey)) { } else if (key.key == submitKey?.key && submitKey.code == null && !keybindEquals(Config.syncDefaults.submitKeybind, submitKey)) {
submitSponsorTimes(); submitSponsorTimes();
} }
} }
@@ -2090,6 +2107,6 @@ function checkForPreloadedSegment() {
if (pushed) { if (pushed) {
Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting; Config.config.unsubmittedSegments[sponsorVideoID] = sponsorTimesSubmitting;
Config.forceUpdate("unsubmittedSegments"); Config.forceSyncUpdate("unsubmittedSegments");
} }
} }

View File

@@ -195,7 +195,7 @@ async function init() {
textChangeResetButton.addEventListener("click", () => { textChangeResetButton.addEventListener("click", () => {
if (!confirm(chrome.i18n.getMessage("areYouSureReset"))) return; if (!confirm(chrome.i18n.getMessage("areYouSureReset"))) return;
Config.config[option] = Config.defaults[option]; Config.config[option] = Config.syncDefaults[option];
textChangeInput.value = Config.config[option]; textChangeInput.value = Config.config[option];
}); });
@@ -247,7 +247,7 @@ async function init() {
const numberInput = optionsElements[i].querySelector("input"); const numberInput = optionsElements[i].querySelector("input");
if (isNaN(configValue) || configValue < 0) { if (isNaN(configValue) || configValue < 0) {
numberInput.value = Config.defaults[option]; numberInput.value = Config.syncDefaults[option];
} else { } else {
numberInput.value = configValue; numberInput.value = configValue;
} }

View File

@@ -1,5 +1,5 @@
import Config from "./config"; import Config, { VideoDownvotes } from "./config";
import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue } from "./types"; import { CategorySelection, SponsorTime, FetchResponse, BackgroundScriptContainer, Registration, HashedValue, VideoID, SponsorHideType } from "./types";
import * as CompileConfig from "../config.json"; import * as CompileConfig from "../config.json";
import { findValidElementFromSelector } from "./utils/pageUtils"; import { findValidElementFromSelector } from "./utils/pageUtils";
@@ -488,4 +488,43 @@ export default class Utils {
return hashHex as T & HashedValue; return hashHex as T & HashedValue;
} }
async addHiddenSegment(videoID: VideoID, segmentUUID: string, hidden: SponsorHideType) {
const hashedVideoID = (await this.getHash(videoID, 1)).slice(0, 4) as VideoID & HashedValue;
const UUIDHash = await this.getHash(segmentUUID, 1);
const allDownvotes = Config.local.downvotedSegments;
const currentVideoData = allDownvotes[hashedVideoID] || { segments: [], lastAccess: 0 };
currentVideoData.lastAccess = Date.now();
const existingData = currentVideoData.segments.find((segment) => segment.uuid === UUIDHash);
if (hidden === SponsorHideType.Visible) {
delete allDownvotes[hashedVideoID];
} else {
if (existingData) {
existingData.hidden = hidden;
} else {
currentVideoData.segments.push({
uuid: UUIDHash,
hidden
});
}
allDownvotes[hashedVideoID] = currentVideoData;
}
const entries = Object.entries(allDownvotes);
if (entries.length > 10000) {
let min: [string, VideoDownvotes] = null;
for (let i = 0; i < entries[0].length; i++) {
if (min === null || entries[i][1].lastAccess < min[1].lastAccess) {
min = entries[i];
}
}
delete allDownvotes[min[0]];
}
Config.forceLocalUpdate("downvotedSegments");
}
} }