Skip to content

Commit 6b6ad69

Browse files
authored
Merge pull request #206 from PerformanC/dev
sync: v3 < dev
2 parents 4f38f9a + 86729ae commit 6b6ad69

82 files changed

Lines changed: 5115 additions & 743 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/cla.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
steps:
1818
- name: "CLA Assistant"
1919
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
20-
uses: contributor-assistant/github-action@v2.6.1
20+
uses: ThePedroo/contributor-assistant@v1.0.1
2121
env:
2222
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2323
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERFORMANC_BOT_ACCESS_TOKEN }}
@@ -29,5 +29,5 @@ jobs:
2929
remote-organization-name: 'PerformanC'
3030
remote-repository-name: 'CLA-Signatures'
3131
create-file-commit-message: 'add: file for storing CLA Signatures'
32-
signed-commit-message: 'add: @$contributorName to the list of signed contributors in $owner'
32+
signed-commit-message: 'add: @$contributorName to the list of signed contributors in $owner/$repo#$pullRequestNo'
3333
custom-allsigned-prcomment: 'All Contributors have signed the CLA. The PR is now allowed to be merged.'

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020

2121
## Prerequisites
2222

23-
* **Node.js** v22 or higher (v24 recommended)
23+
* **Node.js** v22.22.2 or higher (v24 recommended)
2424
* **Git**
2525

2626
---

