nixamp 0.39.1 → 0.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-radio.d.ts +2 -0
- package/dist/ai-radio.js +9 -0
- package/dist/ai-routes.d.ts +3 -0
- package/dist/ai-routes.js +26 -1
- package/dist/listener-shares.d.ts +2 -0
- package/dist/listener-shares.js +4 -0
- package/dist/server.d.ts +8 -1
- package/dist/server.js +79 -1
- package/dist/station-cli.js +31 -0
- package/dist/station-mcp.d.ts +144 -14
- package/dist/station-mcp.js +25 -0
- package/dist/station-routes.d.ts +3 -0
- package/dist/station-routes.js +22 -3
- package/dist/station-runner.d.ts +8 -0
- package/dist/station-runner.js +14 -1
- package/dist/station-usage.d.ts +1 -1
- package/dist/station-usage.js +5 -0
- package/dist/support.d.ts +43 -0
- package/dist/support.js +203 -0
- package/dist/track-facts.d.ts +88 -0
- package/dist/track-facts.js +220 -0
- package/package.json +1 -1
- package/src/ai-radio.ts +9 -0
- package/src/ai-routes.ts +29 -1
- package/src/listener-shares.ts +5 -0
- package/src/server.ts +78 -1
- package/src/station-cli.ts +22 -0
- package/src/station-mcp.ts +24 -0
- package/src/station-routes.ts +23 -3
- package/src/station-runner.ts +14 -1
- package/src/station-usage.ts +5 -0
- package/src/support.ts +206 -0
- package/src/track-facts.ts +243 -0
- package/web/dist/assets/{ar-CfH8XZbx.js → ar-BY-kuED6.js} +1 -1
- package/web/dist/assets/{cs-CeMJ7kYa.js → cs-C6mMbdyp.js} +1 -1
- package/web/dist/assets/{da-6KhxLIZ0.js → da-DqV68WRi.js} +1 -1
- package/web/dist/assets/{de-BVR2Stjg.js → de-C4JKBX1z.js} +1 -1
- package/web/dist/assets/{es-CjMuKD1P.js → es-B_mF5h1R.js} +1 -1
- package/web/dist/assets/{fi-BUyaV3Dl.js → fi-CDbSPL76.js} +1 -1
- package/web/dist/assets/{fr-CJAmexWD.js → fr-B_573mv-.js} +1 -1
- package/web/dist/assets/{hi-C14LtUf_.js → hi--YR7-iSh.js} +1 -1
- package/web/dist/assets/{hu-DcEP4OTS.js → hu-kotmcF8p.js} +1 -1
- package/web/dist/assets/{id-CBLpeDO7.js → id-CY1mU0Fh.js} +1 -1
- package/web/dist/assets/{it-DJoytB_E.js → it-Dj4xHmAf.js} +1 -1
- package/web/dist/assets/{main-CDJ3EJVW.js → main-CwBTD5i3.js} +2 -2
- package/web/dist/assets/{nl-CO2DuuG-.js → nl-DVvshFko.js} +1 -1
- package/web/dist/assets/{ru-dGB_71qg.js → ru-BMwolR6_.js} +1 -1
- package/web/dist/assets/station-BZ2CtCcH.js +1 -0
- package/web/dist/assets/{station-Bq-LiY6G.css → station-CM31TuOq.css} +1 -1
- package/web/dist/assets/{sv-fzYrXr-B.js → sv-B_VngG1H.js} +1 -1
- package/web/dist/assets/{uk-ChGhFlXg.js → uk-D9pKHqQ7.js} +1 -1
- package/web/dist/assets/{vi-C7SpqYEM.js → vi-BezqX85w.js} +1 -1
- package/web/dist/assets/{zh-CcQj0L81.js → zh-CjTmbtGB.js} +1 -1
- package/web/dist/index.html +1 -1
- package/web/dist/station.html +7 -2
- package/web/dist/sw.js +21 -21
- package/web/dist/assets/station-DDvu2lUI.js +0 -1
package/dist/ai-radio.d.ts
CHANGED
|
@@ -125,6 +125,8 @@ export declare class AiRadio {
|
|
|
125
125
|
private composer;
|
|
126
126
|
list(): AiStation[];
|
|
127
127
|
get(key: string): AiStation | undefined;
|
|
128
|
+
/** The track AI radio composed at this path, if it did. */
|
|
129
|
+
trackAt(file: string): AiTrack | undefined;
|
|
128
130
|
/** What the owner sees: the stations, this month's seconds, and why nothing is being made. */
|
|
129
131
|
status(): Record<string, unknown>;
|
|
130
132
|
/** Change the month's cap; `resume` clears a stop for lack of credit. */
|
package/dist/ai-radio.js
CHANGED
|
@@ -218,6 +218,15 @@ export class AiRadio {
|
|
|
218
218
|
get(key) {
|
|
219
219
|
return this.stations.get(key);
|
|
220
220
|
}
|
|
221
|
+
/** The track AI radio composed at this path, if it did. */
|
|
222
|
+
trackAt(file) {
|
|
223
|
+
for (const station of this.stations.values()) {
|
|
224
|
+
const hit = station.tracks.find((one) => one.file === file);
|
|
225
|
+
if (hit)
|
|
226
|
+
return hit;
|
|
227
|
+
}
|
|
228
|
+
return undefined;
|
|
229
|
+
}
|
|
221
230
|
/** What the owner sees: the stations, this month's seconds, and why nothing is being made. */
|
|
222
231
|
status() {
|
|
223
232
|
const month = monthOf(this.now());
|
package/dist/ai-routes.d.ts
CHANGED
|
@@ -11,11 +11,14 @@
|
|
|
11
11
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
12
12
|
import type { AiRadio } from "./ai-radio.ts";
|
|
13
13
|
import { type ListenerShares } from "./listener-shares.ts";
|
|
14
|
+
import type { HolderStore } from "./track-facts.ts";
|
|
14
15
|
/** Whether this path is one anybody may use without the server's key. */
|
|
15
16
|
export declare function isPublicSharePath(path: string): boolean;
|
|
16
17
|
export interface AiRoutesContext {
|
|
17
18
|
ai?: AiRadio;
|
|
18
19
|
shares?: ListenerShares;
|
|
20
|
+
/** The copyright holders the owner registered, for the now-playing card. */
|
|
21
|
+
holders?: HolderStore;
|
|
19
22
|
canControl: boolean;
|
|
20
23
|
/** Who is asking, for per-sharer limits (an address; hashed before it is kept). */
|
|
21
24
|
caller: string;
|
package/dist/ai-routes.js
CHANGED
|
@@ -25,7 +25,8 @@ async function jsonBody(ctx, request) {
|
|
|
25
25
|
export async function handleAiRoutes(request, response, path, url, ctx) {
|
|
26
26
|
const method = request.method ?? "GET";
|
|
27
27
|
const publicPath = PUBLIC.exec(path);
|
|
28
|
-
const ownerPath = path === "/api/v1/stations/ai" || path.startsWith("/api/v1/stations/ai/") || path === "/api/v1/stations/shares" || path.startsWith("/api/v1/stations/shares/")
|
|
28
|
+
const ownerPath = path === "/api/v1/stations/ai" || path.startsWith("/api/v1/stations/ai/") || path === "/api/v1/stations/shares" || path.startsWith("/api/v1/stations/shares/")
|
|
29
|
+
|| path === "/api/v1/stations/holders" || path.startsWith("/api/v1/stations/holders/");
|
|
29
30
|
if (!publicPath && !ownerPath)
|
|
30
31
|
return false;
|
|
31
32
|
try {
|
|
@@ -81,6 +82,30 @@ export async function handleAiRoutes(request, response, path, url, ctx) {
|
|
|
81
82
|
ctx.json(response, 403, { error: "only this server's owner manages AI radio and shares", code: "forbidden" });
|
|
82
83
|
return true;
|
|
83
84
|
}
|
|
85
|
+
if (path === "/api/v1/stations/holders" || path.startsWith("/api/v1/stations/holders/")) {
|
|
86
|
+
const holders = ctx.holders;
|
|
87
|
+
if (!holders) {
|
|
88
|
+
ctx.json(response, 404, { error: "copyright holders are not kept here", code: "not_found" });
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
if (path === "/api/v1/stations/holders" && method === "GET") {
|
|
92
|
+
ctx.json(response, 200, { holders: holders.list() });
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
if (path === "/api/v1/stations/holders" && method === "POST") {
|
|
96
|
+
ctx.json(response, 200, { holder: holders.set(await jsonBody(ctx, request)) });
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
const one = /^\/api\/v1\/stations\/holders\/([a-z0-9-]{1,40})$/.exec(path);
|
|
100
|
+
if (one && method === "DELETE") {
|
|
101
|
+
if (!holders.remove(one[1]))
|
|
102
|
+
throw new StationError("not_found", "no such holder", 404);
|
|
103
|
+
ctx.json(response, 200, { deleted: one[1] });
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
ctx.json(response, 404, { error: "no such holder route", code: "not_found" });
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
84
109
|
if (path === "/api/v1/stations/shares" || path.startsWith("/api/v1/stations/shares/")) {
|
|
85
110
|
const shares = ctx.shares;
|
|
86
111
|
if (!shares) {
|
|
@@ -101,6 +101,8 @@ export declare class ListenerShares {
|
|
|
101
101
|
configure(input: Record<string, unknown>): ShareSettings;
|
|
102
102
|
list(state?: string): Share[];
|
|
103
103
|
get(id: string): Share | undefined;
|
|
104
|
+
/** The share whose copy is this file, if it is one. */
|
|
105
|
+
atFile(file: string): Share | undefined;
|
|
104
106
|
/** The share, if this token is the one its sharer was given. */
|
|
105
107
|
forSharer(id: string, token: string): Share | undefined;
|
|
106
108
|
private fromOf;
|
package/dist/listener-shares.js
CHANGED
|
@@ -146,6 +146,10 @@ export class ListenerShares {
|
|
|
146
146
|
get(id) {
|
|
147
147
|
return this.shares.get(id);
|
|
148
148
|
}
|
|
149
|
+
/** The share whose copy is this file, if it is one. */
|
|
150
|
+
atFile(file) {
|
|
151
|
+
return [...this.shares.values()].find((one) => one.file === file);
|
|
152
|
+
}
|
|
149
153
|
/** The share, if this token is the one its sharer was given. */
|
|
150
154
|
forSharer(id, token) {
|
|
151
155
|
const share = this.shares.get(id);
|
package/dist/server.d.ts
CHANGED
|
@@ -26,9 +26,10 @@ import { ListenerStore } from "./library-user.ts";
|
|
|
26
26
|
import { Importer } from "./handoff.ts";
|
|
27
27
|
import { AiRadio } from "./ai-radio.ts";
|
|
28
28
|
import { ListenerShares } from "./listener-shares.ts";
|
|
29
|
+
import { Support } from "./support.ts";
|
|
29
30
|
import { PeerGuides } from "./station-guide.ts";
|
|
30
31
|
import { StationRunner } from "./station-runner.ts";
|
|
31
|
-
import { StationStore } from "./stations.ts";
|
|
32
|
+
import { StationStore, type Station } from "./stations.ts";
|
|
32
33
|
import { StationUsage, type Limits } from "./station-metering.ts";
|
|
33
34
|
import { CompressionService } from "./compression/service.ts";
|
|
34
35
|
import { Enricher } from "./enrich.ts";
|
|
@@ -598,6 +599,10 @@ export interface HandlerOptions {
|
|
|
598
599
|
ai?: AiRadio;
|
|
599
600
|
/** Tracks anybody may share, sorted onto the air by AI. */
|
|
600
601
|
shares?: ListenerShares;
|
|
602
|
+
/** What a listener may know about the track on the air. */
|
|
603
|
+
facts?: (station: Station, file: string) => Promise<import("./track-facts.ts").TrackFacts>;
|
|
604
|
+
/** Copyright holders, matched to tracks by artist or station. */
|
|
605
|
+
holders?: import("./track-facts.ts").HolderStore;
|
|
601
606
|
/** Proxies (by address) whose X-Forwarded-For is believed, for per-sharer limits. */
|
|
602
607
|
trustedProxies?: () => Set<string>;
|
|
603
608
|
/** Other servers' public stations, for this guide to list beside its own. */
|
|
@@ -795,6 +800,8 @@ export interface HandlerOptions {
|
|
|
795
800
|
speech?: Speech;
|
|
796
801
|
liveVoice?: LiveVoice;
|
|
797
802
|
translationPasses?: TranslationPasses;
|
|
803
|
+
/** Payments to copyright holders nixamp.com collects for (support.ts). */
|
|
804
|
+
support?: Support;
|
|
798
805
|
/** Signing secret of nixamp's CoinPay business, for /api/v1/webhooks/coinpay. */
|
|
799
806
|
coinpayWebhookSecret?: string;
|
|
800
807
|
sharedTranslations?: SharedTranslations;
|
package/dist/server.js
CHANGED
|
@@ -63,6 +63,8 @@ import { handleHandoffs } from "./handoff-routes.js";
|
|
|
63
63
|
import { handleAiRoutes, isPublicSharePath } from "./ai-routes.js";
|
|
64
64
|
import { AiRadio } from "./ai-radio.js";
|
|
65
65
|
import { ListenerShares } from "./listener-shares.js";
|
|
66
|
+
import { HolderStore, TagReader, factsFor } from "./track-facts.js";
|
|
67
|
+
import { SUPPORT_CSP, SUPPORT_PAGE, Support, SupportError, supportHoldersFrom } from "./support.js";
|
|
66
68
|
import { STATION_APP_CSP, STATION_PAGE, STATION_PAGE_CSP, STATION_PAGE_HTML } from "./station-page.js";
|
|
67
69
|
import { GUIDE_CSP, guideHosts, guidePeers, peerBase, PeerGuides, renderGuide } from "./station-guide.js";
|
|
68
70
|
import { StationRunner } from "./station-runner.js";
|
|
@@ -2161,6 +2163,8 @@ export function createHandler(engine, options) {
|
|
|
2161
2163
|
!isPublicSharePath(path) &&
|
|
2162
2164
|
// What is on, as MCP, for anybody.
|
|
2163
2165
|
path !== "/mcp/public" &&
|
|
2166
|
+
// Supporting a copyright holder is for anybody listening.
|
|
2167
|
+
!/^\/support\/[a-z0-9-]{1,40}$/.test(path) && !/^\/api\/v1\/support\/[a-z0-9-]{1,40}$/.test(path) &&
|
|
2164
2168
|
// A station's short link, which sends the browser to wherever it plays.
|
|
2165
2169
|
!/^\/watch\/[a-z0-9]{10}$/.test(path) &&
|
|
2166
2170
|
!STATION_PAGE.test(path) &&
|
|
@@ -2227,6 +2231,57 @@ export function createHandler(engine, options) {
|
|
|
2227
2231
|
* in the directory). It sends the browser to the station's own page, on
|
|
2228
2232
|
* the server that plays it. Nothing private or unlisted is ever found.
|
|
2229
2233
|
*/
|
|
2234
|
+
/* Support a copyright holder: the page, the coins, and a CoinPay checkout. */
|
|
2235
|
+
const supportPage = /^\/support\/([a-z0-9-]{1,40})$/.exec(path);
|
|
2236
|
+
if (supportPage && options.support && (request.method === "GET" || request.method === "HEAD")) {
|
|
2237
|
+
if (!options.support.holder(supportPage[1])) {
|
|
2238
|
+
json(response, 404, { error: "nobody by that name is supported here", code: "not_found" });
|
|
2239
|
+
return;
|
|
2240
|
+
}
|
|
2241
|
+
response.writeHead(200, { "content-type": "text/html; charset=utf-8", "cache-control": "no-cache", "content-security-policy": SUPPORT_CSP, "referrer-policy": "no-referrer", "x-content-type-options": "nosniff" });
|
|
2242
|
+
response.end(request.method === "HEAD" ? undefined : SUPPORT_PAGE);
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
const supportApi = /^\/api\/v1\/support\/([a-z0-9-]{1,40})$/.exec(path);
|
|
2246
|
+
if (supportApi && options.support) {
|
|
2247
|
+
const support = options.support;
|
|
2248
|
+
const holder = support.holder(supportApi[1]);
|
|
2249
|
+
if (!holder) {
|
|
2250
|
+
json(response, 404, { error: "nobody by that name is supported here", code: "not_found" });
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
try {
|
|
2254
|
+
if (request.method === "GET") {
|
|
2255
|
+
const { coins } = support.enabled ? await support.coins() : { coins: [] };
|
|
2256
|
+
json(response, 200, { id: holder.id, name: holder.name, coins, ...(support.enabled ? {} : { error: "Support payments are not set up here." }) });
|
|
2257
|
+
return;
|
|
2258
|
+
}
|
|
2259
|
+
if (request.method === "POST") {
|
|
2260
|
+
const verdict = guard.check(`support:${callerOf(request.headers, request.socket.remoteAddress, behindProxy)}`, { allowed: 10, windowMs: 60_000 });
|
|
2261
|
+
if (!verdict.ok) {
|
|
2262
|
+
json(response, 429, { error: "too many at once; wait a minute", code: "rate_limited", retryAfter: verdict.retryAfter });
|
|
2263
|
+
return;
|
|
2264
|
+
}
|
|
2265
|
+
let body = {};
|
|
2266
|
+
try {
|
|
2267
|
+
body = JSON.parse(await readBody(request));
|
|
2268
|
+
}
|
|
2269
|
+
catch {
|
|
2270
|
+
body = {};
|
|
2271
|
+
}
|
|
2272
|
+
json(response, 200, await support.checkout({ ...body, holder: holder.id }));
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
json(response, 405, { error: "GET or POST", code: "method_not_allowed" });
|
|
2276
|
+
}
|
|
2277
|
+
catch (error) {
|
|
2278
|
+
if (error instanceof SupportError)
|
|
2279
|
+
json(response, error.status, { error: error.message, code: error.code });
|
|
2280
|
+
else
|
|
2281
|
+
json(response, 502, { error: "the payment service did not answer; nothing was charged", code: "provider" });
|
|
2282
|
+
}
|
|
2283
|
+
return;
|
|
2284
|
+
}
|
|
2230
2285
|
const stationShortLink = /^\/watch\/([a-z0-9]{10})$/.exec(path);
|
|
2231
2286
|
if (stationShortLink && options.stations && (request.method === "GET" || request.method === "HEAD")) {
|
|
2232
2287
|
const encrypted = request.socket.encrypted === true;
|
|
@@ -2325,7 +2380,7 @@ export function createHandler(engine, options) {
|
|
|
2325
2380
|
});
|
|
2326
2381
|
return;
|
|
2327
2382
|
}
|
|
2328
|
-
if (options.stations && (isPublicSharePath(path) || path.startsWith("/api/v1/stations/ai") || path.startsWith("/api/v1/stations/shares"))) {
|
|
2383
|
+
if (options.stations && (isPublicSharePath(path) || path.startsWith("/api/v1/stations/ai") || path.startsWith("/api/v1/stations/shares") || path.startsWith("/api/v1/stations/holders"))) {
|
|
2329
2384
|
const control = await controlOf();
|
|
2330
2385
|
// Behind a proxy we trust (dev2 for live.streaming), the sharer is the
|
|
2331
2386
|
// first address it names; otherwise whoever connected.
|
|
@@ -2335,6 +2390,7 @@ export function createHandler(engine, options) {
|
|
|
2335
2390
|
const handled = await handleAiRoutes(request, response, path, url, {
|
|
2336
2391
|
...(options.stations.ai ? { ai: options.stations.ai } : {}),
|
|
2337
2392
|
...(options.stations.shares ? { shares: options.stations.shares } : {}),
|
|
2393
|
+
...(options.stations.holders ? { holders: options.stations.holders } : {}),
|
|
2338
2394
|
canControl: control.allowed,
|
|
2339
2395
|
caller,
|
|
2340
2396
|
json,
|
|
@@ -2387,6 +2443,7 @@ export function createHandler(engine, options) {
|
|
|
2387
2443
|
...(options.stations.usage ? { usage: options.stations.usage } : {}),
|
|
2388
2444
|
...(options.stations.limits ? { limits: options.stations.limits } : {}),
|
|
2389
2445
|
...(options.stations.auto ? { auto: options.stations.auto } : {}),
|
|
2446
|
+
...(options.stations.facts ? { facts: options.stations.facts } : {}),
|
|
2390
2447
|
...(options.stations.peers ? { peers: () => options.stations.peers.entries() } : {}),
|
|
2391
2448
|
throttle: () => {
|
|
2392
2449
|
const verdict = guard.check(`stations:${control.account || callerOf(request.headers, request.socket.remoteAddress, behindProxy)}`, STATION_CONTROL_LIMIT);
|
|
@@ -6517,6 +6574,23 @@ export async function serve(argv, version = "0.1.0") {
|
|
|
6517
6574
|
});
|
|
6518
6575
|
stations.ai = aiRadio;
|
|
6519
6576
|
stations.shares = listenerShares;
|
|
6577
|
+
// The now-playing card: the file's own tags, where it came from here, and
|
|
6578
|
+
// the copyright holders the owner registered.
|
|
6579
|
+
const tagReader = new TagReader(tools.ffprobe);
|
|
6580
|
+
const holderStore = new HolderStore(HolderStore.fileFor(stateDir(), options.port));
|
|
6581
|
+
stations.holders = holderStore;
|
|
6582
|
+
stations.facts = async (station, file) => {
|
|
6583
|
+
const share = listenerShares.atFile(file);
|
|
6584
|
+
const composed = aiRadio.trackAt(file);
|
|
6585
|
+
return factsFor({
|
|
6586
|
+
station, file,
|
|
6587
|
+
tags: await tagReader.read(file),
|
|
6588
|
+
known: trackFor(engine, file),
|
|
6589
|
+
holders: holderStore.list(),
|
|
6590
|
+
share: share ? { basis: share.rights.basis, note: share.rights.note, by: share.by } : null,
|
|
6591
|
+
aiTrack: composed ? { made: composed.made } : null,
|
|
6592
|
+
});
|
|
6593
|
+
};
|
|
6520
6594
|
// Channels as HLS, on demand, for Safari on a phone: one ffmpeg copying a
|
|
6521
6595
|
// channel's fragments into short files while somebody is asking for them.
|
|
6522
6596
|
// What things are, from nichedb.dev, remembered beside the keys so a
|
|
@@ -6644,6 +6718,8 @@ export async function serve(argv, version = "0.1.0") {
|
|
|
6644
6718
|
// is temporarily unconfigured. Self-hosters can explicitly sponsor usage.
|
|
6645
6719
|
const translationPasses = accounts && pool && process.env["NIXAMP_TRANSLATION_BILLING"] !== "off"
|
|
6646
6720
|
? new TranslationPasses({ db: pool, key: coinpayKeyFrom(), site: nixampSite }) : undefined;
|
|
6721
|
+
// Payments to the copyright holders nixamp.com collects for, on its own CoinPay business.
|
|
6722
|
+
const supportPayments = accounts ? new Support({ key: coinpayKeyFrom(), site: nixampSite, holders: supportHoldersFrom() }) : undefined;
|
|
6647
6723
|
if (translationPasses)
|
|
6648
6724
|
void translationPasses.access().catch(() => console.error("nixamp: translation checkout could not warm; it will retry on demand"));
|
|
6649
6725
|
const liveVoice = accounts && process.env["NIXAMP_DUBBING"] !== "off" ? new LiveVoice({
|
|
@@ -7192,6 +7268,7 @@ export async function serve(argv, version = "0.1.0") {
|
|
|
7192
7268
|
...(speech ? { speech } : {}),
|
|
7193
7269
|
...(liveVoice ? { liveVoice } : {}),
|
|
7194
7270
|
...(translationPasses ? { translationPasses } : {}),
|
|
7271
|
+
...(supportPayments ? { support: supportPayments } : {}),
|
|
7195
7272
|
...(process.env["COINPAY_WEBHOOK_SECRET"] ? { coinpayWebhookSecret: process.env["COINPAY_WEBHOOK_SECRET"] } : {}),
|
|
7196
7273
|
...(sharedTranslations ? { sharedTranslations } : {}),
|
|
7197
7274
|
...(translator ? { translator } : {}),
|
|
@@ -7678,6 +7755,7 @@ export async function serve(argv, version = "0.1.0") {
|
|
|
7678
7755
|
enricher.save();
|
|
7679
7756
|
compression.stopAll();
|
|
7680
7757
|
hls.stopAll();
|
|
7758
|
+
stationRunner?.close();
|
|
7681
7759
|
channels.stopAll();
|
|
7682
7760
|
ingest?.stopRtmp();
|
|
7683
7761
|
ingest?.close();
|
package/dist/station-cli.js
CHANGED
|
@@ -265,6 +265,37 @@ export async function station(argv) {
|
|
|
265
265
|
return await aiCommand(argv.slice(1), call, show, json);
|
|
266
266
|
case "shares":
|
|
267
267
|
return await sharesCommand(argv.slice(1), call, show);
|
|
268
|
+
case "holder":
|
|
269
|
+
case "holders": {
|
|
270
|
+
const sub = argv[1] ?? "list";
|
|
271
|
+
const all = (flagName) => argv.flatMap((one, at) => (one === flagName && argv[at + 1] ? [argv[at + 1]] : []));
|
|
272
|
+
const line = (h) => `${String(h["name"])} [${String(h["id"])}]\n artists ${(h["artists"] ?? []).join(", ") || "-"}\n stations ${(h["stations"] ?? []).join(", ") || "-"}\n support ${String(h["support"] || "-")}${h["license"] ? `\n licence ${String(h["license"])}` : ""}`;
|
|
273
|
+
if (sub === "list")
|
|
274
|
+
return show(await call("GET", "/api/v1/stations/holders"), (body) => (body["holders"] ?? []).map(line).join("\n\n") || "No copyright holders yet. nixamp station holder set NAME --artist ARTIST --support URL");
|
|
275
|
+
if (sub === "remove")
|
|
276
|
+
return show(await call("DELETE", `/api/v1/stations/holders/${argv[2] ?? ""}`), () => `Removed ${argv[2]}.`);
|
|
277
|
+
if (sub === "set") {
|
|
278
|
+
const name = argv[2];
|
|
279
|
+
if (!name || name.startsWith("--")) {
|
|
280
|
+
console.error("nixamp station holder set needs a NAME");
|
|
281
|
+
return 2;
|
|
282
|
+
}
|
|
283
|
+
const body = { name };
|
|
284
|
+
if (all("--artist").length)
|
|
285
|
+
body["artists"] = all("--artist");
|
|
286
|
+
if (all("--station").length)
|
|
287
|
+
body["stations"] = all("--station");
|
|
288
|
+
if (flag(argv, "--support"))
|
|
289
|
+
body["support"] = flag(argv, "--support");
|
|
290
|
+
if (flag(argv, "--label"))
|
|
291
|
+
body["label"] = flag(argv, "--label");
|
|
292
|
+
if (flag(argv, "--license"))
|
|
293
|
+
body["license"] = flag(argv, "--license");
|
|
294
|
+
return show(await call("POST", "/api/v1/stations/holders", body), (answer) => line(answer["holder"]));
|
|
295
|
+
}
|
|
296
|
+
console.error(`nixamp station holder: no command "${sub}".\n\n${USAGE}`);
|
|
297
|
+
return 2;
|
|
298
|
+
}
|
|
268
299
|
case "delete": {
|
|
269
300
|
const station = needId();
|
|
270
301
|
if (!station)
|
package/dist/station-mcp.d.ts
CHANGED
|
@@ -28,7 +28,6 @@ export declare const STATION_TOOLS: ({
|
|
|
28
28
|
off?: undefined;
|
|
29
29
|
key?: undefined;
|
|
30
30
|
preset?: undefined;
|
|
31
|
-
name?: undefined;
|
|
32
31
|
style?: undefined;
|
|
33
32
|
description?: undefined;
|
|
34
33
|
vocals?: undefined;
|
|
@@ -36,7 +35,6 @@ export declare const STATION_TOOLS: ({
|
|
|
36
35
|
perDay?: undefined;
|
|
37
36
|
enabled?: undefined;
|
|
38
37
|
monthlySeconds?: undefined;
|
|
39
|
-
action?: undefined;
|
|
40
38
|
state?: undefined;
|
|
41
39
|
reason?: undefined;
|
|
42
40
|
open?: undefined;
|
|
@@ -44,6 +42,13 @@ export declare const STATION_TOOLS: ({
|
|
|
44
42
|
publish?: undefined;
|
|
45
43
|
maxMb?: undefined;
|
|
46
44
|
perHour?: undefined;
|
|
45
|
+
action?: undefined;
|
|
46
|
+
name?: undefined;
|
|
47
|
+
artists?: undefined;
|
|
48
|
+
stations?: undefined;
|
|
49
|
+
support?: undefined;
|
|
50
|
+
label?: undefined;
|
|
51
|
+
license?: undefined;
|
|
47
52
|
id?: undefined;
|
|
48
53
|
private?: undefined;
|
|
49
54
|
};
|
|
@@ -83,7 +88,6 @@ export declare const STATION_TOOLS: ({
|
|
|
83
88
|
off?: undefined;
|
|
84
89
|
key?: undefined;
|
|
85
90
|
preset?: undefined;
|
|
86
|
-
name?: undefined;
|
|
87
91
|
style?: undefined;
|
|
88
92
|
description?: undefined;
|
|
89
93
|
vocals?: undefined;
|
|
@@ -91,7 +95,6 @@ export declare const STATION_TOOLS: ({
|
|
|
91
95
|
perDay?: undefined;
|
|
92
96
|
enabled?: undefined;
|
|
93
97
|
monthlySeconds?: undefined;
|
|
94
|
-
action?: undefined;
|
|
95
98
|
state?: undefined;
|
|
96
99
|
reason?: undefined;
|
|
97
100
|
open?: undefined;
|
|
@@ -99,6 +102,13 @@ export declare const STATION_TOOLS: ({
|
|
|
99
102
|
publish?: undefined;
|
|
100
103
|
maxMb?: undefined;
|
|
101
104
|
perHour?: undefined;
|
|
105
|
+
action?: undefined;
|
|
106
|
+
name?: undefined;
|
|
107
|
+
artists?: undefined;
|
|
108
|
+
stations?: undefined;
|
|
109
|
+
support?: undefined;
|
|
110
|
+
label?: undefined;
|
|
111
|
+
license?: undefined;
|
|
102
112
|
private?: undefined;
|
|
103
113
|
};
|
|
104
114
|
required: string[];
|
|
@@ -168,7 +178,6 @@ export declare const STATION_TOOLS: ({
|
|
|
168
178
|
perDay?: undefined;
|
|
169
179
|
enabled?: undefined;
|
|
170
180
|
monthlySeconds?: undefined;
|
|
171
|
-
action?: undefined;
|
|
172
181
|
state?: undefined;
|
|
173
182
|
reason?: undefined;
|
|
174
183
|
open?: undefined;
|
|
@@ -176,6 +185,12 @@ export declare const STATION_TOOLS: ({
|
|
|
176
185
|
publish?: undefined;
|
|
177
186
|
maxMb?: undefined;
|
|
178
187
|
perHour?: undefined;
|
|
188
|
+
action?: undefined;
|
|
189
|
+
artists?: undefined;
|
|
190
|
+
stations?: undefined;
|
|
191
|
+
support?: undefined;
|
|
192
|
+
label?: undefined;
|
|
193
|
+
license?: undefined;
|
|
179
194
|
id?: undefined;
|
|
180
195
|
private?: undefined;
|
|
181
196
|
};
|
|
@@ -243,7 +258,6 @@ export declare const STATION_TOOLS: ({
|
|
|
243
258
|
perDay?: undefined;
|
|
244
259
|
enabled?: undefined;
|
|
245
260
|
monthlySeconds?: undefined;
|
|
246
|
-
action?: undefined;
|
|
247
261
|
state?: undefined;
|
|
248
262
|
reason?: undefined;
|
|
249
263
|
open?: undefined;
|
|
@@ -251,6 +265,12 @@ export declare const STATION_TOOLS: ({
|
|
|
251
265
|
publish?: undefined;
|
|
252
266
|
maxMb?: undefined;
|
|
253
267
|
perHour?: undefined;
|
|
268
|
+
action?: undefined;
|
|
269
|
+
artists?: undefined;
|
|
270
|
+
stations?: undefined;
|
|
271
|
+
support?: undefined;
|
|
272
|
+
label?: undefined;
|
|
273
|
+
license?: undefined;
|
|
254
274
|
private?: undefined;
|
|
255
275
|
};
|
|
256
276
|
required: string[];
|
|
@@ -293,7 +313,6 @@ export declare const STATION_TOOLS: ({
|
|
|
293
313
|
off?: undefined;
|
|
294
314
|
key?: undefined;
|
|
295
315
|
preset?: undefined;
|
|
296
|
-
name?: undefined;
|
|
297
316
|
style?: undefined;
|
|
298
317
|
description?: undefined;
|
|
299
318
|
vocals?: undefined;
|
|
@@ -308,6 +327,12 @@ export declare const STATION_TOOLS: ({
|
|
|
308
327
|
publish?: undefined;
|
|
309
328
|
maxMb?: undefined;
|
|
310
329
|
perHour?: undefined;
|
|
330
|
+
name?: undefined;
|
|
331
|
+
artists?: undefined;
|
|
332
|
+
stations?: undefined;
|
|
333
|
+
support?: undefined;
|
|
334
|
+
label?: undefined;
|
|
335
|
+
license?: undefined;
|
|
311
336
|
private?: undefined;
|
|
312
337
|
};
|
|
313
338
|
required: string[];
|
|
@@ -357,7 +382,6 @@ export declare const STATION_TOOLS: ({
|
|
|
357
382
|
off?: undefined;
|
|
358
383
|
key?: undefined;
|
|
359
384
|
preset?: undefined;
|
|
360
|
-
name?: undefined;
|
|
361
385
|
style?: undefined;
|
|
362
386
|
description?: undefined;
|
|
363
387
|
vocals?: undefined;
|
|
@@ -365,7 +389,6 @@ export declare const STATION_TOOLS: ({
|
|
|
365
389
|
perDay?: undefined;
|
|
366
390
|
enabled?: undefined;
|
|
367
391
|
monthlySeconds?: undefined;
|
|
368
|
-
action?: undefined;
|
|
369
392
|
state?: undefined;
|
|
370
393
|
reason?: undefined;
|
|
371
394
|
open?: undefined;
|
|
@@ -373,6 +396,13 @@ export declare const STATION_TOOLS: ({
|
|
|
373
396
|
publish?: undefined;
|
|
374
397
|
maxMb?: undefined;
|
|
375
398
|
perHour?: undefined;
|
|
399
|
+
action?: undefined;
|
|
400
|
+
name?: undefined;
|
|
401
|
+
artists?: undefined;
|
|
402
|
+
stations?: undefined;
|
|
403
|
+
support?: undefined;
|
|
404
|
+
label?: undefined;
|
|
405
|
+
license?: undefined;
|
|
376
406
|
private?: undefined;
|
|
377
407
|
};
|
|
378
408
|
required: string[];
|
|
@@ -421,7 +451,6 @@ export declare const STATION_TOOLS: ({
|
|
|
421
451
|
};
|
|
422
452
|
key?: undefined;
|
|
423
453
|
preset?: undefined;
|
|
424
|
-
name?: undefined;
|
|
425
454
|
style?: undefined;
|
|
426
455
|
description?: undefined;
|
|
427
456
|
vocals?: undefined;
|
|
@@ -429,7 +458,6 @@ export declare const STATION_TOOLS: ({
|
|
|
429
458
|
perDay?: undefined;
|
|
430
459
|
enabled?: undefined;
|
|
431
460
|
monthlySeconds?: undefined;
|
|
432
|
-
action?: undefined;
|
|
433
461
|
state?: undefined;
|
|
434
462
|
reason?: undefined;
|
|
435
463
|
open?: undefined;
|
|
@@ -437,6 +465,13 @@ export declare const STATION_TOOLS: ({
|
|
|
437
465
|
publish?: undefined;
|
|
438
466
|
maxMb?: undefined;
|
|
439
467
|
perHour?: undefined;
|
|
468
|
+
action?: undefined;
|
|
469
|
+
name?: undefined;
|
|
470
|
+
artists?: undefined;
|
|
471
|
+
stations?: undefined;
|
|
472
|
+
support?: undefined;
|
|
473
|
+
label?: undefined;
|
|
474
|
+
license?: undefined;
|
|
440
475
|
id?: undefined;
|
|
441
476
|
private?: undefined;
|
|
442
477
|
};
|
|
@@ -515,6 +550,11 @@ export declare const STATION_TOOLS: ({
|
|
|
515
550
|
moderation?: undefined;
|
|
516
551
|
maxMb?: undefined;
|
|
517
552
|
perHour?: undefined;
|
|
553
|
+
artists?: undefined;
|
|
554
|
+
stations?: undefined;
|
|
555
|
+
support?: undefined;
|
|
556
|
+
label?: undefined;
|
|
557
|
+
license?: undefined;
|
|
518
558
|
id?: undefined;
|
|
519
559
|
private?: undefined;
|
|
520
560
|
};
|
|
@@ -546,7 +586,6 @@ export declare const STATION_TOOLS: ({
|
|
|
546
586
|
off?: undefined;
|
|
547
587
|
key?: undefined;
|
|
548
588
|
preset?: undefined;
|
|
549
|
-
name?: undefined;
|
|
550
589
|
style?: undefined;
|
|
551
590
|
description?: undefined;
|
|
552
591
|
vocals?: undefined;
|
|
@@ -589,6 +628,12 @@ export declare const STATION_TOOLS: ({
|
|
|
589
628
|
perHour: {
|
|
590
629
|
type: string;
|
|
591
630
|
};
|
|
631
|
+
name?: undefined;
|
|
632
|
+
artists?: undefined;
|
|
633
|
+
stations?: undefined;
|
|
634
|
+
support?: undefined;
|
|
635
|
+
label?: undefined;
|
|
636
|
+
license?: undefined;
|
|
592
637
|
private?: undefined;
|
|
593
638
|
};
|
|
594
639
|
required?: undefined;
|
|
@@ -619,7 +664,6 @@ export declare const STATION_TOOLS: ({
|
|
|
619
664
|
off?: undefined;
|
|
620
665
|
key?: undefined;
|
|
621
666
|
preset?: undefined;
|
|
622
|
-
name?: undefined;
|
|
623
667
|
style?: undefined;
|
|
624
668
|
description?: undefined;
|
|
625
669
|
vocals?: undefined;
|
|
@@ -627,7 +671,6 @@ export declare const STATION_TOOLS: ({
|
|
|
627
671
|
perDay?: undefined;
|
|
628
672
|
enabled?: undefined;
|
|
629
673
|
monthlySeconds?: undefined;
|
|
630
|
-
action?: undefined;
|
|
631
674
|
state?: undefined;
|
|
632
675
|
reason?: undefined;
|
|
633
676
|
open?: undefined;
|
|
@@ -639,6 +682,93 @@ export declare const STATION_TOOLS: ({
|
|
|
639
682
|
type: "string";
|
|
640
683
|
description: string;
|
|
641
684
|
};
|
|
685
|
+
action: {
|
|
686
|
+
type: string;
|
|
687
|
+
enum: string[];
|
|
688
|
+
};
|
|
689
|
+
id: {
|
|
690
|
+
readonly type: "string";
|
|
691
|
+
};
|
|
692
|
+
name: {
|
|
693
|
+
readonly type: "string";
|
|
694
|
+
};
|
|
695
|
+
artists: {
|
|
696
|
+
type: string;
|
|
697
|
+
items: {
|
|
698
|
+
readonly type: "string";
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
stations: {
|
|
702
|
+
type: string;
|
|
703
|
+
items: {
|
|
704
|
+
readonly type: "string";
|
|
705
|
+
};
|
|
706
|
+
};
|
|
707
|
+
support: {
|
|
708
|
+
type: "string";
|
|
709
|
+
description: string;
|
|
710
|
+
};
|
|
711
|
+
label: {
|
|
712
|
+
readonly type: "string";
|
|
713
|
+
};
|
|
714
|
+
license: {
|
|
715
|
+
readonly type: "string";
|
|
716
|
+
};
|
|
717
|
+
private?: undefined;
|
|
718
|
+
};
|
|
719
|
+
required?: undefined;
|
|
720
|
+
};
|
|
721
|
+
} | {
|
|
722
|
+
name: string;
|
|
723
|
+
description: string;
|
|
724
|
+
inputSchema: {
|
|
725
|
+
type: string;
|
|
726
|
+
properties: {
|
|
727
|
+
playlist?: undefined;
|
|
728
|
+
idempotencyKey?: undefined;
|
|
729
|
+
folder?: undefined;
|
|
730
|
+
items?: undefined;
|
|
731
|
+
shuffle?: undefined;
|
|
732
|
+
repeat?: undefined;
|
|
733
|
+
bitrate?: undefined;
|
|
734
|
+
visibility?: undefined;
|
|
735
|
+
listed?: undefined;
|
|
736
|
+
basis?: undefined;
|
|
737
|
+
note?: undefined;
|
|
738
|
+
video?: undefined;
|
|
739
|
+
minItems?: undefined;
|
|
740
|
+
minFilms?: undefined;
|
|
741
|
+
maxVideoRunning?: undefined;
|
|
742
|
+
breaksEvery?: undefined;
|
|
743
|
+
start?: undefined;
|
|
744
|
+
off?: undefined;
|
|
745
|
+
key?: undefined;
|
|
746
|
+
preset?: undefined;
|
|
747
|
+
style?: undefined;
|
|
748
|
+
description?: undefined;
|
|
749
|
+
vocals?: undefined;
|
|
750
|
+
trackSeconds?: undefined;
|
|
751
|
+
perDay?: undefined;
|
|
752
|
+
enabled?: undefined;
|
|
753
|
+
monthlySeconds?: undefined;
|
|
754
|
+
state?: undefined;
|
|
755
|
+
reason?: undefined;
|
|
756
|
+
open?: undefined;
|
|
757
|
+
moderation?: undefined;
|
|
758
|
+
publish?: undefined;
|
|
759
|
+
maxMb?: undefined;
|
|
760
|
+
perHour?: undefined;
|
|
761
|
+
action?: undefined;
|
|
762
|
+
name?: undefined;
|
|
763
|
+
artists?: undefined;
|
|
764
|
+
stations?: undefined;
|
|
765
|
+
support?: undefined;
|
|
766
|
+
label?: undefined;
|
|
767
|
+
license?: undefined;
|
|
768
|
+
server: {
|
|
769
|
+
type: "string";
|
|
770
|
+
description: string;
|
|
771
|
+
};
|
|
642
772
|
id: {
|
|
643
773
|
type: "string";
|
|
644
774
|
description: string;
|