+1
-1
apps/aqua/src/auth/client.ts
+1
-1
apps/aqua/src/auth/client.ts
···
15
15
? `${url}/client-metadata.json`
16
16
: `http://localhost?redirect_uri=${enc(`${url}/oauth/callback`)}&scope=${enc("atproto transition:generic")}`,
17
17
client_uri: url,
18
-
redirect_uris: [`${url}/oauth/callback`],
18
+
redirect_uris: [`${url}/oauth/callback`, `${url}/oauth/callback/app`],
19
19
scope: "atproto transition:generic",
20
20
grant_types: ["authorization_code", "refresh_token"],
21
21
response_types: ["code"],
+39
-3
apps/aqua/src/auth/router.ts
+39
-3
apps/aqua/src/auth/router.ts
···
8
8
import { setCookie } from "hono/cookie";
9
9
import { env } from "@/lib/env";
10
10
11
-
export async function callback(c: TealContext) {
11
+
const publicUrl = env.PUBLIC_URL;
12
+
const redirectBase = publicUrl || `http://127.0.0.1:${env.PORT}`;
13
+
14
+
export function generateState(prefix?: string) {
15
+
const state = crypto.randomUUID();
16
+
return `${prefix}${prefix ? ":" : ""}${state}`;
17
+
}
18
+
19
+
const SPA_PREFIX = "a37d";
20
+
21
+
// /oauth/login?handle=teal.fm
22
+
export async function login(c: TealContext) {
23
+
const { handle, spa } = c.req.query();
24
+
if (!handle) {
25
+
return Response.json({ error: "Missing handle" });
26
+
}
27
+
const url = await atclient.authorize(handle, {
28
+
scope: "atproto transition:generic",
29
+
// state.appState in callback
30
+
state: generateState(spa ? SPA_PREFIX : undefined),
31
+
});
32
+
return c.json({ url });
33
+
}
34
+
35
+
// Redirect to the app's callback URL.
36
+
async function callbackToApp(c: TealContext) {
37
+
const queries = c.req.query();
38
+
const params = new URLSearchParams(queries);
39
+
return c.redirect(`${env.APP_URI}/oauth/callback?${params.toString()}`);
40
+
}
41
+
42
+
export async function callback(c: TealContext, isSpa: boolean = false) {
12
43
try {
13
44
const honoParams = c.req.query();
14
45
console.log("params", honoParams);
15
46
const params = new URLSearchParams(honoParams);
16
-
const { session } = await atclient.callback(params);
47
+
48
+
const { session, state } = await atclient.callback(params);
49
+
50
+
console.log("state", state);
17
51
18
52
const did = session.did;
19
53
···
42
76
maxAge: 60 * 60 * 24 * 365,
43
77
});
44
78
45
-
if (params.get("spa")) {
79
+
if (isSpa) {
46
80
return c.json({
47
81
provider: "atproto",
48
82
jwt: did,
···
122
156
123
157
const app = new Hono<EnvWithCtx>();
124
158
159
+
app.get("/login", async (c) => login(c));
125
160
app.get("/callback", async (c) => callback(c));
161
+
app.get("/callback/app", async (c) => callback(c, true));
126
162
app.get("/refresh", async (c) => refresh(c));
127
163
128
164
export const getAuthRouter = () => {
-1
apps/aqua/src/auth/storage.ts
-1
apps/aqua/src/auth/storage.ts
+1
apps/aqua/src/lib/env.ts
+1
apps/aqua/src/lib/env.ts
···
14
14
HOST: host({ devDefault: testOnly("0.0.0.0") }),
15
15
PORT: port({ devDefault: testOnly(3000) }),
16
16
PUBLIC_URL: str({}),
17
+
APP_URI: str({ devDefault: "fm.teal.amethyst://" }),
17
18
DB_PATH: str({ devDefault: "file:./db.sqlite" }),
18
19
COOKIE_SECRET: str({ devDefault: "secret_cookie! very secret!" }),
19
20
});
bun.lockb
bun.lockb
This is a binary file and will not be displayed.
+1
-1
package.json
+1
-1
package.json
+24
-8
packages/jetstring/src/index.ts
+24
-8
packages/jetstring/src/index.ts
···
3
3
import { status } from "@teal/db/schema";
4
4
import { CommitCreateEvent, Jetstream } from "@skyware/jetstream";
5
5
6
+
import {
7
+
Record as XyzStatusphereStatus,
8
+
isRecord as isStatusphereStatus,
9
+
} from "@teal/lexicons/generated/server/types/xyz/statusphere/status";
10
+
6
11
class Handler {
7
12
private static instance: Handler;
8
13
private constructor() {}
···
13
18
return Handler.instance;
14
19
}
15
20
16
-
handle(msg_type: string, msg: any) {
21
+
handle(msg_type: string, record: CommitCreateEvent<string & {}>) {
17
22
// Handle message logic here
23
+
const msg = record.commit.record;
18
24
console.log("Handling" + msg_type + "message:", msg);
19
-
if (msg_type === "xyz.statusphere.status") {
20
-
// serialize message as xyz.statusphere.status
21
-
const st = db.insert(status).values({
22
-
status: msg.status,
23
-
uri: msg.uri,
24
-
authorDid: msg.authorDid,
25
-
});
25
+
if (isStatusphereStatus(msg) && msg.$type === "xyz.statusphere.status") {
26
+
if (record.commit.operation === "create") {
27
+
// serialize message as xyz.statusphere.status
28
+
db.insert(status).values({
29
+
createdAt: new Date().getSeconds().toString(),
30
+
indexedAt: new Date(record.time_us).getSeconds().toString(),
31
+
status: msg.status,
32
+
// the AT path
33
+
uri: record.commit.rkey,
34
+
authorDid: record.did,
35
+
});
36
+
} else {
37
+
console.log("unsupported operation:", record.commit.operation);
38
+
}
39
+
} else {
40
+
console.log("Unknown message type:", msg_type);
41
+
console.log("Message:", record);
26
42
}
27
43
}
28
44
}
+50
packages/lexicons/generated/server/index.ts
+50
packages/lexicons/generated/server/index.ts
···
17
17
export class Server {
18
18
xrpc: XrpcServer
19
19
app: AppNS
20
+
fm: FmNS
20
21
xyz: XyzNS
21
22
22
23
constructor(options?: XrpcOptions) {
23
24
this.xrpc = createXrpcServer(schemas, options)
24
25
this.app = new AppNS(this)
26
+
this.fm = new FmNS(this)
25
27
this.xyz = new XyzNS(this)
26
28
}
27
29
}
···
39
41
export class AppBskyNS {
40
42
_server: Server
41
43
actor: AppBskyActorNS
44
+
richtext: AppBskyRichtextNS
42
45
43
46
constructor(server: Server) {
44
47
this._server = server
45
48
this.actor = new AppBskyActorNS(server)
49
+
this.richtext = new AppBskyRichtextNS(server)
46
50
}
47
51
}
48
52
49
53
export class AppBskyActorNS {
54
+
_server: Server
55
+
56
+
constructor(server: Server) {
57
+
this._server = server
58
+
}
59
+
}
60
+
61
+
export class AppBskyRichtextNS {
62
+
_server: Server
63
+
64
+
constructor(server: Server) {
65
+
this._server = server
66
+
}
67
+
}
68
+
69
+
export class FmNS {
70
+
_server: Server
71
+
teal: FmTealNS
72
+
73
+
constructor(server: Server) {
74
+
this._server = server
75
+
this.teal = new FmTealNS(server)
76
+
}
77
+
}
78
+
79
+
export class FmTealNS {
80
+
_server: Server
81
+
alpha: FmTealAlphaNS
82
+
83
+
constructor(server: Server) {
84
+
this._server = server
85
+
this.alpha = new FmTealAlphaNS(server)
86
+
}
87
+
}
88
+
89
+
export class FmTealAlphaNS {
90
+
_server: Server
91
+
actor: FmTealAlphaActorNS
92
+
93
+
constructor(server: Server) {
94
+
this._server = server
95
+
this.actor = new FmTealAlphaActorNS(server)
96
+
}
97
+
}
98
+
99
+
export class FmTealAlphaActorNS {
50
100
_server: Server
51
101
52
102
constructor(server: Server) {
+274
packages/lexicons/generated/server/lexicons.ts
+274
packages/lexicons/generated/server/lexicons.ts
···
59
59
},
60
60
},
61
61
},
62
+
AppBskyRichtextFacet: {
63
+
lexicon: 1,
64
+
id: 'app.bsky.richtext.facet',
65
+
defs: {
66
+
main: {
67
+
type: 'object',
68
+
description: 'Annotation of a sub-string within rich text.',
69
+
required: ['index', 'features'],
70
+
properties: {
71
+
index: {
72
+
type: 'ref',
73
+
ref: 'lex:app.bsky.richtext.facet#byteSlice',
74
+
},
75
+
features: {
76
+
type: 'array',
77
+
items: {
78
+
type: 'union',
79
+
refs: [
80
+
'lex:app.bsky.richtext.facet#mention',
81
+
'lex:app.bsky.richtext.facet#link',
82
+
'lex:app.bsky.richtext.facet#tag',
83
+
],
84
+
},
85
+
},
86
+
},
87
+
},
88
+
mention: {
89
+
type: 'object',
90
+
description:
91
+
"Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
92
+
required: ['did'],
93
+
properties: {
94
+
did: {
95
+
type: 'string',
96
+
format: 'did',
97
+
},
98
+
},
99
+
},
100
+
link: {
101
+
type: 'object',
102
+
description:
103
+
'Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.',
104
+
required: ['uri'],
105
+
properties: {
106
+
uri: {
107
+
type: 'string',
108
+
format: 'uri',
109
+
},
110
+
},
111
+
},
112
+
tag: {
113
+
type: 'object',
114
+
description:
115
+
"Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
116
+
required: ['tag'],
117
+
properties: {
118
+
tag: {
119
+
type: 'string',
120
+
maxLength: 640,
121
+
maxGraphemes: 64,
122
+
},
123
+
},
124
+
},
125
+
byteSlice: {
126
+
type: 'object',
127
+
description:
128
+
'Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.',
129
+
required: ['byteStart', 'byteEnd'],
130
+
properties: {
131
+
byteStart: {
132
+
type: 'integer',
133
+
minimum: 0,
134
+
},
135
+
byteEnd: {
136
+
type: 'integer',
137
+
minimum: 0,
138
+
},
139
+
},
140
+
},
141
+
},
142
+
},
143
+
FmTealAlphaActorProfile: {
144
+
lexicon: 1,
145
+
id: 'fm.teal.alpha.actor.profile',
146
+
defs: {
147
+
main: {
148
+
type: 'record',
149
+
description:
150
+
'This lexicon is in a not officially released state. It is subject to change. | A declaration of a teal.fm account profile.',
151
+
key: 'literal:self',
152
+
record: {
153
+
type: 'object',
154
+
properties: {
155
+
displayName: {
156
+
type: 'string',
157
+
maxGraphemes: 64,
158
+
maxLength: 640,
159
+
},
160
+
description: {
161
+
type: 'string',
162
+
description: 'Free-form profile description text.',
163
+
maxGraphemes: 256,
164
+
maxLength: 2560,
165
+
},
166
+
descriptionFacets: {
167
+
type: 'array',
168
+
description:
169
+
'Annotations of text in the profile description (mentions, URLs, hashtags, etc).',
170
+
items: {
171
+
type: 'ref',
172
+
ref: 'lex:app.bsky.richtext.facet',
173
+
},
174
+
},
175
+
featuredItem: {
176
+
type: 'ref',
177
+
description:
178
+
"The user's most recent item featured on their profile.",
179
+
ref: 'lex:fm.teal.alpha.actor.profile#featuredItem',
180
+
},
181
+
avatar: {
182
+
type: 'blob',
183
+
description:
184
+
"Small image to be displayed next to posts from account. AKA, 'profile picture'",
185
+
accept: ['image/png', 'image/jpeg'],
186
+
maxSize: 1000000,
187
+
},
188
+
banner: {
189
+
type: 'blob',
190
+
description:
191
+
'Larger horizontal image to display behind profile view.',
192
+
accept: ['image/png', 'image/jpeg'],
193
+
maxSize: 1000000,
194
+
},
195
+
createdAt: {
196
+
type: 'string',
197
+
format: 'datetime',
198
+
},
199
+
},
200
+
},
201
+
},
202
+
featuredItem: {
203
+
type: 'object',
204
+
required: ['mbid', 'type'],
205
+
properties: {
206
+
mbid: {
207
+
type: 'string',
208
+
description: 'The Musicbrainz ID of the item',
209
+
},
210
+
type: {
211
+
type: 'string',
212
+
description:
213
+
'The type of the item. Must be a valid Musicbrainz type, e.g. album, track, recording, etc.',
214
+
},
215
+
},
216
+
},
217
+
},
218
+
},
219
+
FmTealAlphaActorStatus: {
220
+
lexicon: 1,
221
+
id: 'fm.teal.alpha.actor.status',
222
+
defs: {
223
+
main: {
224
+
type: 'record',
225
+
description:
226
+
'This lexicon is in a not officially released state. It is subject to change. | A declaration of the status of the actor. Only one can be shown at a time. If there are multiple, the latest record should be picked and earlier records should be deleted or tombstoned.',
227
+
key: 'literal:self',
228
+
record: {
229
+
type: 'object',
230
+
required: ['time', 'item'],
231
+
properties: {
232
+
time: {
233
+
type: 'string',
234
+
format: 'datetime',
235
+
description: 'The unix timestamp of when the item was recorded',
236
+
},
237
+
item: {
238
+
type: 'union',
239
+
refs: ['lex:fm.teal.alpha.play#record'],
240
+
},
241
+
},
242
+
},
243
+
},
244
+
},
245
+
},
246
+
FmTealAlphaPlay: {
247
+
lexicon: 1,
248
+
id: 'fm.teal.alpha.play',
249
+
description:
250
+
"This lexicon is in a not officially released state. It is subject to change. | A declaration of a teal.fm play. Plays are submitted as a result of a user listening to a track. Plays should be marked as tracked when a user has listened to the entire track if it's under 2 minutes long, or half of the track's duration up to 4 minutes, whichever is longest.",
251
+
defs: {
252
+
main: {
253
+
type: 'record',
254
+
key: 'tid',
255
+
record: {
256
+
type: 'object',
257
+
required: ['trackName', 'artistName'],
258
+
properties: {
259
+
trackName: {
260
+
type: 'string',
261
+
minLength: 1,
262
+
maxLength: 256,
263
+
maxGraphemes: 2560,
264
+
description: 'The name of the track',
265
+
},
266
+
trackMbId: {
267
+
type: 'string',
268
+
description: 'The Musicbrainz ID of the track',
269
+
},
270
+
recordingMbId: {
271
+
type: 'string',
272
+
description: 'The Musicbrainz recording ID of the track',
273
+
},
274
+
duration: {
275
+
type: 'integer',
276
+
description: 'The length of the track in seconds',
277
+
},
278
+
artistName: {
279
+
type: 'string',
280
+
minLength: 1,
281
+
maxLength: 256,
282
+
maxGraphemes: 2560,
283
+
description: 'The name of the artist',
284
+
},
285
+
artistMbIds: {
286
+
type: 'array',
287
+
items: {
288
+
type: 'string',
289
+
},
290
+
description: 'Array of Musicbrainz artist IDs',
291
+
},
292
+
releaseName: {
293
+
type: 'string',
294
+
maxLength: 256,
295
+
maxGraphemes: 2560,
296
+
description: 'The name of the release/album',
297
+
},
298
+
releaseMbId: {
299
+
type: 'string',
300
+
description: 'The Musicbrainz release ID',
301
+
},
302
+
isrc: {
303
+
type: 'string',
304
+
description: 'The ISRC code associated with the recording',
305
+
},
306
+
originUrl: {
307
+
type: 'string',
308
+
description: 'The URL associated with this track',
309
+
},
310
+
musicServiceBaseDomain: {
311
+
type: 'string',
312
+
description:
313
+
'The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com.',
314
+
},
315
+
submissionClientAgent: {
316
+
type: 'string',
317
+
maxLength: 256,
318
+
maxGraphemes: 2560,
319
+
description:
320
+
'A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b',
321
+
},
322
+
playedTime: {
323
+
type: 'string',
324
+
format: 'datetime',
325
+
description: 'The unix timestamp of when the track was played',
326
+
},
327
+
},
328
+
},
329
+
},
330
+
},
331
+
},
62
332
XyzStatusphereStatus: {
63
333
lexicon: 1,
64
334
id: 'xyz.statusphere.status',
···
90
360
export const lexicons: Lexicons = new Lexicons(schemas)
91
361
export const ids = {
92
362
AppBskyActorProfile: 'app.bsky.actor.profile',
363
+
AppBskyRichtextFacet: 'app.bsky.richtext.facet',
364
+
FmTealAlphaActorProfile: 'fm.teal.alpha.actor.profile',
365
+
FmTealAlphaActorStatus: 'fm.teal.alpha.actor.status',
366
+
FmTealAlphaPlay: 'fm.teal.alpha.play',
93
367
XyzStatusphereStatus: 'xyz.statusphere.status',
94
368
}
+98
packages/lexicons/generated/server/types/app/bsky/richtext/facet.ts
+98
packages/lexicons/generated/server/types/app/bsky/richtext/facet.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
/** Annotation of a sub-string within rich text. */
10
+
export interface Main {
11
+
index: ByteSlice
12
+
features: (Mention | Link | Tag | { $type: string; [k: string]: unknown })[]
13
+
[k: string]: unknown
14
+
}
15
+
16
+
export function isMain(v: unknown): v is Main {
17
+
return (
18
+
isObj(v) &&
19
+
hasProp(v, '$type') &&
20
+
(v.$type === 'app.bsky.richtext.facet#main' ||
21
+
v.$type === 'app.bsky.richtext.facet')
22
+
)
23
+
}
24
+
25
+
export function validateMain(v: unknown): ValidationResult {
26
+
return lexicons.validate('app.bsky.richtext.facet#main', v)
27
+
}
28
+
29
+
/** Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID. */
30
+
export interface Mention {
31
+
did: string
32
+
[k: string]: unknown
33
+
}
34
+
35
+
export function isMention(v: unknown): v is Mention {
36
+
return (
37
+
isObj(v) &&
38
+
hasProp(v, '$type') &&
39
+
v.$type === 'app.bsky.richtext.facet#mention'
40
+
)
41
+
}
42
+
43
+
export function validateMention(v: unknown): ValidationResult {
44
+
return lexicons.validate('app.bsky.richtext.facet#mention', v)
45
+
}
46
+
47
+
/** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. */
48
+
export interface Link {
49
+
uri: string
50
+
[k: string]: unknown
51
+
}
52
+
53
+
export function isLink(v: unknown): v is Link {
54
+
return (
55
+
isObj(v) &&
56
+
hasProp(v, '$type') &&
57
+
v.$type === 'app.bsky.richtext.facet#link'
58
+
)
59
+
}
60
+
61
+
export function validateLink(v: unknown): ValidationResult {
62
+
return lexicons.validate('app.bsky.richtext.facet#link', v)
63
+
}
64
+
65
+
/** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). */
66
+
export interface Tag {
67
+
tag: string
68
+
[k: string]: unknown
69
+
}
70
+
71
+
export function isTag(v: unknown): v is Tag {
72
+
return (
73
+
isObj(v) && hasProp(v, '$type') && v.$type === 'app.bsky.richtext.facet#tag'
74
+
)
75
+
}
76
+
77
+
export function validateTag(v: unknown): ValidationResult {
78
+
return lexicons.validate('app.bsky.richtext.facet#tag', v)
79
+
}
80
+
81
+
/** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. */
82
+
export interface ByteSlice {
83
+
byteStart: number
84
+
byteEnd: number
85
+
[k: string]: unknown
86
+
}
87
+
88
+
export function isByteSlice(v: unknown): v is ByteSlice {
89
+
return (
90
+
isObj(v) &&
91
+
hasProp(v, '$type') &&
92
+
v.$type === 'app.bsky.richtext.facet#byteSlice'
93
+
)
94
+
}
95
+
96
+
export function validateByteSlice(v: unknown): ValidationResult {
97
+
return lexicons.validate('app.bsky.richtext.facet#byteSlice', v)
98
+
}
+56
packages/lexicons/generated/server/types/fm/teal/alpha/actor/profile.ts
+56
packages/lexicons/generated/server/types/fm/teal/alpha/actor/profile.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as AppBskyRichtextFacet from '../../../../app/bsky/richtext/facet'
9
+
10
+
export interface Record {
11
+
displayName?: string
12
+
/** Free-form profile description text. */
13
+
description?: string
14
+
/** Annotations of text in the profile description (mentions, URLs, hashtags, etc). */
15
+
descriptionFacets?: AppBskyRichtextFacet.Main[]
16
+
featuredItem?: FeaturedItem
17
+
/** Small image to be displayed next to posts from account. AKA, 'profile picture' */
18
+
avatar?: BlobRef
19
+
/** Larger horizontal image to display behind profile view. */
20
+
banner?: BlobRef
21
+
createdAt?: string
22
+
[k: string]: unknown
23
+
}
24
+
25
+
export function isRecord(v: unknown): v is Record {
26
+
return (
27
+
isObj(v) &&
28
+
hasProp(v, '$type') &&
29
+
(v.$type === 'fm.teal.alpha.actor.profile#main' ||
30
+
v.$type === 'fm.teal.alpha.actor.profile')
31
+
)
32
+
}
33
+
34
+
export function validateRecord(v: unknown): ValidationResult {
35
+
return lexicons.validate('fm.teal.alpha.actor.profile#main', v)
36
+
}
37
+
38
+
export interface FeaturedItem {
39
+
/** The Musicbrainz ID of the item */
40
+
mbid: string
41
+
/** The type of the item. Must be a valid Musicbrainz type, e.g. album, track, recording, etc. */
42
+
type: string
43
+
[k: string]: unknown
44
+
}
45
+
46
+
export function isFeaturedItem(v: unknown): v is FeaturedItem {
47
+
return (
48
+
isObj(v) &&
49
+
hasProp(v, '$type') &&
50
+
v.$type === 'fm.teal.alpha.actor.profile#featuredItem'
51
+
)
52
+
}
53
+
54
+
export function validateFeaturedItem(v: unknown): ValidationResult {
55
+
return lexicons.validate('fm.teal.alpha.actor.profile#featuredItem', v)
56
+
}
+28
packages/lexicons/generated/server/types/fm/teal/alpha/actor/status.ts
+28
packages/lexicons/generated/server/types/fm/teal/alpha/actor/status.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
import * as FmTealAlphaPlay from '../play'
9
+
10
+
export interface Record {
11
+
/** The unix timestamp of when the item was recorded */
12
+
time: string
13
+
item: FmTealAlphaPlay.Record | { $type: string; [k: string]: unknown }
14
+
[k: string]: unknown
15
+
}
16
+
17
+
export function isRecord(v: unknown): v is Record {
18
+
return (
19
+
isObj(v) &&
20
+
hasProp(v, '$type') &&
21
+
(v.$type === 'fm.teal.alpha.actor.status#main' ||
22
+
v.$type === 'fm.teal.alpha.actor.status')
23
+
)
24
+
}
25
+
26
+
export function validateRecord(v: unknown): ValidationResult {
27
+
return lexicons.validate('fm.teal.alpha.actor.status#main', v)
28
+
}
+49
packages/lexicons/generated/server/types/fm/teal/alpha/play.ts
+49
packages/lexicons/generated/server/types/fm/teal/alpha/play.ts
···
1
+
/**
2
+
* GENERATED CODE - DO NOT MODIFY
3
+
*/
4
+
import { ValidationResult, BlobRef } from '@atproto/lexicon'
5
+
import { lexicons } from '../../../../lexicons'
6
+
import { isObj, hasProp } from '../../../../util'
7
+
import { CID } from 'multiformats/cid'
8
+
9
+
export interface Record {
10
+
/** The name of the track */
11
+
trackName: string
12
+
/** The Musicbrainz ID of the track */
13
+
trackMbId?: string
14
+
/** The Musicbrainz recording ID of the track */
15
+
recordingMbId?: string
16
+
/** The length of the track in seconds */
17
+
duration?: number
18
+
/** The name of the artist */
19
+
artistName: string
20
+
/** Array of Musicbrainz artist IDs */
21
+
artistMbIds?: string[]
22
+
/** The name of the release/album */
23
+
releaseName?: string
24
+
/** The Musicbrainz release ID */
25
+
releaseMbId?: string
26
+
/** The ISRC code associated with the recording */
27
+
isrc?: string
28
+
/** The URL associated with this track */
29
+
originUrl?: string
30
+
/** The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com. */
31
+
musicServiceBaseDomain?: string
32
+
/** A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b */
33
+
submissionClientAgent?: string
34
+
/** The unix timestamp of when the track was played */
35
+
playedTime?: string
36
+
[k: string]: unknown
37
+
}
38
+
39
+
export function isRecord(v: unknown): v is Record {
40
+
return (
41
+
isObj(v) &&
42
+
hasProp(v, '$type') &&
43
+
(v.$type === 'fm.teal.alpha.play#main' || v.$type === 'fm.teal.alpha.play')
44
+
)
45
+
}
46
+
47
+
export function validateRecord(v: unknown): ValidationResult {
48
+
return lexicons.validate('fm.teal.alpha.play#main', v)
49
+
}
+51
packages/lexicons/src/app.bsky.richtext.facet.json
+51
packages/lexicons/src/app.bsky.richtext.facet.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "app.bsky.richtext.facet",
4
+
"defs": {
5
+
"main": {
6
+
"type": "object",
7
+
"description": "Annotation of a sub-string within rich text.",
8
+
"required": ["index", "features"],
9
+
"properties": {
10
+
"index": { "type": "ref", "ref": "#byteSlice" },
11
+
"features": {
12
+
"type": "array",
13
+
"items": { "type": "union", "refs": ["#mention", "#link", "#tag"] }
14
+
}
15
+
}
16
+
},
17
+
"mention": {
18
+
"type": "object",
19
+
"description": "Facet feature for mention of another account. The text is usually a handle, including a '@' prefix, but the facet reference is a DID.",
20
+
"required": ["did"],
21
+
"properties": {
22
+
"did": { "type": "string", "format": "did" }
23
+
}
24
+
},
25
+
"link": {
26
+
"type": "object",
27
+
"description": "Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL.",
28
+
"required": ["uri"],
29
+
"properties": {
30
+
"uri": { "type": "string", "format": "uri" }
31
+
}
32
+
},
33
+
"tag": {
34
+
"type": "object",
35
+
"description": "Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags').",
36
+
"required": ["tag"],
37
+
"properties": {
38
+
"tag": { "type": "string", "maxLength": 640, "maxGraphemes": 64 }
39
+
}
40
+
},
41
+
"byteSlice": {
42
+
"type": "object",
43
+
"description": "Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets.",
44
+
"required": ["byteStart", "byteEnd"],
45
+
"properties": {
46
+
"byteStart": { "type": "integer", "minimum": 0 },
47
+
"byteEnd": { "type": "integer", "minimum": 0 }
48
+
}
49
+
}
50
+
}
51
+
}
+64
packages/lexicons/src/fm.teal.alpha.actor.profile.json
+64
packages/lexicons/src/fm.teal.alpha.actor.profile.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "fm.teal.alpha.actor.profile",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "This lexicon is in a not officially released state. It is subject to change. | A declaration of a teal.fm account profile.",
8
+
"key": "literal:self",
9
+
"record": {
10
+
"type": "object",
11
+
"properties": {
12
+
"displayName": {
13
+
"type": "string",
14
+
"maxGraphemes": 64,
15
+
"maxLength": 640
16
+
},
17
+
"description": {
18
+
"type": "string",
19
+
"description": "Free-form profile description text.",
20
+
"maxGraphemes": 256,
21
+
"maxLength": 2560
22
+
},
23
+
"descriptionFacets": {
24
+
"type": "array",
25
+
"description": "Annotations of text in the profile description (mentions, URLs, hashtags, etc).",
26
+
"items": { "type": "ref", "ref": "app.bsky.richtext.facet" }
27
+
},
28
+
"featuredItem": {
29
+
"type": "ref",
30
+
"description": "The user's most recent item featured on their profile.",
31
+
"ref": "#featuredItem"
32
+
},
33
+
"avatar": {
34
+
"type": "blob",
35
+
"description": "Small image to be displayed next to posts from account. AKA, 'profile picture'",
36
+
"accept": ["image/png", "image/jpeg"],
37
+
"maxSize": 1000000
38
+
},
39
+
"banner": {
40
+
"type": "blob",
41
+
"description": "Larger horizontal image to display behind profile view.",
42
+
"accept": ["image/png", "image/jpeg"],
43
+
"maxSize": 1000000
44
+
},
45
+
"createdAt": { "type": "string", "format": "datetime" }
46
+
}
47
+
}
48
+
},
49
+
"featuredItem": {
50
+
"type": "object",
51
+
"required": ["mbid", "type"],
52
+
"properties": {
53
+
"mbid": {
54
+
"type": "string",
55
+
"description": "The Musicbrainz ID of the item"
56
+
},
57
+
"type": {
58
+
"type": "string",
59
+
"description": "The type of the item. Must be a valid Musicbrainz type, e.g. album, track, recording, etc."
60
+
}
61
+
}
62
+
}
63
+
}
64
+
}
+23
packages/lexicons/src/fm.teal.alpha.actor.status.json
+23
packages/lexicons/src/fm.teal.alpha.actor.status.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "fm.teal.alpha.actor.status",
4
+
"defs": {
5
+
"main": {
6
+
"type": "record",
7
+
"description": "This lexicon is in a not officially released state. It is subject to change. | A declaration of the status of the actor. Only one can be shown at a time. If there are multiple, the latest record should be picked and earlier records should be deleted or tombstoned.",
8
+
"key": "literal:self",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["time", "item"],
12
+
"properties": {
13
+
"time": {
14
+
"type": "string",
15
+
"format": "datetime",
16
+
"description": "The unix timestamp of when the item was recorded"
17
+
},
18
+
"item": { "type": "union", "refs": ["fm.teal.alpha.play#record"] }
19
+
}
20
+
}
21
+
}
22
+
}
23
+
}
+84
packages/lexicons/src/fm.teal.alpha.play.json
+84
packages/lexicons/src/fm.teal.alpha.play.json
···
1
+
{
2
+
"lexicon": 1,
3
+
"id": "fm.teal.alpha.play",
4
+
"description": "This lexicon is in a not officially released state. It is subject to change. | A declaration of a teal.fm play. Plays are submitted as a result of a user listening to a track. Plays should be marked as tracked when a user has listened to the entire track if it's under 2 minutes long, or half of the track's duration up to 4 minutes, whichever is longest.",
5
+
"defs": {
6
+
"main": {
7
+
"type": "record",
8
+
"key": "tid",
9
+
"record": {
10
+
"type": "object",
11
+
"required": ["trackName", "artistName"],
12
+
"properties": {
13
+
"trackName": {
14
+
"type": "string",
15
+
"minLength": 1,
16
+
"maxLength": 256,
17
+
"maxGraphemes": 2560,
18
+
"description": "The name of the track"
19
+
},
20
+
"trackMbId": {
21
+
"type": "string",
22
+
23
+
"description": "The Musicbrainz ID of the track"
24
+
},
25
+
"recordingMbId": {
26
+
"type": "string",
27
+
"description": "The Musicbrainz recording ID of the track"
28
+
},
29
+
"duration": {
30
+
"type": "integer",
31
+
"description": "The length of the track in seconds"
32
+
},
33
+
"artistName": {
34
+
"type": "string",
35
+
"minLength": 1,
36
+
"maxLength": 256,
37
+
"maxGraphemes": 2560,
38
+
"description": "The name of the artist"
39
+
},
40
+
"artistMbIds": {
41
+
"type": "array",
42
+
"items": {
43
+
"type": "string"
44
+
},
45
+
"description": "Array of Musicbrainz artist IDs"
46
+
},
47
+
"releaseName": {
48
+
"type": "string",
49
+
"maxLength": 256,
50
+
"maxGraphemes": 2560,
51
+
"description": "The name of the release/album"
52
+
},
53
+
"releaseMbId": {
54
+
"type": "string",
55
+
"description": "The Musicbrainz release ID"
56
+
},
57
+
"isrc": {
58
+
"type": "string",
59
+
"description": "The ISRC code associated with the recording"
60
+
},
61
+
"originUrl": {
62
+
"type": "string",
63
+
"description": "The URL associated with this track"
64
+
},
65
+
"musicServiceBaseDomain": {
66
+
"type": "string",
67
+
"description": "The base domain of the music service. e.g. music.apple.com, tidal.com, spotify.com."
68
+
},
69
+
"submissionClientAgent": {
70
+
"type": "string",
71
+
"maxLength": 256,
72
+
"maxGraphemes": 2560,
73
+
"description": "A user-agent style string specifying the user agent. e.g. tealtracker/0.0.1b"
74
+
},
75
+
"playedTime": {
76
+
"type": "string",
77
+
"format": "datetime",
78
+
"description": "The unix timestamp of when the track was played"
79
+
}
80
+
}
81
+
}
82
+
}
83
+
}
84
+
}