A decentralized music tracking and discovery platform built on AT Protocol 🎵
at main 138 lines 4.4 kB view raw
1import { Hono } from "hono"; 2import { ctx } from "context"; 3import users from "schema/users"; 4import albums, { type SelectAlbum } from "schema/albums"; 5import artists, { type SelectArtist } from "schema/artists"; 6import tracks, { type SelectTrack } from "schema/tracks"; 7import scrobbles from "schema/scrobbles"; 8import { eq, or } from "drizzle-orm"; 9 10const app = new Hono(); 11 12app.get("/", async (c) => { 13 const path = c.req.query("path"); 14 15 if (!path) { 16 return c.text("OG Service: please provide a path query parameter.", 400); 17 } 18 19 let m = path.match(/^\/profile\/([^/]+)$/); 20 if (m) { 21 const handle = decodeURIComponent(m[1]); 22 const user = await ctx.db 23 .select() 24 .from(users) 25 .where(or(eq(users.handle, handle), eq(users.did, handle))) 26 .limit(1) 27 .execute() 28 .then(([row]) => row); 29 if (!user) { 30 return c.text("OG Service: user not found.", 404); 31 } 32 return c.json({ 33 title: `@${user.handle} on Rocksky`, 34 description: 35 "Rocksky user profile — recent scrobbles, top artists, albums, and tracks.", 36 image: user.avatar.endsWith("/@jpeg") ? undefined : user.avatar, 37 url: `https://rocksky.app/profile/${user.handle}`, 38 type: "website", 39 twitterCard: "summary_large_image", 40 }); 41 } 42 43 m = path.match(/^\/(did:plc:[^/]+)\/(scrobble|album|artist|song)\/([^/]+)$/); 44 if (m) { 45 const did = decodeURIComponent(m[1]); 46 const kind = m[2] as "scrobble" | "album" | "artist" | "song"; 47 const rkey = decodeURIComponent(m[3]); 48 const uri = `at://${did}/app.rocksky.${kind}/${rkey}`; 49 50 const tableMap = { 51 scrobble: scrobbles, 52 album: albums, 53 artist: artists, 54 song: tracks, 55 }; 56 57 const table = tableMap[kind]; 58 if (kind === "scrobble") { 59 const record = await ctx.db 60 .select({ 61 scrobbles: scrobbles, 62 users: users, 63 tracks: tracks, 64 artists: artists, 65 albums: albums, 66 }) 67 .from(table) 68 .where(eq(table.uri, uri)) 69 .leftJoin(users, eq(scrobbles.userId, users.id)) 70 .leftJoin(tracks, eq(scrobbles.trackId, tracks.id)) 71 .leftJoin(artists, eq(scrobbles.artistId, artists.id)) 72 .leftJoin(albums, eq(scrobbles.albumId, albums.id)) 73 .limit(1) 74 .execute() 75 .then(([row]) => row); 76 77 if (!record) { 78 return c.text("OG Service: record not found.", 404); 79 } 80 81 return c.json({ 82 title: `Scrobble: ${record.tracks.title} by ${record.artists.name}`, 83 description: `A listening activity (scrobble) - ${record.tracks.title} - ${record.artists.name} by @${record.users.handle} on Rocksky.`, 84 image: record.albums.albumArt, 85 url: `https://rocksky.app/${did}/scrobble/${rkey}`, 86 type: "website", 87 twitterCard: "summary_large_image", 88 }); 89 } 90 91 const record = await ctx.db 92 .select() 93 .from(table) 94 .where(eq(table.uri, uri)) 95 .limit(1) 96 .execute() 97 .then(([row]) => row); 98 if (!record) { 99 return c.text("OG Service: record not found.", 404); 100 } 101 102 let title; 103 let description; 104 let image; 105 const url = `https://rocksky.app/${did}/${kind}/${rkey}`; 106 107 if (kind === "album") { 108 title = `Album: ${(record as SelectAlbum).title} by ${(record as SelectAlbum).artist}`; 109 description = `See listening stats and favorites for ${(record as SelectAlbum).title} by ${(record as SelectAlbum).artist} on Rocksky.`; 110 image = (record as SelectAlbum).albumArt; 111 } 112 113 if (kind === "artist") { 114 title = `Artist: ${(record as SelectArtist).name}`; 115 description = `See listening stats and favorites for ${(record as SelectArtist).name} on Rocksky.`; 116 image = (record as SelectArtist).picture; 117 } 118 119 if (kind === "song") { 120 title = `Track: ${(record as SelectTrack).title} by ${(record as SelectTrack).artist}`; 121 description = `See listening stats and favorites for ${(record as SelectTrack).title} by ${(record as SelectTrack).artist} on Rocksky.`; 122 image = (record as SelectTrack).albumArt; 123 } 124 125 return c.json({ 126 title, 127 description, 128 image, 129 url, 130 type: "website", 131 twitterCard: "summary_large_image", 132 }); 133 } 134 135 return c.text("OG Service: unsupported path format.", 400); 136}); 137 138export default app;