A decentralized music tracking and discovery platform built on AT Protocol 🎵

feat: add feed generator lexicon and related types

+170 -5
+47
apps/api/lexicons/feed/generator.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "app.rocksky.feed.generator", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.", 8 + "key": "tid", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "did", 13 + "displayName", 14 + "createdAt" 15 + ], 16 + "properties": { 17 + "did": { 18 + "type": "string", 19 + "format": "did" 20 + }, 21 + "avatar": { 22 + "type": "blob", 23 + "accept": [ 24 + "image/png", 25 + "image/jpeg" 26 + ], 27 + "maxSize": 1000000 28 + }, 29 + "displayName": { 30 + "type": "string", 31 + "maxGraphemes": 24, 32 + "maxLength": 240 33 + }, 34 + "description": { 35 + "type": "string", 36 + "maxGraphemes": 300, 37 + "maxLength": 3000 38 + }, 39 + "createdAt": { 40 + "type": "string", 41 + "format": "datetime" 42 + } 43 + } 44 + } 45 + } 46 + } 47 + }
+1
apps/api/package.json
··· 69 69 "nats": "^2.29.2", 70 70 "node-cron": "^4.2.1", 71 71 "pg": "^8.13.3", 72 + "prompts": "^2.4.2", 72 73 "ramda": "^0.30.1", 73 74 "redis": "^4.7.0", 74 75 "unstorage": "^1.14.4",
+41
apps/api/pkl/defs/feed/generator.pkl
··· 1 + amends "../../schema/lexicon.pkl" 2 + 3 + lexicon = 1 4 + id = "app.rocksky.feed.generator" 5 + defs = new Mapping<String, Record> { 6 + ["main"] { 7 + type = "record" 8 + key = "tid" 9 + description = "Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository." 10 + `record` { 11 + type = "object" 12 + required = List("did", "displayName", "createdAt") 13 + properties { 14 + ["did"] = new StringType { 15 + type = "string" 16 + format = "did" 17 + } 18 + ["avatar"] = new Blob { 19 + type = "blob" 20 + accept = List("image/png", "image/jpeg") 21 + maxSize = 1000000 22 + } 23 + ["displayName"] = new StringType { 24 + type = "string" 25 + maxLength = 240 26 + maxGraphemes = 24 27 + } 28 + ["description"] = new StringType { 29 + type = "string" 30 + maxLength = 3000 31 + maxGraphemes = 300 32 + } 33 + ["createdAt"] = new StringType { 34 + type = "string" 35 + format = "datetime" 36 + } 37 + } 38 + } 39 + } 40 + } 41 +
+1 -1
apps/api/pkl/schema/lexicon.pkl
··· 9 9 maxGraphemes: Int? 10 10 minLength: Int? 11 11 maxLength: Int? 12 - format: "uri" | "datetime" | "cid" | "at-uri" | "at-identifier" | Null = null 12 + format: "uri" | "datetime" | "cid" | "at-uri" | "at-identifier" | "did" | Null = null 13 13 } 14 14 15 15 class IntegerType extends BaseType {
+42
apps/api/src/lexicon/lexicons.ts
··· 2274 2274 }, 2275 2275 }, 2276 2276 }, 2277 + AppRockskyFeedGenerator: { 2278 + lexicon: 1, 2279 + id: "app.rocksky.feed.generator", 2280 + defs: { 2281 + main: { 2282 + type: "record", 2283 + description: 2284 + "Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository.", 2285 + key: "tid", 2286 + record: { 2287 + type: "object", 2288 + required: ["did", "displayName", "createdAt"], 2289 + properties: { 2290 + did: { 2291 + type: "string", 2292 + format: "did", 2293 + }, 2294 + avatar: { 2295 + type: "blob", 2296 + accept: ["image/png", "image/jpeg"], 2297 + maxSize: 1000000, 2298 + }, 2299 + displayName: { 2300 + type: "string", 2301 + maxGraphemes: 24, 2302 + maxLength: 240, 2303 + }, 2304 + description: { 2305 + type: "string", 2306 + maxGraphemes: 300, 2307 + maxLength: 3000, 2308 + }, 2309 + createdAt: { 2310 + type: "string", 2311 + format: "datetime", 2312 + }, 2313 + }, 2314 + }, 2315 + }, 2316 + }, 2317 + }, 2277 2318 AppRockskyFeedGetNowPlayings: { 2278 2319 lexicon: 1, 2279 2320 id: "app.rocksky.feed.getNowPlayings", ··· 5081 5122 AppRockskyDropboxGetMetadata: "app.rocksky.dropbox.getMetadata", 5082 5123 AppRockskyDropboxGetTemporaryLink: "app.rocksky.dropbox.getTemporaryLink", 5083 5124 AppRockskyFeedDefs: "app.rocksky.feed.defs", 5125 + AppRockskyFeedGenerator: "app.rocksky.feed.generator", 5084 5126 AppRockskyFeedGetNowPlayings: "app.rocksky.feed.getNowPlayings", 5085 5127 AppRockskyFeedSearch: "app.rocksky.feed.search", 5086 5128 AppRockskyGoogledriveDefs: "app.rocksky.googledrive.defs",
+29
apps/api/src/lexicon/types/app/rocksky/feed/generator.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import type { 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 + did: string; 11 + avatar?: BlobRef; 12 + displayName: string; 13 + description?: string; 14 + createdAt: string; 15 + [k: string]: unknown; 16 + } 17 + 18 + export function isRecord(v: unknown): v is Record { 19 + return ( 20 + isObj(v) && 21 + hasProp(v, "$type") && 22 + (v.$type === "app.rocksky.feed.generator#main" || 23 + v.$type === "app.rocksky.feed.generator") 24 + ); 25 + } 26 + 27 + export function validateRecord(v: unknown): ValidationResult { 28 + return lexicons.validate("app.rocksky.feed.generator#main", v); 29 + }
apps/api/src/scripts/feed.ts

This is a binary file and will not be displayed.

+4 -4
apps/api/src/tealfm/index.ts
··· 22 22 async function publishPlayingNow( 23 23 agent: Agent, 24 24 track: MusicbrainzTrack, 25 - duration: number 25 + duration: number, 26 26 ) { 27 27 try { 28 28 // wait 60 seconds to ensure the track is actually being played ··· 36 36 // diff in seconds less than 60 37 37 Math.abs( 38 38 new Date(record.playedTime).getTime() - 39 - new Date(track.timestamp).getTime() 39 + new Date(track.timestamp).getTime(), 40 40 ) < 60000 41 41 ); 42 42 }); 43 43 if (alreadyPlayed) { 44 44 console.log( 45 45 `Track ${chalk.cyan(track.name)} by ${chalk.cyan( 46 - track.artist.map((a) => a.name).join(", ") 47 - )} already played recently. Skipping...` 46 + track.artist.map((a) => a.name).join(", "), 47 + )} already played recently. Skipping...`, 48 48 ); 49 49 return; 50 50 }
+5
bun.lock
··· 59 59 "nats": "^2.29.2", 60 60 "node-cron": "^4.2.1", 61 61 "pg": "^8.13.3", 62 + "prompts": "^2.4.2", 62 63 "ramda": "^0.30.1", 63 64 "redis": "^4.7.0", 64 65 "unstorage": "^1.14.4", ··· 2361 2362 2362 2363 "process-warning": ["process-warning@3.0.0", "", {}, "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ=="], 2363 2364 2365 + "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="], 2366 + 2364 2367 "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="], 2365 2368 2366 2369 "prop-types-extra": ["prop-types-extra@1.1.1", "", { "dependencies": { "react-is": "^16.3.2", "warning": "^4.0.0" }, "peerDependencies": { "react": ">=0.14.0" } }, "sha512-59+AHNnHYCdiC+vMwY52WmvP5dM3QLeoumYuEyceQDi9aEhtwN9zIQ2ZNo25sMyXnbh32h+P1ezDsUpUH3JAew=="], ··· 2564 2567 "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="], 2565 2568 2566 2569 "sirv": ["sirv@3.0.1", "", { "dependencies": { "@polka/url": "^1.0.0-next.24", "mrmime": "^2.0.0", "totalist": "^3.0.0" } }, "sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A=="], 2570 + 2571 + "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], 2567 2572 2568 2573 "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], 2569 2574