source: Klonkt/src/services/ap-core.js@ d60c498

main
Last change on this file since d60c498 was 0677bac, checked in by Robin <roboburr@…>, 4 weeks ago

De tags kwamen er als onzin uit, en de tekst helemaal niet (shaer-38y)

Live nagekeken op dev, en twee dingen klopten niet.

DE TAGS. posts.tags staat als JSON-ARRAY in de database, niet als kommalijst.
Mijn eigen split leverde #["Doen we Niet" en #"music" op -- geen fout die
omvalt, een die onzin uitlevert. Nu met normalizeTags en tagParts, dezelfde
parser als de rest, dus ook dezelfde CamelCase voor tags van meerdere woorden.

Die twee stonden in ActivityPubService en music/ mag daar niet uit importeren,
dus ze zijn naar ap-core verhuisd -- de plek voor precies dit soort gedeelde
grondstof. Een kopie maken zou hetzelfde uit elkaar lopen opleveren als bij de
afleidingsregel.

DE TEKST. De excerpt bleek in de praktijk vaak leeg, en dan kwam er geen
content mee terwijl de post wel tekst heeft. Nu: excerpt eerst -- die is
geschreven om samen te vatten -- en anders het lijf zelf, zonder shortcodes
(die zijn de muziek, niet het verhaal erover) en zonder opmaak. Blijft er niets
over, dan geen veld: dat is beter dan een leeg veld.

De test gebruikte de kommavorm en testte dus de terugval in plaats van de echte
weg. Nu JSON, zoals het er staat.

Co-Authored-By: Claude Opus 5 <noreply@…>

  • Property mode set to 100644