biome.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"$schema": "https://biomejs.dev/schemas/2.4.0/schema.json",
2+
"$schema": "https://biomejs.dev/schemas/2.4.12/schema.json",
33
"assist": { "actions": { "source": { "organizeImports": "on" } } },
44
"formatter": {
55
"enabled": true,

config.default.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ export default {
9292
enableLoadStreamEndpoint: false,
9393
resolveExternalLinks: false,
9494
fetchChannelInfo: false,
95+
sponsorblock: {
96+
enabled: false,
97+
api: 'https://sponsor.ajay.app',
98+
categories: [
99+
'sponsor',
100+
'selfpromo',
101+
'interaction',
102+
'intro',
103+
'outro',
104+
'preview',
105+
'music_offtopic',
106+
'filler'
107+
],
108+
actionTypes: ['skip'],
109+
skipMarginMs: 150
110+
},
95111
filters: {
96112
enabled: {
97113
tremolo: true,
@@ -467,6 +483,9 @@ export default {
467483
instances: [], // (optional) list of API instances
468484
streamingInstances: [], // (optional) list of streaming instances
469485
quality: 'HI_RES_LOSSLESS' // HI_RES_LOSSLESS, LOSSLESS, HIGH, LOW
486+
},
487+
googledrive: {
488+
enabled: true
470489
}
471490
},
472491
lyrics: {

dist/config.default.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,22 @@ export default {
9292
enableLoadStreamEndpoint: false,
9393
resolveExternalLinks: false,
9494
fetchChannelInfo: false,
95+
sponsorblock: {
96+
enabled: false,
97+
api: 'https://sponsor.ajay.app',
98+
categories: [
99+
'sponsor',
100+
'selfpromo',
101+
'interaction',
102+
'intro',
103+
'outro',
104+
'preview',
105+
'music_offtopic',
106+
'filler'
107+
],
108+
actionTypes: ['skip'],
109+
skipMarginMs: 150
110+
},
95111
filters: {
96112
enabled: {
97113
tremolo: true,
@@ -467,6 +483,9 @@ export default {
467483
instances: [], // (optional) list of API instances
468484
streamingInstances: [], // (optional) list of streaming instances
469485
quality: 'HI_RES_LOSSLESS' // HI_RES_LOSSLESS, LOSSLESS, HIGH, LOW
486+
},
487+
googledrive: {
488+
enabled: true
470489
}
471490
},
472491
lyrics: {

dist/package.json

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
{
22
"name": "nodelink",
3-
"version": "3.7.0",
3+
"version": "3.8.0-dev.20260422.1",
44
"scripts": {
55
"build": "tsc --incremental false",
66
"start": "node --dns-result-order=ipv4first --import tsx src/index.ts",
77
"start:experimental": "node --experimental-transform-types --dns-result-order=ipv4first src/index.ts",
88
"start:dist": "node --dns-result-order=ipv4first dist/src/index.js",
9-
"start:bun": "bun run --dns-result-order=ipv4first src/index.js",
9+
"start:bun": "bun run --dns-result-order=ipv4first src/index.ts",
1010
"type-check": "tsc --noEmit",
1111
"prepare": "husky"
1212
},
1313
"type": "module",
1414
"main": "src/index.ts",
15+
"engines": {
16+
"node": ">=22.22.2"
17+
},
1518
"dependencies": {
1619
"@alexanderolsen/libsamplerate-js": "^2.1.2",
1720
"@ecliptia/faad2-wasm": "2.11.2-ecliptia.2",
@@ -21,21 +24,21 @@
2124
"@toddynnn/symphonia-decoder": "1.0.6",
2225
"@toddynnn/voice-opus": "^1.0.1",
2326
"fastest-validator": "^1.19.1",
24-
"jsdom": "^27.4.0",
27+
"jsdom": "^29.0.2",
2528
"mp4box": "^2.3.0",
2629
"prom-client": "^15.1.3",
27-
"proxy-agent": "^6.5.0"
30+
"proxy-agent": "^8.0.1"
2831
},
2932
"devDependencies": {
30-
"@biomejs/biome": "^2.4.0",
31-
"@commitlint/cli": "20.3.0",
32-
"@commitlint/config-conventional": "20.3.0",
33-
"@types/bun": "^1.3.8",
34-
"@types/jsdom": "^27.0.0",
35-
"@types/node": "^25.2.1",
36-
"dotenv": "^17.3.1",
33+
"@biomejs/biome": "^2.4.12",
34+
"@commitlint/cli": "20.5.0",
35+
"@commitlint/config-conventional": "20.5.0",
36+
"@types/bun": "^1.3.12",
37+
"@types/jsdom": "^28.0.1",
38+
"@types/node": "^25.6.0",
39+
"dotenv": "^17.4.2",
3740
"husky": "9.1.7",
3841
"tsx": "^4.21.0",
39-
"typescript": "^5.9.3"
42+
"typescript": "^6.0.3"
4043
}
4144
}

dist/src/api/loadLyrics.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ function getLoadLyricsRuntime(nodelink) {
3939
const runtime = nodelink;
4040
if (runtime.sourceWorkerManager === undefined ||
4141
runtime.workerManager === undefined ||
42-
!runtime.lyrics) {
42+
runtime.lyrics === undefined) {
4343
return null;
4444
}
4545
return runtime;
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
import { sendErrorResponse } from "../utils.js";
2+
/**
3+
* Builds a strongly typed runtime view for the SponsorBlock route.
4+
*
5+
* @param nodelink - Router-facing runtime instance.
6+
* @returns Runtime compatible with the route, or `null` when the required
7+
* session manager field is unavailable.
8+
*/
9+
function getSponsorBlockRuntime(nodelink) {
10+
const runtime = nodelink;
11+
if (!runtime.sessions || typeof runtime.sessions.get !== 'function') {
12+
return null;
13+
}
14+
return runtime;
15+
}
16+
/**
17+
* Extracts and validates the path parameters used by the route.
18+
*
19+
* @param parsedUrl - Parsed request URL.
20+
* @returns Validated path parameters, or `null` when validation fails.
21+
*/
22+
function getPathParams(parsedUrl) {
23+
const pathParts = parsedUrl.pathname.split('/');
24+
const sessionId = pathParts[3];
25+
const guildId = pathParts[5];
26+
if (!sessionId || !guildId) {
27+
return null;
28+
}
29+
if (!/^\d{17,20}$/.test(guildId)) {
30+
return null;
31+
}
32+
return {
33+
sessionId,
34+
guildId
35+
};
36+
}
37+
/**
38+
* Handles `GET /sessions/:id/players/:guildId/sponsorblock`.
39+
*/
40+
async function handleGetSponsorBlock(req, res, pathParams, runtime, sendResponse) {
41+
const session = runtime.sessions.get(pathParams.sessionId);
42+
if (!session) {
43+
sendErrorResponse(req, res, 404, 'Not Found', "The provided sessionId doesn't exist.", req.url || '');
44+
return;
45+
}
46+
try {
47+
const state = session.players.getSponsorBlock(pathParams.guildId);
48+
sendResponse(req, res, state, 200);
49+
}
50+
catch (error) {
51+
const errorMessage = error instanceof Error ? error.message : 'Player not found';
52+
sendErrorResponse(req, res, 404, 'Not Found', errorMessage, req.url || '');
53+
}
54+
}
55+
/**
56+
* Handles `PATCH /sessions/:id/players/:guildId/sponsorblock`.
57+
*/
58+
async function handlePatchSponsorBlock(req, res, pathParams, runtime, sendResponse) {
59+
const session = runtime.sessions.get(pathParams.sessionId);
60+
if (!session) {
61+
sendErrorResponse(req, res, 404, 'Not Found', "The provided sessionId doesn't exist.", req.url || '');
62+
return;
63+
}
64+
const body = req.body;
65+
if (!body || typeof body !== 'object' || Array.isArray(body)) {
66+
sendErrorResponse(req, res, 400, 'Bad Request', 'Invalid body', req.url || '');
67+
return;
68+
}
69+
try {
70+
session.players.updateSponsorBlock(pathParams.guildId, {
71+
enabled: body.enabled,
72+
categories: body.categories,
73+
actionTypes: body.actionTypes,
74+
skipMarginMs: body.skipMarginMs
75+
});
76+
sendResponse(req, res, session.players.getSponsorBlock(pathParams.guildId), 200);
77+
}
78+
catch (error) {
79+
const errorMessage = error instanceof Error ? error.message : 'Player not found';
80+
sendErrorResponse(req, res, 404, 'Not Found', errorMessage, req.url || '');
81+
}
82+
}
83+
/**
84+
* Handles `POST /sessions/:id/players/:guildId/sponsorblock`.
85+
*/
86+
async function handlePostSponsorBlock(req, res, pathParams, runtime, sendResponse) {
87+
const session = runtime.sessions.get(pathParams.sessionId);
88+
if (!session) {
89+
sendErrorResponse(req, res, 404, 'Not Found', "The provided sessionId doesn't exist.", req.url || '');
90+
return;
91+
}
92+
const body = req.body;
93+
if (!body?.segments || !Array.isArray(body.segments)) {
94+
sendErrorResponse(req, res, 400, 'Bad Request', 'Invalid segments array', req.url || '');
95+
return;
96+
}
97+
try {
98+
session.players.setSponsorBlockSegments(pathParams.guildId, body.segments);
99+
sendResponse(req, res, session.players.getSponsorBlock(pathParams.guildId), 200);
100+
}
101+
catch (error) {
102+
const errorMessage = error instanceof Error ? error.message : 'Player not found';
103+
sendErrorResponse(req, res, 404, 'Not Found', errorMessage, req.url || '');
104+
}
105+
}
106+
/**
107+
* Handles `DELETE /sessions/:id/players/:guildId/sponsorblock`.
108+
*/
109+
async function handleDeleteSponsorBlock(req, res, pathParams, runtime) {
110+
const session = runtime.sessions.get(pathParams.sessionId);
111+
if (!session) {
112+
sendErrorResponse(req, res, 404, 'Not Found', "The provided sessionId doesn't exist.", req.url || '');
113+
return;
114+
}
115+
try {
116+
session.players.clearSponsorBlock(pathParams.guildId);
117+
res.writeHead(204);
118+
res.end();
119+
}
120+
catch (error) {
121+
const errorMessage = error instanceof Error ? error.message : 'Player not found';
122+
sendErrorResponse(req, res, 404, 'Not Found', errorMessage, req.url || '');
123+
}
124+
}
125+
/**
126+
* Handles requests for the SponsorBlock route.
127+
*/
128+
async function handler(nodelink, req, res, sendResponse, parsedUrl) {
129+
const runtime = getSponsorBlockRuntime(nodelink);
130+
if (!runtime) {
131+
sendErrorResponse(req, res, 500, 'Internal Server Error', 'SponsorBlock runtime contract is incomplete.', parsedUrl.pathname, true);
132+
return;
133+
}
134+
const pathParams = getPathParams(parsedUrl);
135+
if (!pathParams) {
136+
sendErrorResponse(req, res, 400, 'Bad Request', 'Invalid path parameters', parsedUrl.pathname, true);
137+
return;
138+
}
139+
if (req.method === 'GET') {
140+
await handleGetSponsorBlock(req, res, pathParams, runtime, sendResponse);
141+
return;
142+
}
143+
if (req.method === 'PATCH') {
144+
await handlePatchSponsorBlock(req, res, pathParams, runtime, sendResponse);
145+
return;
146+
}
147+
if (req.method === 'POST') {
148+
await handlePostSponsorBlock(req, res, pathParams, runtime, sendResponse);
149+
return;
150+
}
151+
if (req.method === 'DELETE') {
152+
await handleDeleteSponsorBlock(req, res, pathParams, runtime);
153+
return;
154+
}
155+
sendErrorResponse(req, res, 405, 'Method Not Allowed', 'Method Not Allowed', parsedUrl.pathname);
156+
}
157+
const sponsorBlockRoute = {
158+
handler,
159+
methods: ['GET', 'POST', 'PATCH', 'DELETE']
160+
};
161+
export default sponsorBlockRoute;

dist/src/api/sessions.id.players.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ function getPlayerPatchPayload(body) {
178178
typeof encodedTrack !== 'string') {
179179
return null;
180180
}
181-
const position = payload.position;
181+
const position = payload.position ?? payload.startTime;
182182
if (position !== undefined &&
183183
(typeof position !== 'number' || !Number.isFinite(position) || position < 0)) {
184184
return null;
@@ -231,6 +231,10 @@ function getPlayerPatchPayload(body) {
231231
? null
232232
: undefined,
233233
position,
234+
startTime: typeof payload.startTime === 'number' &&
235+
Number.isFinite(payload.startTime)
236+
? payload.startTime
237+
: undefined,
234238
endTime: typeof endTime === 'number'
235239
? endTime
236240
: endTime === null

dist/src/api/trackstream.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ function getItagFromQuery(parsedUrl) {
4141
*/
4242
function getTrackStreamRuntime(nodelink) {
4343
const runtime = nodelink;
44-
if (runtime.workerManager === undefined || !runtime.sources) {
44+
if (runtime.workerManager === undefined || runtime.sources === undefined) {
4545
return null;
4646
}
4747
return runtime;

0 commit comments

Comments
 (0)