mirror of
https://github.com/ajayyy/SponsorBlock.git
synced 2025-12-07 03:57:09 +03:00
Add functions for importing/exporting segments
This commit is contained in:
@@ -8,6 +8,7 @@ import { Registration } from "./types";
|
|||||||
window.SB = Config;
|
window.SB = Config;
|
||||||
|
|
||||||
import Utils from "./utils";
|
import Utils from "./utils";
|
||||||
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
const utils = new Utils({
|
const utils = new Utils({
|
||||||
registerFirefoxContentScript,
|
registerFirefoxContentScript,
|
||||||
unregisterFirefoxContentScript
|
unregisterFirefoxContentScript
|
||||||
@@ -77,7 +78,7 @@ chrome.runtime.onInstalled.addListener(function () {
|
|||||||
chrome.tabs.create({url: chrome.extension.getURL("/help/index.html")});
|
chrome.tabs.create({url: chrome.extension.getURL("/help/index.html")});
|
||||||
|
|
||||||
//generate a userID
|
//generate a userID
|
||||||
const newUserID = utils.generateUserID();
|
const newUserID = GenericUtils.generateUserID();
|
||||||
//save this UUID
|
//save this UUID
|
||||||
Config.config.userID = newUserID;
|
Config.config.userID = newUserID;
|
||||||
|
|
||||||
@@ -120,7 +121,7 @@ async function submitVote(type: number, UUID: string, category: string) {
|
|||||||
|
|
||||||
if (userID == undefined || userID === "undefined") {
|
if (userID == undefined || userID === "undefined") {
|
||||||
//generate one
|
//generate one
|
||||||
userID = utils.generateUserID();
|
userID = GenericUtils.generateUserID();
|
||||||
Config.config.userID = userID;
|
Config.config.userID = userID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import ThumbsUpSvg from "../svg-icons/thumbs_up_svg";
|
|||||||
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
import ThumbsDownSvg from "../svg-icons/thumbs_down_svg";
|
||||||
import PencilSvg from "../svg-icons/pencil_svg";
|
import PencilSvg from "../svg-icons/pencil_svg";
|
||||||
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
import { downvoteButtonColor, SkipNoticeAction } from "../utils/noticeUtils";
|
||||||
|
import { GenericUtils } from "../utils/genericUtils";
|
||||||
|
|
||||||
export interface SkipNoticeProps {
|
export interface SkipNoticeProps {
|
||||||
segments: SponsorTime[];
|
segments: SponsorTime[];
|
||||||
@@ -511,7 +512,7 @@ class SkipNoticeComponent extends React.Component<SkipNoticeProps, SkipNoticeSta
|
|||||||
const sponsorVideoID = this.props.contentContainer().sponsorVideoID;
|
const sponsorVideoID = this.props.contentContainer().sponsorVideoID;
|
||||||
const sponsorTimesSubmitting : SponsorTime = {
|
const sponsorTimesSubmitting : SponsorTime = {
|
||||||
segment: this.segments[index].segment,
|
segment: this.segments[index].segment,
|
||||||
UUID: utils.generateUserID() as SegmentUUID,
|
UUID: GenericUtils.generateUserID() as SegmentUUID,
|
||||||
category: this.segments[index].category,
|
category: this.segments[index].category,
|
||||||
actionType: this.segments[index].actionType,
|
actionType: this.segments[index].actionType,
|
||||||
source: SponsorSourceType.Local
|
source: SponsorSourceType.Local
|
||||||
|
|||||||
@@ -180,9 +180,9 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
style={timeDisplayStyle}
|
style={timeDisplayStyle}
|
||||||
className="sponsorTimeDisplay"
|
className="sponsorTimeDisplay"
|
||||||
onClick={this.toggleEditTime.bind(this)}>
|
onClick={this.toggleEditTime.bind(this)}>
|
||||||
{utils.getFormattedTime(segment[0], true) +
|
{GenericUtils.getFormattedTime(segment[0], true) +
|
||||||
((!isNaN(segment[1]) && sponsorTime.actionType !== ActionType.Poi)
|
((!isNaN(segment[1]) && sponsorTime.actionType !== ActionType.Poi)
|
||||||
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segment[1], true) : "")}
|
? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(segment[1], true) : "")}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -324,7 +324,7 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
timeAsNumber = 0;
|
timeAsNumber = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
sponsorTimeEdits[index] = utils.getFormattedTime(timeAsNumber, true);
|
sponsorTimeEdits[index] = GenericUtils.getFormattedTime(timeAsNumber, true);
|
||||||
if (sponsorTime.actionType === ActionType.Poi) sponsorTimeEdits[1] = sponsorTimeEdits[0];
|
if (sponsorTime.actionType === ActionType.Poi) sponsorTimeEdits[1] = sponsorTimeEdits[0];
|
||||||
|
|
||||||
this.setState({sponsorTimeEdits});
|
this.setState({sponsorTimeEdits});
|
||||||
@@ -523,8 +523,8 @@ class SponsorTimeEditComponent extends React.Component<SponsorTimeEditProps, Spo
|
|||||||
|
|
||||||
/** Returns an array in the sponsorTimeEdits form (formatted time string) from a normal seconds sponsor time */
|
/** Returns an array in the sponsorTimeEdits form (formatted time string) from a normal seconds sponsor time */
|
||||||
getFormattedSponsorTimesEdits(sponsorTime: SponsorTime): [string, string] {
|
getFormattedSponsorTimesEdits(sponsorTime: SponsorTime): [string, string] {
|
||||||
return [utils.getFormattedTime(sponsorTime.segment[0], true),
|
return [GenericUtils.getFormattedTime(sponsorTime.segment[0], true),
|
||||||
utils.getFormattedTime(sponsorTime.segment[1], true)];
|
GenericUtils.getFormattedTime(sponsorTime.segment[1], true)];
|
||||||
}
|
}
|
||||||
|
|
||||||
saveEditTimes(): void {
|
saveEditTimes(): void {
|
||||||
|
|||||||
@@ -1568,7 +1568,7 @@ function startOrEndTimingNewSegment() {
|
|||||||
if (!isSegmentCreationInProgress()) {
|
if (!isSegmentCreationInProgress()) {
|
||||||
sponsorTimesSubmitting.push({
|
sponsorTimesSubmitting.push({
|
||||||
segment: [roundedTime],
|
segment: [roundedTime],
|
||||||
UUID: utils.generateUserID() as SegmentUUID,
|
UUID: GenericUtils.generateUserID() as SegmentUUID,
|
||||||
category: Config.config.defaultCategory,
|
category: Config.config.defaultCategory,
|
||||||
actionType: ActionType.Skip,
|
actionType: ActionType.Skip,
|
||||||
source: SponsorSourceType.Local
|
source: SponsorSourceType.Local
|
||||||
@@ -2000,7 +2000,7 @@ function getSegmentsMessage(sponsorTimes: SponsorTime[]): string {
|
|||||||
|
|
||||||
for (let i = 0; i < sponsorTimes.length; i++) {
|
for (let i = 0; i < sponsorTimes.length; i++) {
|
||||||
for (let s = 0; s < sponsorTimes[i].segment.length; s++) {
|
for (let s = 0; s < sponsorTimes[i].segment.length; s++) {
|
||||||
let timeMessage = utils.getFormattedTime(sponsorTimes[i].segment[s]);
|
let timeMessage = GenericUtils.getFormattedTime(sponsorTimes[i].segment[s]);
|
||||||
//if this is an end time
|
//if this is an end time
|
||||||
if (s == 1) {
|
if (s == 1) {
|
||||||
timeMessage = " " + chrome.i18n.getMessage("to") + " " + timeMessage;
|
timeMessage = " " + chrome.i18n.getMessage("to") + " " + timeMessage;
|
||||||
@@ -2147,7 +2147,7 @@ function showTimeWithoutSkips(skippedDuration: number): void {
|
|||||||
display.appendChild(duration);
|
display.appendChild(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
const durationAfterSkips = utils.getFormattedTime(video?.duration - skippedDuration)
|
const durationAfterSkips = GenericUtils.getFormattedTime(video?.duration - skippedDuration)
|
||||||
|
|
||||||
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
|
duration.innerText = (durationAfterSkips == null || skippedDuration <= 0) ? "" : " (" + durationAfterSkips + ")";
|
||||||
}
|
}
|
||||||
@@ -2166,7 +2166,7 @@ function checkForPreloadedSegment() {
|
|||||||
if (!sponsorTimesSubmitting.some((s) => s.segment[0] === segment.segment[0] && s.segment[1] === s.segment[1])) {
|
if (!sponsorTimesSubmitting.some((s) => s.segment[0] === segment.segment[0] && s.segment[1] === s.segment[1])) {
|
||||||
sponsorTimesSubmitting.push({
|
sponsorTimesSubmitting.push({
|
||||||
segment: segment.segment,
|
segment: segment.segment,
|
||||||
UUID: utils.generateUserID() as SegmentUUID,
|
UUID: GenericUtils.generateUserID() as SegmentUUID,
|
||||||
category: segment.category ? segment.category : Config.config.defaultCategory,
|
category: segment.category ? segment.category : Config.config.defaultCategory,
|
||||||
actionType: segment.actionType ? segment.actionType : ActionType.Skip,
|
actionType: segment.actionType ? segment.actionType : ActionType.Skip,
|
||||||
source: SponsorSourceType.Local
|
source: SponsorSourceType.Local
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import Config from "../config";
|
|||||||
import { ActionType, Category, SegmentContainer, SponsorSourceType, SponsorTime } from "../types";
|
import { ActionType, Category, SegmentContainer, SponsorSourceType, SponsorTime } from "../types";
|
||||||
import Utils from "../utils";
|
import Utils from "../utils";
|
||||||
import { partition } from "../utils/arrayUtils";
|
import { partition } from "../utils/arrayUtils";
|
||||||
|
import { shortCategoryName } from "../utils/categoryUtils";
|
||||||
import { GenericUtils } from "../utils/genericUtils";
|
import { GenericUtils } from "../utils/genericUtils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
@@ -154,7 +155,7 @@ class PreviewBar {
|
|||||||
|
|
||||||
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
|
private setTooltipTitle(segment: PreviewBarSegment, tooltip: HTMLElement): void {
|
||||||
if (segment) {
|
if (segment) {
|
||||||
const name = segment.description || utils.shortCategoryName(segment.category);
|
const name = segment.description || shortCategoryName(segment.category);
|
||||||
if (segment.unsubmitted) {
|
if (segment.unsubmitted) {
|
||||||
tooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name;
|
tooltip.textContent = chrome.i18n.getMessage("unsubmitted") + " " + name;
|
||||||
} else {
|
} else {
|
||||||
@@ -603,7 +604,7 @@ class PreviewBar {
|
|||||||
chapterButton.disabled = false;
|
chapterButton.disabled = false;
|
||||||
|
|
||||||
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
const chapterTitle = chaptersContainer.querySelector(".ytp-chapter-title-content") as HTMLDivElement;
|
||||||
chapterTitle.innerText = chosenSegment.description || utils.shortCategoryName(chosenSegment.category);
|
chapterTitle.innerText = chosenSegment.description || shortCategoryName(chosenSegment.category);
|
||||||
} else {
|
} else {
|
||||||
// Hide chapters menu again
|
// Hide chapters menu again
|
||||||
chaptersContainer.style.display = "none";
|
chaptersContainer.style.display = "none";
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { Message, MessageResponse, IsInfoFoundMessageResponse } from "./messageT
|
|||||||
import { showDonationLink } from "./utils/configUtils";
|
import { showDonationLink } from "./utils/configUtils";
|
||||||
import { AnimationUtils } from "./utils/animationUtils";
|
import { AnimationUtils } from "./utils/animationUtils";
|
||||||
import { GenericUtils } from "./utils/genericUtils";
|
import { GenericUtils } from "./utils/genericUtils";
|
||||||
|
import { shortCategoryName } from "./utils/categoryUtils";
|
||||||
const utils = new Utils();
|
const utils = new Utils();
|
||||||
|
|
||||||
interface MessageListener {
|
interface MessageListener {
|
||||||
@@ -474,15 +475,15 @@ async function runThePopup(messageListener?: MessageListener): Promise<void> {
|
|||||||
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
|
extraInfo = " (" + chrome.i18n.getMessage("manuallyHidden") + ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = segmentTimes[i].description || utils.shortCategoryName(category);
|
const name = segmentTimes[i].description || shortCategoryName(category);
|
||||||
const textNode = document.createTextNode(name + extraInfo);
|
const textNode = document.createTextNode(name + extraInfo);
|
||||||
const segmentTimeFromToNode = document.createElement("div");
|
const segmentTimeFromToNode = document.createElement("div");
|
||||||
if (segmentTimes[i].actionType === ActionType.Full) {
|
if (segmentTimes[i].actionType === ActionType.Full) {
|
||||||
segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full");
|
segmentTimeFromToNode.innerText = chrome.i18n.getMessage("full");
|
||||||
} else {
|
} else {
|
||||||
segmentTimeFromToNode.innerText = utils.getFormattedTime(segmentTimes[i].segment[0], true) +
|
segmentTimeFromToNode.innerText = GenericUtils.getFormattedTime(segmentTimes[i].segment[0], true) +
|
||||||
(actionType !== ActionType.Poi
|
(actionType !== ActionType.Poi
|
||||||
? " " + chrome.i18n.getMessage("to") + " " + utils.getFormattedTime(segmentTimes[i].segment[1], true)
|
? " " + chrome.i18n.getMessage("to") + " " + GenericUtils.getFormattedTime(segmentTimes[i].segment[1], true)
|
||||||
: "");
|
: "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
52
src/utils.ts
52
src/utils.ts
@@ -298,24 +298,6 @@ export default class Utils {
|
|||||||
return permissionRegex;
|
return permissionRegex;
|
||||||
}
|
}
|
||||||
|
|
||||||
generateUserID(length = 36): string {
|
|
||||||
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
||||||
let result = "";
|
|
||||||
if (window.crypto && window.crypto.getRandomValues) {
|
|
||||||
const values = new Uint32Array(length);
|
|
||||||
window.crypto.getRandomValues(values);
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += charset[values[i] % charset.length];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < length; i++) {
|
|
||||||
result += charset[Math.floor(Math.random() * charset.length)];
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends a request to a custom server
|
* Sends a request to a custom server
|
||||||
*
|
*
|
||||||
@@ -412,40 +394,6 @@ export default class Utils {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormattedTime(seconds: number, precise?: boolean): string {
|
|
||||||
seconds = Math.max(seconds, 0);
|
|
||||||
|
|
||||||
const hours = Math.floor(seconds / 60 / 60);
|
|
||||||
const minutes = Math.floor(seconds / 60) % 60;
|
|
||||||
let minutesDisplay = String(minutes);
|
|
||||||
let secondsNum = seconds % 60;
|
|
||||||
if (!precise) {
|
|
||||||
secondsNum = Math.floor(secondsNum);
|
|
||||||
}
|
|
||||||
|
|
||||||
let secondsDisplay = String(precise ? secondsNum.toFixed(3) : secondsNum);
|
|
||||||
|
|
||||||
if (secondsNum < 10) {
|
|
||||||
//add a zero
|
|
||||||
secondsDisplay = "0" + secondsDisplay;
|
|
||||||
}
|
|
||||||
if (hours && minutes < 10) {
|
|
||||||
//add a zero
|
|
||||||
minutesDisplay = "0" + minutesDisplay;
|
|
||||||
}
|
|
||||||
if (isNaN(hours) || isNaN(minutes)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatted = (hours ? hours + ":" : "") + minutesDisplay + ":" + secondsDisplay;
|
|
||||||
|
|
||||||
return formatted;
|
|
||||||
}
|
|
||||||
|
|
||||||
shortCategoryName(categoryName: string): string {
|
|
||||||
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
|
|
||||||
}
|
|
||||||
|
|
||||||
isContentScript(): boolean {
|
isContentScript(): boolean {
|
||||||
return window.location.protocol === "http:" || window.location.protocol === "https:";
|
return window.location.protocol === "http:" || window.location.protocol === "https:";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,3 +45,7 @@ export function getCategorySuffix(category: Category): string {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function shortCategoryName(categoryName: string): string {
|
||||||
|
return chrome.i18n.getMessage("category_" + categoryName + "_short") || chrome.i18n.getMessage("category_" + categoryName);
|
||||||
|
}
|
||||||
65
src/utils/exporter.ts
Normal file
65
src/utils/exporter.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from "../types";
|
||||||
|
import { shortCategoryName } from "./categoryUtils";
|
||||||
|
import { GenericUtils } from "./genericUtils";
|
||||||
|
|
||||||
|
export function exportTimes(segments: SponsorTime[]): string {
|
||||||
|
let result = "";
|
||||||
|
for (const segment of segments) {
|
||||||
|
if (![ActionType.Full, ActionType.Mute].includes(segment.actionType)
|
||||||
|
&& segment.source !== SponsorSourceType.YouTube) {
|
||||||
|
result += exportTime(segment) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function exportTime(segment: SponsorTime): string {
|
||||||
|
const name = segment.description || shortCategoryName(segment.category);
|
||||||
|
|
||||||
|
return `${GenericUtils.getFormattedTime(segment.segment[0])}${
|
||||||
|
segment.segment[1] && segment.segment[0] !== segment.segment[1]
|
||||||
|
? ` - ${GenericUtils.getFormattedTime(segment.segment[1])}` : ""} ${name}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function importTimes(data: string, videoDuration: number): SponsorTime[] {
|
||||||
|
const lines = data.split("\n");
|
||||||
|
const result: SponsorTime[] = [];
|
||||||
|
for (const line of lines) {
|
||||||
|
const match = line.match(/(?:(\d+:\d+)+(?:\.\d+)?)|(?:\d+(?=s| second))/g);
|
||||||
|
if (match) {
|
||||||
|
const startTime = GenericUtils.getFormattedTimeToSeconds(match[0]);
|
||||||
|
if (startTime) {
|
||||||
|
const specialCharsMatcher = /^(?:\s+seconds?)?[:()-\s]*|(?:\s+at)?[:()-\s]+$/g
|
||||||
|
const titleLeft = line.split(match[0])[0].replace(specialCharsMatcher, "");
|
||||||
|
let titleRight = null;
|
||||||
|
const split2 = line.split(match[1] || match[0]);
|
||||||
|
titleRight = split2[split2.length - 1].replace(specialCharsMatcher, "");
|
||||||
|
|
||||||
|
const title = titleLeft?.length > titleRight?.length ? titleLeft : titleRight;
|
||||||
|
if (title) {
|
||||||
|
const segment: SponsorTime = {
|
||||||
|
segment: [startTime, GenericUtils.getFormattedTimeToSeconds(match[1])],
|
||||||
|
category: "chapter" as Category,
|
||||||
|
actionType: ActionType.Chapter,
|
||||||
|
description: title,
|
||||||
|
source: SponsorSourceType.Local,
|
||||||
|
UUID: GenericUtils.generateUserID() as SegmentUUID
|
||||||
|
};
|
||||||
|
|
||||||
|
if (result.length > 0 && result[result.length - 1].segment[1] === null) {
|
||||||
|
result[result.length - 1].segment[1] = segment.segment[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.length > 0 && result[result.length - 1].segment[1] === null) {
|
||||||
|
result[result.length - 1].segment[1] = videoDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
@@ -35,6 +35,36 @@ function getFormattedTimeToSeconds(formatted: string): number | null {
|
|||||||
return hours * 3600 + minutes * 60 + seconds;
|
return hours * 3600 + minutes * 60 + seconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getFormattedTime(seconds: number, precise?: boolean): string {
|
||||||
|
seconds = Math.max(seconds, 0);
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 60 / 60);
|
||||||
|
const minutes = Math.floor(seconds / 60) % 60;
|
||||||
|
let minutesDisplay = String(minutes);
|
||||||
|
let secondsNum = seconds % 60;
|
||||||
|
if (!precise) {
|
||||||
|
secondsNum = Math.floor(secondsNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
let secondsDisplay = String(precise ? secondsNum.toFixed(3) : secondsNum);
|
||||||
|
|
||||||
|
if (secondsNum < 10) {
|
||||||
|
//add a zero
|
||||||
|
secondsDisplay = "0" + secondsDisplay;
|
||||||
|
}
|
||||||
|
if (hours && minutes < 10) {
|
||||||
|
//add a zero
|
||||||
|
minutesDisplay = "0" + minutesDisplay;
|
||||||
|
}
|
||||||
|
if (isNaN(hours) || isNaN(minutes)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatted = (hours ? hours + ":" : "") + minutesDisplay + ":" + secondsDisplay;
|
||||||
|
|
||||||
|
return formatted;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the error message in a nice string
|
* Gets the error message in a nice string
|
||||||
*
|
*
|
||||||
@@ -80,9 +110,29 @@ function hexToRgb(hex: string): {r: number, g: number, b: number} {
|
|||||||
} : null;
|
} : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generateUserID(length = 36): string {
|
||||||
|
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
let result = "";
|
||||||
|
if (window.crypto && window.crypto.getRandomValues) {
|
||||||
|
const values = new Uint32Array(length);
|
||||||
|
window.crypto.getRandomValues(values);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += charset[values[i] % charset.length];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result += charset[Math.floor(Math.random() * charset.length)];
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const GenericUtils = {
|
export const GenericUtils = {
|
||||||
wait,
|
wait,
|
||||||
|
getFormattedTime,
|
||||||
getFormattedTimeToSeconds,
|
getFormattedTimeToSeconds,
|
||||||
getErrorMessage,
|
getErrorMessage,
|
||||||
getLuminance
|
getLuminance,
|
||||||
|
generateUserID
|
||||||
}
|
}
|
||||||
241
test/extractor.test.ts
Normal file
241
test/extractor.test.ts
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
/**
|
||||||
|
* @jest-environment jsdom
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ActionType, Category, SegmentUUID, SponsorSourceType, SponsorTime } from "../src/types";
|
||||||
|
import { exportTimes, importTimes } from "../src/utils/exporter";
|
||||||
|
|
||||||
|
describe("Export segments", () => {
|
||||||
|
it("Some segments", () => {
|
||||||
|
const segments: SponsorTime[] = [{
|
||||||
|
segment: [0, 10],
|
||||||
|
category: "chapter" as Category,
|
||||||
|
actionType: ActionType.Chapter,
|
||||||
|
description: "Chapter 1",
|
||||||
|
source: SponsorSourceType.Server,
|
||||||
|
UUID: "1" as SegmentUUID
|
||||||
|
}, {
|
||||||
|
segment: [20, 20],
|
||||||
|
category: "poi_highlight" as Category,
|
||||||
|
actionType: ActionType.Poi,
|
||||||
|
description: "Highlight",
|
||||||
|
source: SponsorSourceType.Server,
|
||||||
|
UUID: "2" as SegmentUUID
|
||||||
|
}, {
|
||||||
|
segment: [30, 40],
|
||||||
|
category: "sponsor" as Category,
|
||||||
|
actionType: ActionType.Skip,
|
||||||
|
description: "Sponsor", // Force a description since chrome is not defined
|
||||||
|
source: SponsorSourceType.Server,
|
||||||
|
UUID: "3" as SegmentUUID
|
||||||
|
}, {
|
||||||
|
segment: [50, 60],
|
||||||
|
category: "selfpromo" as Category,
|
||||||
|
actionType: ActionType.Mute,
|
||||||
|
description: "Selfpromo",
|
||||||
|
source: SponsorSourceType.Server,
|
||||||
|
UUID: "4" as SegmentUUID
|
||||||
|
}, {
|
||||||
|
segment: [0, 0],
|
||||||
|
category: "selfpromo" as Category,
|
||||||
|
actionType: ActionType.Full,
|
||||||
|
description: "Selfpromo",
|
||||||
|
source: SponsorSourceType.Server,
|
||||||
|
UUID: "5" as SegmentUUID
|
||||||
|
}, {
|
||||||
|
segment: [80, 90],
|
||||||
|
category: "interaction" as Category,
|
||||||
|
actionType: ActionType.Skip,
|
||||||
|
description: "Interaction",
|
||||||
|
source: SponsorSourceType.YouTube,
|
||||||
|
UUID: "6" as SegmentUUID
|
||||||
|
}];
|
||||||
|
|
||||||
|
const result = exportTimes(segments);
|
||||||
|
|
||||||
|
expect(result).toBe(
|
||||||
|
"0:00 - 0:10 Chapter 1\n" +
|
||||||
|
"0:20 Highlight\n" +
|
||||||
|
"0:30 - 0:40 Sponsor\n"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Import segments", () => {
|
||||||
|
it("1:20 to 1:21 thing", () => {
|
||||||
|
const input = ` 1:20 to 1:21 thing
|
||||||
|
1:25 to 1:28 another thing`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 81],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 88],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("thing 1:20 to 1:21", () => {
|
||||||
|
const input = ` thing 1:20 to 1:21
|
||||||
|
another thing 1:25 to 1:28 ext`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 81],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 88],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1:20 - 1:21 thing", () => {
|
||||||
|
const input = ` 1:20 - 1:21 thing
|
||||||
|
1:25 - 1:28 another thing`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 81],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 88],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1:20 1:21 thing", () => {
|
||||||
|
const input = ` 1:20 1:21 thing
|
||||||
|
1:25 1:28 another thing`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 81],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 88],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1:20 thing", () => {
|
||||||
|
const input = ` 1:20 thing
|
||||||
|
1:25 another thing`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 85],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1:20: thing", () => {
|
||||||
|
const input = ` 1:20: thing
|
||||||
|
1:25: another thing`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 85],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1:20 (thing)", () => {
|
||||||
|
const input = ` 1:20 (thing)
|
||||||
|
1:25 (another thing)`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 85],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("thing 1:20", () => {
|
||||||
|
const input = ` thing 1:20
|
||||||
|
another thing 1:25`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 85],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("thing at 1:20", () => {
|
||||||
|
const input = ` thing at 1:20
|
||||||
|
another thing at 1:25`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [80, 85],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [85, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("thing at 1s", () => {
|
||||||
|
const input = ` thing at 1s
|
||||||
|
another thing at 5s`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [1, 5],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [5, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("thing at 1 second", () => {
|
||||||
|
const input = ` thing at 1 second
|
||||||
|
another thing at 5 seconds`;
|
||||||
|
|
||||||
|
const result = importTimes(input, 120);
|
||||||
|
expect(result).toMatchObject([{
|
||||||
|
segment: [1, 5],
|
||||||
|
description: "thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}, {
|
||||||
|
segment: [5, 120],
|
||||||
|
description: "another thing",
|
||||||
|
category: "chapter" as Category
|
||||||
|
}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user