File size: 6.2 KB
RevLine 
[8179fdb]1/**
2 * De primitieven die iedereen die ActivityPub uitzendt nodig heeft.
3 *
4 * Waarom dit bestand er is (shaer-drc): ActivityPubService.js was 6436 regels
5 * met 166 exports en tweeentwintig secties. Een submap zoals music/ kan pas
6 * zelfstandig bestaan als deze zes dingen ergens staan waar BEIDE uit kunnen
7 * putten -- anders importeert de submap uit ActivityPubService en importeert
8 * die weer terug, en dat is een kring.
9 *
10 * Guardianship laat zien hoe het wel moet: die map importeert alleen db en zijn
11 * eigen buren, nooit terug. Dit bestand maakt datzelfde mogelijk voor de rest.
12 *
13 * Alles hier is PUUR: geen database, geen netwerk, geen toestand. Dat is de
14 * grens -- komt daar iets bij dat wel iets weet, dan hoort het hier niet.
15 *
16 * EEN UITZONDERING OP "PUUR": AP_CONTEXT stelt zichzelf samen uit
17 * Guardianship.SHAER_CONTEXT. Die context is nu eenmaal de optelsom van ieders
18 * termen, dus dat hoort zo. Het maakt geen kring: guardianship kent alleen db
19 * en zijn eigen buren en importeert nooit terug.
20 */
21
22import * as Guardianship from './guardianship/index.js';
23
24export const PUBLIC = 'https://www.w3.org/ns/activitystreams#Public';
25// Full JSON-LD context for every AP object we emit: AS2 core + security (publicKey) + the
26// extension terms we actually use (Mastodon/toot + schema.org), each with a term definition
27// so a strict JSON-LD processor resolves them instead of dropping them → valid AS2/JSON-LD.
28// This is the same context shape Mastodon publishes, so Mastodon sees no change.
29export const AP_CONTEXT = [
30 'https://www.w3.org/ns/activitystreams',
31 'https://w3id.org/security/v1',
32 {
33 toot: 'http://joinmastodon.org/ns#',
34 schema: 'http://schema.org#',
35 sensitive: 'as:sensitive',
36 Hashtag: 'as:Hashtag',
37 manuallyApprovesFollowers: 'as:manuallyApprovesFollowers',
38 discoverable: 'toot:discoverable',
39 // FEP-7628 (account moves): same term declaration Mastodon ships.
40 alsoKnownAs: { '@id': 'as:alsoKnownAs', '@type': '@id' },
41 movedTo: { '@id': 'as:movedTo', '@type': '@id' },
42 featured: { '@id': 'toot:featured', '@type': '@id' },
43 PropertyValue: 'schema:PropertyValue',
44 value: 'schema:value',
45 embedUrl: { '@id': 'schema:embedUrl', '@type': '@id' },
46 // Wat een track beschrijft en AS2 niet kent (shaer-0nh). Funkwhale zet deze
47 // vier op zijn Audio; het bleken geen eigen verzinsels maar termen die
48 // schema.org gewoon heeft -- en schema.org stond hier al. De SLEUTELS zijn
49 // die van Funkwhale, want daar leest hij op; de BETEKENIS komt van
50 // schema.org, dus we hoeven geen vreemd vocabulaire binnen te halen.
51 license: { '@id': 'schema:license', '@type': '@id' },
52 position: 'schema:position',
53 bitrate: 'schema:bitrate',
54 size: 'schema:contentSize',
55 // Poll (Question) extension: Question/oneOf/anyOf/endTime/closed are AS2 core, but the
56 // per-poll unique-voter count is a Mastodon (toot) term — declare it so the emitted
57 // Question stays valid JSON-LD (a strict processor would otherwise drop votersCount).
58 votersCount: 'toot:votersCount',
59 // Kanaal-vocabulaire (shaer-0nh). Funkwhale declareert `category` niet
60 // inline maar via zijn eigen remote context https://funkwhale.audio/ns, en
61 // die host is vanaf hier onbereikbaar -- de IRI hieronder is dus AFGELEID
62 // en niet geverifieerd. Wat vandaag telt voor interop is de JSON-sleutel,
63 // want daar matchen lezers op; de declaratie zorgt alleen dat een strikte
64 // JSON-LD-processor hem niet laat vallen. Nakijken zodra die host weer
65 // antwoordt.
66 category: { '@id': 'https://funkwhale.audio/ns#category' },
67 // FEP-633c (Guardians): the shaer namespace, owned by the guardianship
68 // module (src/services/guardianship/).
69 ...Guardianship.SHAER_CONTEXT,
70 },
71];
72
73/** Een absolute http(s)-URL, of leeg. De enige plek die bepaalt wat wij een
74 * bruikbare URL vinden. */
75export const safeUrl = (u) => { const s = String(u == null ? '' : u).trim(); return /^https?:\/\//i.test(s) ? s : ''; };
76
77export function actorId(base, slug) { return `${base}/ap/users/${encodeURIComponent(slug)}`; }
78export function noteId(base, postId) { return `${base}/ap/notes/${encodeURIComponent(postId)}`; }
79
80/**
81 * mediaType raden uit een bestandsnaam. Stond twee keer functie-lokaal in dit
82 * bestand, met een commentaar dat ze "dezelfde afleiding" waren -- en dat was
83 * niet zo: de ene kende video, de andere alleen beeld. Nu een kaart, hier.
84 * De terugval is image/jpeg omdat dit alleen op omslagen en bijlagen wordt
85 * losgelaten, nooit op geluid: dat draagt zijn eigen mime_type uit de database.
86 */
87export function guessMediaType(u) {
88 const e = ((u || '').split('?')[0].match(/\.(\w+)$/) || [])[1];
89 return ({
90 jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', gif: 'image/gif',
91 webp: 'image/webp', avif: 'image/avif',
92 mp4: 'video/mp4', webm: 'video/webm', mov: 'video/quicktime',
93 })[(e || '').toLowerCase()] || 'image/jpeg';
94}
[0677bac]95
96/**
97 * Het tagveld van een post als lijst. Het staat in de database als JSON-ARRAY
98 * en niet als kommalijst -- op komma's splitsen levert `#["Doen we Niet"` op,
99 * en dat faalt niet, het liegt. Vandaar een echte parser, met de kommavorm als
100 * terugval voor wat er handmatig is ingevuld.
101 */
102export function normalizeTags(t) {
103 if (Array.isArray(t)) return t;
104 if (typeof t === 'string') {
105 const s = t.trim(); if (!s) return [];
106 if (s[0] === '[') { try { const a = JSON.parse(s); return Array.isArray(a) ? a : []; } catch { /* dan toch als kommalijst */ } }
107 return s.split(',').map((x) => x.trim()).filter(Boolean);
108 }
109 return [];
110}
111
112/**
113 * Een tag -> { label, slug }. Tags van meerdere woorden worden CamelCase
114 * (#LiveMusic) voor de weergavenaam -- een Mastodon-hashtag mag geen spaties
115 * bevatten en CamelCase is daar de toegankelijkheidsnorm; de slug en de href
116 * blijven kleingeschreven ("livemusic").
117 */
118export function tagParts(raw) {
119 const words = String(raw || '').trim().split(/[\s_]+/).map((w) => w.replace(/[^\p{L}\p{M}\p{N}]/gu, '')).filter(Boolean);
120 if (!words.length) return null;
121 const slug = words.join('').toLowerCase();
122 if (!slug) return null;
123 const label = words.length > 1 ? words.map((w) => w[0].toUpperCase() + w.slice(1)).join('') : words[0];
124 return { label, slug };
125}
Note: See TracBrowser for help on using the repository browser.