diff --git a/.eslintrc.js b/.eslintrc.js
index edd417b..5078587 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,5 +1,5 @@
const unitTestsExtends = ['plugin:ava/recommended']
-const cypressTestsExtends = ['plugin:cypress/recommended', 'eslint-config-sinon', 'plugin:chai-friendly/recommended']
+const cypressTestsExtends = ['plugin:cypress/recommended', 'plugin:chai-friendly/recommended']
const commonExtends = ['plugin:prettier/recommended', 'plugin:unicorn/recommended', 'plugin:sonarjs/recommended']
const tsExtends = ['airbnb-typescript/base', ...commonExtends]
const jsExtends = ['airbnb-base', ...commonExtends]
@@ -60,6 +60,7 @@ const TS_OVERRIDE = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.eslint.json',
+ // eslint-disable-next-line unicorn/prefer-module
tsconfigRootDir: __dirname,
},
plugins: ['@typescript-eslint'],
@@ -72,6 +73,7 @@ const TS_OVERRIDE = {
overrides: [UNIT_TESTS_TS_OVERRIDE, CYPRESS_TS_OVERRIDE],
}
+// eslint-disable-next-line unicorn/prefer-module
module.exports = {
extends: jsExtends,
overrides: [UNIT_TESTS_JS_OVERRIDE, CYPRESS_JS_OVERRIDE, TS_OVERRIDE],
diff --git a/.nycrc b/.nycrc
index bfbc2df..a0b2edf 100644
--- a/.nycrc
+++ b/.nycrc
@@ -1,18 +1,9 @@
{
"extends": "@istanbuljs/nyc-config-typescript",
"check-coverage": true,
- "include": [
- "src/**",
- "metadata.ts"
- ],
- "exclude": [
- "dist/**",
- "cypress/**",
- "test/**"
- ],
- "reporter": [
- "text"
- ],
+ "include": ["src/**", "metadata.ts"],
+ "exclude": ["dist/**", "cypress/**", "test/**"],
+ "reporter": ["text"],
"lines": 70,
"branches": 70,
"statements": 70
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 235d379..fe665f6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,8 @@
## [1.7.1](https://github.com/avallete/yt-playlists-delete-enhancer/compare/v1.7.0...v1.7.1) (2024-05-12)
-
### Bug Fixes
-* api continuation requests ([#242](https://github.com/avallete/yt-playlists-delete-enhancer/issues/242)) ([87c238b](https://github.com/avallete/yt-playlists-delete-enhancer/commit/87c238b4d25777172d99f7c80475563cbae6e3ec))
+- api continuation requests ([#242](https://github.com/avallete/yt-playlists-delete-enhancer/issues/242)) ([87c238b](https://github.com/avallete/yt-playlists-delete-enhancer/commit/87c238b4d25777172d99f7c80475563cbae6e3ec))
# [1.7.0](https://github.com/avallete/yt-playlists-delete-enhancer/compare/v1.6.2...v1.7.0) (2024-05-06)
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index 1d7b148..f5f281c 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -1,3 +1,4 @@
+/* eslint-disable unicorn/prefer-module */
///
// ***********************************************************
// This example plugins/index.js can be used to load plugins
@@ -12,7 +13,7 @@
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
// eslint-disable-next-line import/no-extraneous-dependencies
-const path = require('path')
+const path = require('node:path')
const DIST_PATH = path.resolve(path.join(__dirname, '..', '..', 'dist'))
diff --git a/src/components/remove-video-enhancer-app.tsx b/src/components/remove-video-enhancer-app.tsx
index 76767be..3c1f485 100644
--- a/src/components/remove-video-enhancer-app.tsx
+++ b/src/components/remove-video-enhancer-app.tsx
@@ -40,11 +40,15 @@ export default class RemoveVideoEnhancerApp extends Component
await removeWatchHistoryForVideo(this.props.config, videoId)
removeWatchedFromPlaylistUI(videoId)
const { playlist } = this.state
- playlist?.continuations[0].videos.forEach((v) => {
- if (v.videoId === videoId) {
- v.percentDurationWatched = 0
+ if (playlist) {
+ for (const v of playlist.continuations[0].videos) {
+ if (v.videoId === videoId) {
+ v.percentDurationWatched = 0
+ }
}
- })
+ } else {
+ throw new Error('Playlist not found')
+ }
} catch (error) {
this.setState({ ...this.state, errorMessages: [(error as Error).message] })
}
@@ -55,7 +59,7 @@ export default class RemoveVideoEnhancerApp extends Component
if (playlist && playlist.continuations[0].videos.length > 0) {
const [toDeleteVideos, toKeepVideos] = partition(
playlist.continuations[0].videos,
- (v) => v.percentDurationWatched >= watchTimeValue
+ (v) => v.percentDurationWatched >= watchTimeValue,
)
if (toDeleteVideos.length > 0) {
try {
@@ -88,10 +92,7 @@ export default class RemoveVideoEnhancerApp extends Component
}
shouldComponentUpdate(nextProperties: Properties) {
- if (nextProperties.playlistName !== this.state?.playlist?.playlistId) {
- return true
- }
- return false
+ return nextProperties.playlistName !== this.state?.playlist?.playlistId
}
async componentDidUpdate(previousProperties: Properties) {
diff --git a/src/components/remove-video-enhancer-container.tsx b/src/components/remove-video-enhancer-container.tsx
index de4a81f..9dd0a30 100644
--- a/src/components/remove-video-enhancer-container.tsx
+++ b/src/components/remove-video-enhancer-container.tsx
@@ -22,10 +22,7 @@ export const REMOVE_BUTTON_ALT = 'Remove button to start removing videos'
function validate(value: any): boolean {
const numberValue = Number(value)
- if (Number.isSafeInteger(numberValue) && numberValue >= 0 && numberValue <= 100) {
- return true
- }
- return false
+ return !!(Number.isSafeInteger(numberValue) && numberValue >= 0 && numberValue <= 100)
}
function RemoveVideoEnhancerContainer({
@@ -70,7 +67,7 @@ function RemoveVideoEnhancerContainer({
onClick: removeVideo,
}),
],
- element
+ element,
)
}
diff --git a/src/lib/list-map-search.ts b/src/lib/list-map-search.ts
index 10b7a16..c7fa38a 100644
--- a/src/lib/list-map-search.ts
+++ b/src/lib/list-map-search.ts
@@ -15,7 +15,7 @@ export default function listMapSearch(
needles: Array,
haystack: Array,
needleKeyGetter: (item: T) => K,
- haystackKeyGetter: (item: U) => K
+ haystackKeyGetter: (item: U) => K,
): Record | false {
const searchMap: Record = {} as Record
// We cannot found all our needles into our haystack
diff --git a/src/operations/actions/remove-videos-from-playlist-ui.ts b/src/operations/actions/remove-videos-from-playlist-ui.ts
index 3f124d8..f6b92a1 100644
--- a/src/operations/actions/remove-videos-from-playlist-ui.ts
+++ b/src/operations/actions/remove-videos-from-playlist-ui.ts
@@ -7,6 +7,7 @@ export function removeVideoFromPlaylistUI(videoId: string) {
removeVideosFromPlaylist([{ videoId, percentDurationWatched: 100 }])
decrementNumberOfVideosInPlaylist(1)
} catch (error) {
+ // eslint-disable-next-line no-console
console.error(error)
// If an error occurs while trying to dynamically update the UI
// reload the page to update the UI
@@ -19,6 +20,7 @@ export default function removeVideosFromPlaylistUI(toDeleteVideos: PlaylistVideo
removeVideosFromPlaylist(toDeleteVideos)
decrementNumberOfVideosInPlaylist(toDeleteVideos.length)
} catch (error) {
+ // eslint-disable-next-line no-console
console.error(error)
// If an error occurs while trying to dynamically update the UI
// reload the page to update the UI
diff --git a/src/operations/conditions/is-on-playlist-page.ts b/src/operations/conditions/is-on-playlist-page.ts
index eac7a35..15df6ba 100644
--- a/src/operations/conditions/is-on-playlist-page.ts
+++ b/src/operations/conditions/is-on-playlist-page.ts
@@ -4,10 +4,7 @@ const PLAYLIST_URL_PATHNAME = '/playlist'
const isOnPlaylistPage: Condition = (window_: Window): boolean => {
const url = new URL(window_.location.href)
- if (url.pathname === PLAYLIST_URL_PATHNAME) {
- return true
- }
- return false
+ return url.pathname === PLAYLIST_URL_PATHNAME
}
export default isOnPlaylistPage
diff --git a/src/operations/ui/decrement-number-of-videos-in-playlist.ts b/src/operations/ui/decrement-number-of-videos-in-playlist.ts
index d7201e0..a9bb3cc 100644
--- a/src/operations/ui/decrement-number-of-videos-in-playlist.ts
+++ b/src/operations/ui/decrement-number-of-videos-in-playlist.ts
@@ -12,6 +12,7 @@ export default function decrementNumberOfVideosInPlaylist(value: number) {
// - The "There are no videos in this playlist yet" text
// - The "No videos" text
// Both strings are not part of the `yt.msgs_` object to use for localization
+ // eslint-disable-next-line no-console
console.log('empty playlist reload')
window.location.reload()
}
diff --git a/src/operations/ui/remove-videos-from-playlist.ts b/src/operations/ui/remove-videos-from-playlist.ts
index bdebc4e..e2a91e3 100644
--- a/src/operations/ui/remove-videos-from-playlist.ts
+++ b/src/operations/ui/remove-videos-from-playlist.ts
@@ -17,7 +17,7 @@ function removeVideoWithYtAction(videoId: String) {
],
returnValue: [],
},
- })
+ }),
)
}
@@ -32,7 +32,7 @@ export default function removeVideosFromPlaylist(videosToDelete: PlaylistVideo[]
uniqueVideosToDelete,
playlistVideoRendererNodes,
(video) => video.videoId,
- (node) => node.data.videoId
+ (node) => node.data.videoId,
)
// if all videos to remove are present in the UI
if (searchMap) {
diff --git a/src/yt-api.ts b/src/yt-api.ts
index 173edbd..00815c1 100644
--- a/src/yt-api.ts
+++ b/src/yt-api.ts
@@ -1,12 +1,10 @@
import sha1 from 'sha1'
-import { YTConfigData, PlaylistVideo, Playlist, PlaylistContinuation } from './youtube'
import { PlaylistNotEditableError, PlaylistEmptyError } from '~src/errors'
+import { YTConfigData, PlaylistVideo, Playlist, PlaylistContinuation } from './youtube'
type YTHeaderKey =
| 'X-Goog-Visitor-Id'
- // eslint-disable-next-line radar/no-duplicate-string
| 'X-YouTube-Client-Name'
- // eslint-disable-next-line radar/no-duplicate-string
| 'X-YouTube-Client-Version'
| 'X-YouTube-Device'
| 'X-YouTube-Identity-Token'
@@ -45,6 +43,7 @@ const API_REQUIRED_HEADERS: HeaderKey[] = [
function generateSAPISIDHASH(origin: string, sapisid: string, date: Date = new Date()): string {
const roundedTimestamp = Math.floor(date.getTime() / 1000)
// deepcode ignore InsecureHash: we need to replicate youtube webapp behavior
+ // eslint-disable-next-line sonarjs/no-nested-template-literals
return `${roundedTimestamp}_${sha1(`${roundedTimestamp} ${sapisid} ${origin}`)}`
}
@@ -72,21 +71,19 @@ function generateRequestHeaders(config: YTConfigData, headerKeys: HeaderKey[] =
}
function extractPlaylistVideoListRendererContents(playlistVideoListContents: Array): PlaylistVideo[] {
- return playlistVideoListContents.map(
- (item): PlaylistVideo => {
- return {
- videoId: item.playlistVideoRenderer.videoId,
- percentDurationWatched:
- item.playlistVideoRenderer.thumbnailOverlays[1].thumbnailOverlayResumePlaybackRenderer
- ?.percentDurationWatched || 0,
- }
+ return playlistVideoListContents.map((item): PlaylistVideo => {
+ return {
+ videoId: item.playlistVideoRenderer.videoId,
+ percentDurationWatched:
+ item.playlistVideoRenderer.thumbnailOverlays[1].thumbnailOverlayResumePlaybackRenderer
+ ?.percentDurationWatched || 0,
}
- )
+ })
}
function extractPlaylistContinuation(playlistContents: Array): PlaylistContinuation {
// ContinuationToken should be in the last item of the playlist contents
- const lastItem = playlistContents[playlistContents.length - 1]
+ const lastItem = playlistContents.at(-1)
if (lastItem && lastItem.continuationItemRenderer) {
// Remove last item from playlist contents since it contain continuationItem
playlistContents.pop()
@@ -113,8 +110,10 @@ async function fetchPlaylistInitialData(config: YTConfigData, playlistName: stri
method: 'GET',
mode: 'cors',
})
- const data = (await response.json()).response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content
- .sectionListRenderer.contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer
+ const respJson = await response.json()
+ const data =
+ respJson.response.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer
+ .contents[0].itemSectionRenderer.contents[0].playlistVideoListRenderer
if (!data) {
throw PlaylistEmptyError
@@ -130,7 +129,7 @@ async function fetchPlaylistInitialData(config: YTConfigData, playlistName: stri
async function fetchPlaylistContinuation(
config: YTConfigData,
- continuation: PlaylistContinuation
+ continuation: PlaylistContinuation,
): Promise {
const url = new URL(`${API_GET_PLAYLIST_VIDEOS_URL}`)
const headers = generateRequestHeaders(config, API_V1_REQUIRED_HEADERS)
@@ -152,7 +151,8 @@ async function fetchPlaylistContinuation(
method: 'POST',
mode: 'cors',
})
- const data = (await response.json()).onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems
+ const responseJson = await response.json()
+ const data = responseJson.onResponseReceivedActions[0].appendContinuationItemsAction.continuationItems
return extractPlaylistContinuation(data)
}
@@ -160,11 +160,11 @@ export async function fetchAllPlaylistContent(config: YTConfigData, playlistName
const playlist = await fetchPlaylistInitialData(config, playlistName)
if (playlist.isEditable) {
// If all data has been retrieved, the last continuation item token will be undefined
- while (playlist.continuations[playlist.continuations.length - 1].continuationToken !== undefined) {
+ while (playlist.continuations.at(-1)?.continuationToken !== undefined) {
playlist.continuations.push(
// We need the next continuationToken to launch the next request
// eslint-disable-next-line no-await-in-loop
- await fetchPlaylistContinuation(config, playlist.continuations[playlist.continuations.length - 1])
+ await fetchPlaylistContinuation(config, playlist.continuations.at(-1)!),
)
}
// Merge all the videos into a single PlaylistContinuation
@@ -193,9 +193,10 @@ async function getRemoveFromHistoryToken(videoId: string): Promise {
if (!matchedData || !matchedData[1]) throw new Error('Failed to parse initData')
const initData = JSON.parse(matchedData[1])
- const groups = initData?.contents?.twoColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents
- .map((group: { itemSectionRenderer: object }) => group.itemSectionRenderer)
- .filter(Boolean)
+ const groups =
+ initData?.contents?.twoColumnBrowseResultsRenderer?.tabs?.[0]?.tabRenderer?.content?.sectionListRenderer?.contents
+ .map((group: { itemSectionRenderer: object }) => group.itemSectionRenderer)
+ .filter(Boolean)
let matchingVideo
for (const item of groups) {
@@ -264,7 +265,7 @@ export async function removeWatchHistoryForVideo(config: YTConfigData, videoId:
export async function removeVideosFromPlaylist(
config: YTConfigData,
playlistId: string,
- videosToRemove: PlaylistVideo[]
+ videosToRemove: PlaylistVideo[],
): Promise {
const url = new URL(`${API_EDIT_PLAYLIST_VIDEOS_URL}`)
const headers = generateRequestHeaders(config, API_V1_REQUIRED_HEADERS)
@@ -292,8 +293,5 @@ export async function removeVideosFromPlaylist(
mode: 'cors',
})
const data = await response.json()
- if (data.status !== 'STATUS_SUCCEEDED') {
- return true
- }
- return false
+ return data.status !== 'STATUS_SUCCEEDED'
}
diff --git a/test/_setup-browser-environment.js b/test/_setup-browser-environment.js
index 436db1d..518e71e 100644
--- a/test/_setup-browser-environment.js
+++ b/test/_setup-browser-environment.js
@@ -1,3 +1,4 @@
+// eslint-disable-next-line unicorn/prefer-module
const browserEnv = require('browser-env')
browserEnv()
diff --git a/test/metadata.test.ts b/test/metadata.test.ts
index a48d5ec..f499a28 100644
--- a/test/metadata.test.ts
+++ b/test/metadata.test.ts
@@ -20,25 +20,25 @@ test.afterEach(() => {
test('generateDownloadUrlFromRepositoryUrl: should extract repo from github ssh url', (t) => {
t.is(
generateDownloadUrlFromRepositoryUrl('git@github.com:some-user/my-repo.git'),
- `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`
+ `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`,
)
})
test('generateDownloadUrlFromRepositoryUrl: should extract repo from github https url', (t) => {
t.is(
generateDownloadUrlFromRepositoryUrl('https://github.com/some-user/my-repo.git'),
- `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`
+ `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`,
)
})
test('generateDownloadUrlFromRepositoryUrl: should extract repo from git:// url', (t) => {
t.is(
generateDownloadUrlFromRepositoryUrl('git://github.com/some-user/my-repo.git'),
- `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`
+ `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`,
)
})
test('generateDownloadUrlFromRepositoryUrl: should extract repo from git+ssh:// url', (t) => {
t.is(
generateDownloadUrlFromRepositoryUrl('git+ssh://github.com/some-user/my-repo.git'),
- `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`
+ `https://raw.githubusercontent.com/some-user/my-repo/stubreleaseBranch/stubid.user.js`,
)
})
test('generateDownloadUrlFromRepositoryUrl: should return empty string if non-git url', (t) => {
diff --git a/test/src/lib/get-elements-by-xpaths.test.tsx b/test/src/lib/get-elements-by-xpaths.test.tsx
index 5c4c8a3..92af4b5 100644
--- a/test/src/lib/get-elements-by-xpaths.test.tsx
+++ b/test/src/lib/get-elements-by-xpaths.test.tsx
@@ -15,7 +15,7 @@ test.beforeEach(() => {
-
+ ,
)
container = result.container
})
@@ -34,7 +34,7 @@ test.serial('getElementsByXpath: should return array with the matching nodes in
One
Two
Three
- >
+ >,
)
const expected = [
expectedSnap.container.childNodes.item(0),
@@ -53,7 +53,7 @@ test.serial('getElementsByXpath: should work without parent element and use docu
One
Two
Three
- >
+ >,
)
const expected = [
expectedSnap.container.childNodes.item(0),
diff --git a/test/src/lib/list-map-search.test.ts b/test/src/lib/list-map-search.test.ts
index 36efe06..2c679ee 100644
--- a/test/src/lib/list-map-search.test.ts
+++ b/test/src/lib/list-map-search.test.ts
@@ -25,7 +25,7 @@ test('listMapSearch: should early break when all needles are found', (t) => {
{ id: 1, value: '41' },
],
needleKeyGetSpy,
- haystackKeyGetSpy
+ haystackKeyGetSpy,
)
t.deepEqual(result, { 1: { id: 1, value: '42' } })
t.true(needleKeyGetSpy.calledOnceWith({ id: 1 }))
@@ -41,7 +41,7 @@ test('listMapSearch: should early break when all needles are larger than haystac
{ id: 1, value: '41' },
],
needleKeyGetSpy,
- haystackKeyGetSpy
+ haystackKeyGetSpy,
)
t.is(result, false)
t.true(needleKeyGetSpy.notCalled)
@@ -60,7 +60,7 @@ test('listMapSearch: should not be troubled by duplicates in haystack and keep t
{ id: 2, value: '69' },
],
getIdAsString,
- getIdAsString
+ getIdAsString,
)
t.deepEqual(result, { '1': { id: 1, value: '42' }, '2': { id: 2, value: '69' } })
})
@@ -73,7 +73,7 @@ test('listMapSearch: should return false after all haystack has been tried', (t)
{ id: 3, value: '69' },
],
getIdAsString,
- getIdAsString
+ getIdAsString,
)
t.is(result, false)
})
@@ -90,7 +90,7 @@ test('listMapSearch: needle can be plain array', (t) => {
{ id: 2, value: '69' },
],
(index) => index,
- getId
+ getId,
)
t.deepEqual(result, { 1: { id: 1, value: '42' }, 2: { id: 2, value: '69' } })
})
diff --git a/test/src/lib/partition.test.ts b/test/src/lib/partition.test.ts
index 07fcc43..b27ec65 100644
--- a/test/src/lib/partition.test.ts
+++ b/test/src/lib/partition.test.ts
@@ -4,19 +4,19 @@ import partition from '~src/lib/partition'
test('partition: should return two array', (t) => {
t.deepEqual(
partition([], () => true),
- [[], []]
+ [[], []],
)
})
test('partition: should put values with truthy predicate to first array', (t) => {
t.deepEqual(
partition([1, 2, 3], () => true),
- [[1, 2, 3], []]
+ [[1, 2, 3], []],
)
})
test('partition: should put values with falsy predicate to second array', (t) => {
t.deepEqual(
partition([1, 2, 3], () => false),
- [[], [1, 2, 3]]
+ [[], [1, 2, 3]],
)
})
test('partition: should properly split array in two according to predicate', (t) => {
@@ -25,6 +25,6 @@ test('partition: should properly split array in two according to predicate', (t)
[
[4, 5, 6],
[1, 2, 3],
- ]
+ ],
)
})