nixamp 0.24.0 → 0.24.2
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/README.md +9 -2
- package/dist/server.js +14 -34
- package/dist/translate.d.ts +8 -0
- package/dist/translate.js +16 -1
- package/dist/trollbox.d.ts +5 -6
- package/dist/trollbox.js +0 -0
- package/package.json +1 -1
- package/src/server.ts +14 -33
- package/src/translate.ts +17 -1
- package/src/trollbox.ts +0 -0
- package/web/dist/assets/{hls-3VKVEQE3-Dtl-3mpW.js → hls-3VKVEQE3-B3PGV4wK.js} +1 -1
- package/web/dist/assets/index-DOOjAq1w.js +1 -0
- package/web/dist/assets/index-oyp61Kly.css +1 -0
- package/web/dist/assets/{mpegts-BJC48bFV.js → mpegts-CtxeYR5-.js} +1 -1
- package/web/dist/assets/{mpegts-LO6RVLD6-CUIAB9k3.js → mpegts-LO6RVLD6-CBHUeoME.js} +1 -1
- package/web/dist/index.html +2 -2
- package/web/dist/sw.js +6 -6
- package/web/dist/assets/index-B0h4Nexr.js +0 -1
- package/web/dist/assets/index-BTGV3Pi5.css +0 -1
package/README.md
CHANGED
|
@@ -147,6 +147,12 @@ site. In the **Parties** panel, enter an **Invite code** and select **Join party
|
|
|
147
147
|
to open its room. A listed party's **Join party** link opens the film on the
|
|
148
148
|
site hosting it; **Open room** opens its nixamp room.
|
|
149
149
|
|
|
150
|
+
A live party also shows the current file's source and folder path, so a course
|
|
151
|
+
can read **Course › Section › Lecture**, with its position in that folder's
|
|
152
|
+
playlist. Numbered download filenames become readable lecture titles. Share
|
|
153
|
+
sheets, browser tabs and device media information follow the current lecture;
|
|
154
|
+
course names come from the server's metadata, never a guess from the filename.
|
|
155
|
+
|
|
150
156
|
A watch party lives on the site that has the film. bittorrented.com has them:
|
|
151
157
|
a six-character code, a host, and everybody at the same second. nixamp has
|
|
152
158
|
rooms, chat, invitations, a directory, and five clients that can already open
|
|
@@ -331,8 +337,9 @@ is tap, talk, tap: the page records, brings the sound to 16 kHz mono itself,
|
|
|
331
337
|
and sends nixamp.com a small WAV with the room's name on it; the ear posts
|
|
332
338
|
the words to the room in the same request, and the line appears. Said is
|
|
333
339
|
sent, unless the **Edit before sending** switch under the box is on, in
|
|
334
|
-
which case the words wait in the box for Send. A
|
|
335
|
-
|
|
340
|
+
which case the words wait in the box for Send. A line, once sent, is public
|
|
341
|
+
record: there is no taking it down, not by its author and not by the
|
|
342
|
+
server's owner.
|
|
336
343
|
|
|
337
344
|
**On the phone, too.** Every live room has a six-digit code on the party
|
|
338
345
|
line (see below), and only when somebody is on the phone in a room, each
|
package/dist/server.js
CHANGED
|
@@ -2241,14 +2241,20 @@ export function createHandler(engine, options) {
|
|
|
2241
2241
|
* The trollbox: the chat for one live room, keyed by the server and
|
|
2242
2242
|
* the channel so every viewer of a stream is in the same box whichever
|
|
2243
2243
|
* page they came from. Reading is open; a line needs a nixamp.com
|
|
2244
|
-
* account and is signed with its public handle;
|
|
2245
|
-
*
|
|
2244
|
+
* account and is signed with its public handle; and once sent it is
|
|
2245
|
+
* public record, so there is no DELETE here for anybody.
|
|
2246
2246
|
*/
|
|
2247
2247
|
if ((path === "/api/v1/trollbox" || path.startsWith("/api/v1/trollbox/")) && options.trollbox && options.accounts) {
|
|
2248
2248
|
const trollbox = options.trollbox;
|
|
2249
2249
|
const child = path.startsWith("/api/v1/trollbox/") ? path.slice("/api/v1/trollbox/".length) : "";
|
|
2250
|
+
// Nothing lives under a line's id: a sent line is public record, so
|
|
2251
|
+
// there is no line to address, whatever the method.
|
|
2252
|
+
if (child !== "") {
|
|
2253
|
+
json(response, 404, { error: "no such endpoint" });
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2250
2256
|
try {
|
|
2251
|
-
if (request.method === "GET"
|
|
2257
|
+
if (request.method === "GET") {
|
|
2252
2258
|
const where = roomFor(url.searchParams.get("server"), url.searchParams.get("channel"));
|
|
2253
2259
|
if (!where) {
|
|
2254
2260
|
json(response, 400, { error: "a room is a server address and a channel" });
|
|
@@ -2259,13 +2265,11 @@ export function createHandler(engine, options) {
|
|
|
2259
2265
|
json(response, 200, {
|
|
2260
2266
|
room: where.room,
|
|
2261
2267
|
you: who ? ((await options.handles?.of(who.id)) || fallbackHandle(who.id)) : "",
|
|
2262
|
-
messages: lines.map((one) => ({
|
|
2263
|
-
id: one.id, handle: one.handle, body: one.body, createdAt: one.createdAt, mine: who !== null && one.authorId === who.id,
|
|
2264
|
-
})),
|
|
2268
|
+
messages: lines.map((one) => ({ id: one.id, handle: one.handle, body: one.body, createdAt: one.createdAt })),
|
|
2265
2269
|
});
|
|
2266
2270
|
return;
|
|
2267
2271
|
}
|
|
2268
|
-
if (request.method === "POST"
|
|
2272
|
+
if (request.method === "POST") {
|
|
2269
2273
|
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
2270
2274
|
if (who === null) {
|
|
2271
2275
|
json(response, 401, { error: "sign in to nixamp.com to chat" });
|
|
@@ -2287,34 +2291,10 @@ export function createHandler(engine, options) {
|
|
|
2287
2291
|
const handle = (await options.handles?.of(who.id)) || fallbackHandle(who.id);
|
|
2288
2292
|
const line = await trollbox.post(where, who.id, handle, body.body);
|
|
2289
2293
|
readOnThePhone(where, who.id, line.handle, line.body);
|
|
2290
|
-
json(response, 201, { message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt
|
|
2294
|
+
json(response, 201, { message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt } });
|
|
2291
2295
|
return;
|
|
2292
2296
|
}
|
|
2293
|
-
|
|
2294
|
-
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
2295
|
-
if (who === null) {
|
|
2296
|
-
json(response, 401, { error: "sign in to nixamp.com first" });
|
|
2297
|
-
return;
|
|
2298
|
-
}
|
|
2299
|
-
const where = roomFor(url.searchParams.get("server"), url.searchParams.get("channel"));
|
|
2300
|
-
if (!where) {
|
|
2301
|
-
json(response, 400, { error: "a room is a server address and a channel" });
|
|
2302
|
-
return;
|
|
2303
|
-
}
|
|
2304
|
-
// The listing owner moderates their own server's rooms.
|
|
2305
|
-
const moderator = (options.directory?.list() ?? []).some((listing) => {
|
|
2306
|
-
try {
|
|
2307
|
-
return listing.ownerId === who.id && new URL(listing.url).origin === where.server;
|
|
2308
|
-
}
|
|
2309
|
-
catch {
|
|
2310
|
-
return false;
|
|
2311
|
-
}
|
|
2312
|
-
});
|
|
2313
|
-
const removed = await trollbox.remove(where.room, child, who.id, moderator);
|
|
2314
|
-
json(response, removed ? 200 : 404, removed ? { ok: true } : { error: "not your line, or already gone" });
|
|
2315
|
-
return;
|
|
2316
|
-
}
|
|
2317
|
-
json(response, 405, { error: "GET, POST or DELETE" });
|
|
2297
|
+
json(response, 405, { error: "GET or POST" });
|
|
2318
2298
|
}
|
|
2319
2299
|
catch (error) {
|
|
2320
2300
|
if (error instanceof TrollboxError)
|
|
@@ -2373,7 +2353,7 @@ export function createHandler(engine, options) {
|
|
|
2373
2353
|
readOnThePhone(where, who.id, line.handle, line.body);
|
|
2374
2354
|
json(response, 201, {
|
|
2375
2355
|
...said,
|
|
2376
|
-
message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt
|
|
2356
|
+
message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt },
|
|
2377
2357
|
});
|
|
2378
2358
|
return;
|
|
2379
2359
|
}
|
package/dist/translate.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ export declare const LANGUAGES: Record<string, {
|
|
|
21
21
|
name: string;
|
|
22
22
|
native: string;
|
|
23
23
|
}>;
|
|
24
|
+
/**
|
|
25
|
+
* A Marian model's line, tidied. Given a short line it tends to run on in
|
|
26
|
+
* punctuation until the token cap ("Och sånt.............."), and to put a
|
|
27
|
+
* comma and a full stop where one was meant. A space before a mark, a mark
|
|
28
|
+
* repeated, and a run of different marks each become the one mark, the
|
|
29
|
+
* first of them.
|
|
30
|
+
*/
|
|
31
|
+
export declare function tidyTranslation(text: string): string;
|
|
24
32
|
/** The models a translation from one language to another goes through, or null when there is no way. */
|
|
25
33
|
export declare function route(from: string, to: string, pairs?: ReadonlySet<string>): [string, string][] | null;
|
|
26
34
|
export declare function modelFor(from: string, to: string): string;
|
package/dist/translate.js
CHANGED
|
@@ -71,6 +71,21 @@ export const LANGUAGES = {
|
|
|
71
71
|
id: { name: "Indonesian", native: "Bahasa Indonesia" },
|
|
72
72
|
hi: { name: "Hindi", native: "हिन्दी" },
|
|
73
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* A Marian model's line, tidied. Given a short line it tends to run on in
|
|
76
|
+
* punctuation until the token cap ("Och sånt.............."), and to put a
|
|
77
|
+
* comma and a full stop where one was meant. A space before a mark, a mark
|
|
78
|
+
* repeated, and a run of different marks each become the one mark, the
|
|
79
|
+
* first of them.
|
|
80
|
+
*/
|
|
81
|
+
export function tidyTranslation(text) {
|
|
82
|
+
return text
|
|
83
|
+
.replace(/\s+([.,!?;:])/g, "$1")
|
|
84
|
+
.replace(/([.,!?;:])[.,;:]+/g, "$1")
|
|
85
|
+
.replace(/([!?])[!?]+/g, "$1")
|
|
86
|
+
.replace(/\s+/g, " ")
|
|
87
|
+
.trim();
|
|
88
|
+
}
|
|
74
89
|
/** The models a translation from one language to another goes through, or null when there is no way. */
|
|
75
90
|
export function route(from, to, pairs = PAIRS) {
|
|
76
91
|
if (from === to)
|
|
@@ -226,7 +241,7 @@ export class Translator {
|
|
|
226
241
|
}
|
|
227
242
|
const out = [...spoken];
|
|
228
243
|
which.forEach((i, at) => {
|
|
229
|
-
out[i] = (current[at] ?? "")
|
|
244
|
+
out[i] = tidyTranslation(current[at] ?? "");
|
|
230
245
|
});
|
|
231
246
|
return out;
|
|
232
247
|
});
|
package/dist/trollbox.d.ts
CHANGED
|
@@ -40,14 +40,13 @@ export declare class Trollbox {
|
|
|
40
40
|
private readonly spoke;
|
|
41
41
|
constructor(db: Queryable, now?: () => number);
|
|
42
42
|
private ensure;
|
|
43
|
-
/**
|
|
43
|
+
/**
|
|
44
|
+
* The room's lines, oldest first, after a moment when given. Nothing
|
|
45
|
+
* writes deleted_at any more; the filter only keeps the lines taken down
|
|
46
|
+
* before a sent line became public record out of view.
|
|
47
|
+
*/
|
|
44
48
|
messages(room: string, after?: string, limit?: number): Promise<TrollboxMessage[]>;
|
|
45
49
|
/** Whether this account may speak now, and the bookkeeping if so. */
|
|
46
50
|
private allow;
|
|
47
51
|
post(where: TrollboxRoom, authorId: string, handle: string, body: unknown): Promise<TrollboxMessage>;
|
|
48
|
-
/**
|
|
49
|
-
* A line taken down: by its author, or by a moderator (whoever owns the
|
|
50
|
-
* server's listing). Anybody else's ask removes nothing and says so.
|
|
51
|
-
*/
|
|
52
|
-
remove(room: string, id: string, byAccount: string, moderator: boolean): Promise<boolean>;
|
|
53
52
|
}
|
package/dist/trollbox.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
package/src/server.ts
CHANGED
|
@@ -2771,14 +2771,20 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2771
2771
|
* The trollbox: the chat for one live room, keyed by the server and
|
|
2772
2772
|
* the channel so every viewer of a stream is in the same box whichever
|
|
2773
2773
|
* page they came from. Reading is open; a line needs a nixamp.com
|
|
2774
|
-
* account and is signed with its public handle;
|
|
2775
|
-
*
|
|
2774
|
+
* account and is signed with its public handle; and once sent it is
|
|
2775
|
+
* public record, so there is no DELETE here for anybody.
|
|
2776
2776
|
*/
|
|
2777
2777
|
if ((path === "/api/v1/trollbox" || path.startsWith("/api/v1/trollbox/")) && options.trollbox && options.accounts) {
|
|
2778
2778
|
const trollbox = options.trollbox;
|
|
2779
2779
|
const child = path.startsWith("/api/v1/trollbox/") ? path.slice("/api/v1/trollbox/".length) : "";
|
|
2780
|
+
// Nothing lives under a line's id: a sent line is public record, so
|
|
2781
|
+
// there is no line to address, whatever the method.
|
|
2782
|
+
if (child !== "") {
|
|
2783
|
+
json(response, 404, { error: "no such endpoint" });
|
|
2784
|
+
return;
|
|
2785
|
+
}
|
|
2780
2786
|
try {
|
|
2781
|
-
if (request.method === "GET"
|
|
2787
|
+
if (request.method === "GET") {
|
|
2782
2788
|
const where = roomFor(url.searchParams.get("server"), url.searchParams.get("channel"));
|
|
2783
2789
|
if (!where) {
|
|
2784
2790
|
json(response, 400, { error: "a room is a server address and a channel" });
|
|
@@ -2789,13 +2795,11 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2789
2795
|
json(response, 200, {
|
|
2790
2796
|
room: where.room,
|
|
2791
2797
|
you: who ? ((await options.handles?.of(who.id)) || fallbackHandle(who.id)) : "",
|
|
2792
|
-
messages: lines.map((one) => ({
|
|
2793
|
-
id: one.id, handle: one.handle, body: one.body, createdAt: one.createdAt, mine: who !== null && one.authorId === who.id,
|
|
2794
|
-
})),
|
|
2798
|
+
messages: lines.map((one) => ({ id: one.id, handle: one.handle, body: one.body, createdAt: one.createdAt })),
|
|
2795
2799
|
});
|
|
2796
2800
|
return;
|
|
2797
2801
|
}
|
|
2798
|
-
if (request.method === "POST"
|
|
2802
|
+
if (request.method === "POST") {
|
|
2799
2803
|
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
2800
2804
|
if (who === null) {
|
|
2801
2805
|
json(response, 401, { error: "sign in to nixamp.com to chat" });
|
|
@@ -2816,33 +2820,10 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2816
2820
|
const handle = (await options.handles?.of(who.id)) || fallbackHandle(who.id);
|
|
2817
2821
|
const line = await trollbox.post(where, who.id, handle, body.body);
|
|
2818
2822
|
readOnThePhone(where, who.id, line.handle, line.body);
|
|
2819
|
-
json(response, 201, { message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt
|
|
2823
|
+
json(response, 201, { message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt } });
|
|
2820
2824
|
return;
|
|
2821
2825
|
}
|
|
2822
|
-
|
|
2823
|
-
const who = await options.accounts.whoIs(tokenFrom(request.headers));
|
|
2824
|
-
if (who === null) {
|
|
2825
|
-
json(response, 401, { error: "sign in to nixamp.com first" });
|
|
2826
|
-
return;
|
|
2827
|
-
}
|
|
2828
|
-
const where = roomFor(url.searchParams.get("server"), url.searchParams.get("channel"));
|
|
2829
|
-
if (!where) {
|
|
2830
|
-
json(response, 400, { error: "a room is a server address and a channel" });
|
|
2831
|
-
return;
|
|
2832
|
-
}
|
|
2833
|
-
// The listing owner moderates their own server's rooms.
|
|
2834
|
-
const moderator = (options.directory?.list() ?? []).some((listing) => {
|
|
2835
|
-
try {
|
|
2836
|
-
return listing.ownerId === who.id && new URL(listing.url).origin === where.server;
|
|
2837
|
-
} catch {
|
|
2838
|
-
return false;
|
|
2839
|
-
}
|
|
2840
|
-
});
|
|
2841
|
-
const removed = await trollbox.remove(where.room, child, who.id, moderator);
|
|
2842
|
-
json(response, removed ? 200 : 404, removed ? { ok: true } : { error: "not your line, or already gone" });
|
|
2843
|
-
return;
|
|
2844
|
-
}
|
|
2845
|
-
json(response, 405, { error: "GET, POST or DELETE" });
|
|
2826
|
+
json(response, 405, { error: "GET or POST" });
|
|
2846
2827
|
} catch (error) {
|
|
2847
2828
|
if (error instanceof TrollboxError) json(response, error.status, { error: error.message });
|
|
2848
2829
|
else throw error;
|
|
@@ -2899,7 +2880,7 @@ export function createHandler(engine: Engine, options: HandlerOptions) {
|
|
|
2899
2880
|
readOnThePhone(where, who.id, line.handle, line.body);
|
|
2900
2881
|
json(response, 201, {
|
|
2901
2882
|
...said,
|
|
2902
|
-
message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt
|
|
2883
|
+
message: { id: line.id, handle: line.handle, body: line.body, createdAt: line.createdAt },
|
|
2903
2884
|
});
|
|
2904
2885
|
return;
|
|
2905
2886
|
}
|
package/src/translate.ts
CHANGED
|
@@ -75,6 +75,22 @@ export const LANGUAGES: Record<string, { name: string; native: string }> = {
|
|
|
75
75
|
hi: { name: "Hindi", native: "हिन्दी" },
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* A Marian model's line, tidied. Given a short line it tends to run on in
|
|
80
|
+
* punctuation until the token cap ("Och sånt.............."), and to put a
|
|
81
|
+
* comma and a full stop where one was meant. A space before a mark, a mark
|
|
82
|
+
* repeated, and a run of different marks each become the one mark, the
|
|
83
|
+
* first of them.
|
|
84
|
+
*/
|
|
85
|
+
export function tidyTranslation(text: string): string {
|
|
86
|
+
return text
|
|
87
|
+
.replace(/\s+([.,!?;:])/g, "$1")
|
|
88
|
+
.replace(/([.,!?;:])[.,;:]+/g, "$1")
|
|
89
|
+
.replace(/([!?])[!?]+/g, "$1")
|
|
90
|
+
.replace(/\s+/g, " ")
|
|
91
|
+
.trim();
|
|
92
|
+
}
|
|
93
|
+
|
|
78
94
|
/** The models a translation from one language to another goes through, or null when there is no way. */
|
|
79
95
|
export function route(from: string, to: string, pairs: ReadonlySet<string> = PAIRS): [string, string][] | null {
|
|
80
96
|
if (from === to) return [];
|
|
@@ -262,7 +278,7 @@ export class Translator {
|
|
|
262
278
|
}
|
|
263
279
|
const out = [...spoken];
|
|
264
280
|
which.forEach((i, at) => {
|
|
265
|
-
out[i] = (current[at] ?? "")
|
|
281
|
+
out[i] = tidyTranslation(current[at] ?? "");
|
|
266
282
|
});
|
|
267
283
|
return out;
|
|
268
284
|
});
|
package/src/trollbox.ts
CHANGED
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./index-
|
|
1
|
+
import{t as e}from"./index-DOOjAq1w.js";var t=3;async function n(n){let{media:r,src:i,isTv:a}=n,{default:o}=await e(async()=>{let{default:e}=await import(`./hls-n74Cnh8A.js`);return{default:e}},[]);if(!o.isSupported())return n.onError(`This browser cannot play HLS streams.`),{destroy:()=>void 0,levels:()=>[]};let s=new o({...a?{maxBufferLength:60,maxMaxBufferLength:120,backBufferLength:30,liveSyncDurationCount:4}:{backBufferLength:90},enableWorker:!0}),c=0,l=!1;s.on(o.Events.ERROR,(e,r)=>{if(!l&&r.fatal){if(c>=t){n.onError(`This stream kept failing and has been stopped.`),s.destroy();return}switch(c+=1,r.type){case o.ErrorTypes.NETWORK_ERROR:n.onNotice(`Reconnecting…`),s.startLoad();break;case o.ErrorTypes.MEDIA_ERROR:n.onNotice(`Recovering…`),s.recoverMediaError();break;default:n.onError(`This stream could not be played.`),s.destroy()}}}),s.on(o.Events.MANIFEST_PARSED,()=>{l||(n.onNotice(null),n.onReady?.({live:s.levels.length>0&&!Number.isFinite(r.duration),levels:u()}))}),s.on(o.Events.LEVEL_LOADED,(e,t)=>{l||n.onReady?.({live:t.details.live,levels:u()})}),s.on(o.Events.FRAG_BUFFERED,()=>{l||n.onNotice(null)});function u(){return s.levels.map((e,t)=>({index:t,height:e.height||null,bitrate:e.bitrate||null,label:e.height?`${String(e.height)}p`:`${String(Math.round((e.bitrate||0)/1e3))}k`}))}return s.loadSource(i),s.attachMedia(r),{destroy(){l=!0,s.destroy()},levels:u,setLevel(e){s.currentLevel=e},currentLevel:()=>s.autoLevelEnabled?-1:s.currentLevel}}export{n as createHlsEngine};
|