A Wrapped / Replay like for teal.fm and rocksky.app (currently on hiatus)
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

lexicon defs

Mia efb27a70 5296286e

+304 -11
+51 -11
Cargo.lock
··· 1274 1274 "duckdb", 1275 1275 "eyre", 1276 1276 "futures", 1277 + "inventory", 1277 1278 "jacquard", 1278 1279 "jacquard-api", 1280 + "jacquard-common", 1281 + "jacquard-lexicon", 1279 1282 "r2d2", 1280 1283 "reqwest", 1284 + "serde", 1281 1285 "tempfile", 1282 1286 "tokio", 1283 1287 "tokio-util", ··· 1521 1525 version = "0.3.3" 1522 1526 source = "registry+https://github.com/rust-lang/crates.io-index" 1523 1527 checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 1528 + 1529 + [[package]] 1530 + name = "gloo-storage" 1531 + version = "0.3.0" 1532 + source = "registry+https://github.com/rust-lang/crates.io-index" 1533 + checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" 1534 + dependencies = [ 1535 + "gloo-utils", 1536 + "js-sys", 1537 + "serde", 1538 + "serde_json", 1539 + "thiserror 1.0.69", 1540 + "wasm-bindgen", 1541 + "web-sys", 1542 + ] 1543 + 1544 + [[package]] 1545 + name = "gloo-utils" 1546 + version = "0.2.0" 1547 + source = "registry+https://github.com/rust-lang/crates.io-index" 1548 + checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" 1549 + dependencies = [ 1550 + "js-sys", 1551 + "serde", 1552 + "serde_json", 1553 + "wasm-bindgen", 1554 + "web-sys", 1555 + ] 1524 1556 1525 1557 [[package]] 1526 1558 name = "group" ··· 2055 2087 2056 2088 [[package]] 2057 2089 name = "jacquard" 2058 - version = "0.9.0" 2090 + version = "0.9.3" 2059 2091 source = "registry+https://github.com/rust-lang/crates.io-index" 2060 - checksum = "256e82c3a0968cbfe967c47211395dfb92fbfc1b34d288db571b17bf8ab8b15d" 2092 + checksum = "c19864761c8d69d23201fd19dd021cddf1fb7acbebb6e6b50e2b1776ec982768" 2061 2093 dependencies = [ 2062 2094 "bytes", 2063 2095 "getrandom 0.2.16", 2096 + "gloo-storage", 2064 2097 "http", 2065 2098 "jacquard-api", 2066 2099 "jacquard-common", ··· 2070 2103 "jose-jwk", 2071 2104 "miette", 2072 2105 "regex", 2106 + "regex-lite", 2073 2107 "reqwest", 2074 2108 "serde", 2075 2109 "serde_html_form", ··· 2084 2118 2085 2119 [[package]] 2086 2120 name = "jacquard-api" 2087 - version = "0.9.0" 2121 + version = "0.9.2" 2088 2122 source = "registry+https://github.com/rust-lang/crates.io-index" 2089 - checksum = "27ef4a19bb41e8f2ce67e47aa6a3411aa26014c34f31bc8517c109315b2e011a" 2123 + checksum = "bbbfd6e2b10fa1731f4d4e40c8f791956b0d4f804fb3efef891afec903f20597" 2090 2124 dependencies = [ 2091 2125 "bon", 2092 2126 "bytes", ··· 2103 2137 2104 2138 [[package]] 2105 2139 name = "jacquard-common" 2106 - version = "0.9.0" 2140 + version = "0.9.2" 2107 2141 source = "registry+https://github.com/rust-lang/crates.io-index" 2108 - checksum = "7d023139a3ab90528392ac201833a3516579fd6781cf4e70ebe556f61a2d1cb6" 2142 + checksum = "df86cb117d9f1c2b0251ba67c3f0e3f963fd22abc6cf8de0e02a7fc846c288ca" 2109 2143 dependencies = [ 2110 2144 "base64", 2111 2145 "bon", ··· 2128 2162 "p256", 2129 2163 "rand 0.9.2", 2130 2164 "regex", 2165 + "regex-lite", 2131 2166 "reqwest", 2132 2167 "serde", 2133 2168 "serde_html_form", ··· 2185 2220 2186 2221 [[package]] 2187 2222 name = "jacquard-lexicon" 2188 - version = "0.9.1" 2223 + version = "0.9.2" 2189 2224 source = "registry+https://github.com/rust-lang/crates.io-index" 2190 - checksum = "0dc39efada5328f96c49c986ffabac8f3b5d9cd88d06de8b3951cf5ebdfc6378" 2225 + checksum = "de87f2c938faea1b1f1b32d5b9e0c870e7b5bb5efbf96e3692ae2d8f6b2beb7a" 2191 2226 dependencies = [ 2192 2227 "cid", 2193 2228 "dashmap 6.1.0", ··· 2212 2247 2213 2248 [[package]] 2214 2249 name = "jacquard-oauth" 2215 - version = "0.9.0" 2250 + version = "0.9.2" 2216 2251 source = "registry+https://github.com/rust-lang/crates.io-index" 2217 - checksum = "4b384e36205a45b9d1beed428dfd975b6a48b986ac601a20b74b6cceb7896893" 2252 + checksum = "aafe9b4b2160cb57cd48d02d84d2c09706853d098e053baacb06a59fcd59a898" 2218 2253 dependencies = [ 2219 2254 "base64", 2220 2255 "bytes", ··· 2233 2268 "serde_html_form", 2234 2269 "serde_json", 2235 2270 "sha2", 2236 - "signature", 2237 2271 "smol_str", 2238 2272 "thiserror 2.0.17", 2239 2273 "tokio", ··· 3394 3428 "memchr", 3395 3429 "regex-syntax", 3396 3430 ] 3431 + 3432 + [[package]] 3433 + name = "regex-lite" 3434 + version = "0.1.8" 3435 + source = "registry+https://github.com/rust-lang/crates.io-index" 3436 + checksum = "8d942b98df5e658f56f20d592c7f868833fe38115e65c33003d8cd224b0155da" 3397 3437 3398 3438 [[package]] 3399 3439 name = "regex-syntax"
+4
Cargo.toml
··· 12 12 duckdb = { version = "1.4", features = ["bundled", "chrono", "r2d2", "uuid"] } 13 13 eyre = "0.6" 14 14 futures = "0.3.31" 15 + inventory = "0.3.21" 15 16 jacquard = { version = "0.9", default-features = false , features = ["api_bluesky", "derive", "dns", "websocket"] } 16 17 jacquard-api = { version = "0.9", features = ["fm_teal", "app_rocksky"] } 18 + jacquard-common = "0.9" 19 + jacquard-lexicon = "0.9.2" 17 20 r2d2 = "0.8" 18 21 reqwest = "0.12.24" 22 + serde = { version = "1.0.228", features = ["derive"] } 19 23 tempfile = "3.23.0" 20 24 tokio = { version = "1.42", features = ["full"] } 21 25 tokio-util = { version = "0.7.17", features = ["compat"] }
+76
src/lex/mod.rs
··· 1 + use jacquard::{IntoStatic, LexiconSchema}; 2 + use jacquard_common::CowStr; 3 + use serde::{Deserialize, Serialize}; 4 + 5 + pub mod queries; 6 + 7 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 8 + #[lexicon(nsid = "at.parakeet.flashback.summary.summary")] 9 + pub struct Summary<'a> { 10 + #[serde(borrow)] 11 + pub top_albums: Vec<TopAlbumEntry<'a>>, 12 + #[serde(borrow)] 13 + pub top_artists: Vec<TopArtistEntry<'a>>, 14 + #[serde(borrow)] 15 + pub top_tracks: Vec<TopTrackEntry<'a>>, 16 + } 17 + 18 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 19 + #[lexicon(nsid = "at.parakeet.flashback.stats.album")] 20 + pub struct TopAlbumEntry<'a> { 21 + #[serde(borrow)] 22 + pub album: Album<'a>, 23 + pub count: i64, 24 + } 25 + 26 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 27 + #[lexicon(nsid = "at.parakeet.flashback.album")] 28 + pub struct Album<'a> { 29 + #[serde(borrow)] 30 + pub album_name: CowStr<'a>, 31 + #[serde(borrow)] 32 + #[serde(skip_serializing_if = "Option::is_none")] 33 + pub album_mbid: Option<CowStr<'a>>, 34 + #[serde(borrow)] 35 + #[serde(skip_serializing_if = "Option::is_none")] 36 + pub album_art_uri: Option<CowStr<'a>>, 37 + } 38 + 39 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 40 + #[lexicon(nsid = "at.parakeet.flashback.stats.artist")] 41 + pub struct TopArtistEntry<'a> { 42 + #[serde(borrow)] 43 + pub artist: Artist<'a>, 44 + pub count: i64, 45 + } 46 + 47 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 48 + #[lexicon(nsid = "at.parakeet.flashback.album")] 49 + pub struct Artist<'a> { 50 + #[serde(borrow)] 51 + pub artist_name: CowStr<'a>, 52 + #[serde(borrow)] 53 + #[serde(skip_serializing_if = "Option::is_none")] 54 + pub artist_mbid: Option<CowStr<'a>>, 55 + #[serde(borrow)] 56 + #[serde(skip_serializing_if = "Option::is_none")] 57 + pub artist_art_uri: Option<CowStr<'a>>, 58 + } 59 + 60 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 61 + #[lexicon(nsid = "at.parakeet.flashback.stats.track")] 62 + pub struct TopTrackEntry<'a> { 63 + #[serde(borrow)] 64 + pub track: Track<'a>, 65 + pub count: i64, 66 + } 67 + 68 + #[derive(Debug, LexiconSchema, IntoStatic, Serialize, Deserialize)] 69 + #[lexicon(nsid = "at.parakeet.flashback.album")] 70 + pub struct Track<'a> { 71 + #[serde(borrow)] 72 + pub track_name: CowStr<'a>, 73 + #[serde(borrow)] 74 + #[serde(skip_serializing_if = "Option::is_none")] 75 + pub track_mbid: Option<CowStr<'a>>, 76 + }
+172
src/lex/queries.rs
··· 1 + use jacquard::{IntoStatic, XrpcRequest, lexicon}; 2 + use jacquard_api::app_bsky::actor::ProfileViewBasic; 3 + use jacquard_common::types::datetime::Datetime; 4 + use jacquard_common::types::ident::AtIdentifier; 5 + use jacquard_common::xrpc::{XrpcEndpoint, XrpcMethod}; 6 + use serde::{Deserialize, Serialize}; 7 + 8 + #[derive(Debug, XrpcRequest, IntoStatic, Serialize, Deserialize)] 9 + #[xrpc( 10 + nsid = "at.parakeet.flashback.actor.getTopAlbums", 11 + method = Query, 12 + output = GetAnnualSummaryOutput 13 + )] 14 + pub struct GetTopAlbums<'a> { 15 + #[serde(borrow)] 16 + pub actor: AtIdentifier<'a>, 17 + pub start: Datetime, 18 + pub end: Datetime, 19 + pub limit: Option<i64>, 20 + } 21 + 22 + pub struct GetTopAlbumsRequest; 23 + impl XrpcEndpoint for GetTopAlbumsRequest { 24 + const PATH: &'static str = "/xrpc/at.parakeet.flashback.actor.getTopAlbums"; 25 + const METHOD: XrpcMethod = XrpcMethod::Query; 26 + type Request<'de> = GetTopAlbums<'de>; 27 + type Response = GetTopAlbumsResponse; 28 + } 29 + 30 + 31 + #[lexicon] 32 + #[derive(Debug, IntoStatic, Serialize, Deserialize)] 33 + pub struct GetTopAlbumsOutput<'a> { 34 + pub start: Datetime, 35 + pub end: Datetime, 36 + // TODO: should we have our own profile view that amalgamates teal,rocksky,bluesky profiles? 37 + // we really don't even need half the info that we get from bluesky, so, probably... 38 + #[serde(borrow)] 39 + pub profile: ProfileViewBasic<'a>, 40 + pub albums: Vec<super::TopAlbumEntry<'a>>, 41 + } 42 + 43 + #[derive(Debug, XrpcRequest, IntoStatic, Serialize, Deserialize)] 44 + #[xrpc( 45 + nsid = "at.parakeet.flashback.actor.getTopArtists", 46 + method = Query, 47 + output = GetTopArtistsOutput 48 + )] 49 + pub struct GetTopArtists<'a> { 50 + #[serde(borrow)] 51 + pub actor: AtIdentifier<'a>, 52 + pub start: Datetime, 53 + pub end: Datetime, 54 + pub limit: Option<i64>, 55 + } 56 + 57 + pub struct GetTopArtistsRequest; 58 + impl XrpcEndpoint for GetTopArtistsRequest { 59 + const PATH: &'static str = "/xrpc/at.parakeet.flashback.actor.getTopArtists"; 60 + const METHOD: XrpcMethod = XrpcMethod::Query; 61 + type Request<'de> = GetTopArtists<'de>; 62 + type Response = GetTopArtistsResponse; 63 + } 64 + 65 + #[lexicon] 66 + #[derive(Debug, IntoStatic, Serialize, Deserialize)] 67 + pub struct GetTopArtistsOutput<'a> { 68 + pub start: Datetime, 69 + pub end: Datetime, 70 + // TODO: should we have our own profile view that amalgamates teal,rocksky,bluesky profiles? 71 + // we really don't even need half the info that we get from bluesky, so, probably... 72 + #[serde(borrow)] 73 + pub profile: ProfileViewBasic<'a>, 74 + pub artists: Vec<super::TopArtistEntry<'a>>, 75 + } 76 + 77 + #[derive(Debug, XrpcRequest, IntoStatic, Serialize, Deserialize)] 78 + #[xrpc( 79 + nsid = "at.parakeet.flashback.actor.getTopTracks", 80 + method = Query, 81 + output = GetTopTracksOutput 82 + )] 83 + pub struct GetTopTracks<'a> { 84 + #[serde(borrow)] 85 + pub actor: AtIdentifier<'a>, 86 + pub start: Datetime, 87 + pub end: Datetime, 88 + pub limit: Option<i64>, 89 + } 90 + 91 + pub struct GetTopTracksRequest; 92 + impl XrpcEndpoint for GetTopTracksRequest { 93 + const PATH: &'static str = "/xrpc/at.parakeet.flashback.actor.getTopTracks"; 94 + const METHOD: XrpcMethod = XrpcMethod::Query; 95 + type Request<'de> = GetTopTracks<'de>; 96 + type Response = GetTopTracksResponse; 97 + } 98 + 99 + #[lexicon] 100 + #[derive(Debug, IntoStatic, Serialize, Deserialize)] 101 + pub struct GetTopTracksOutput<'a> { 102 + pub start: Datetime, 103 + pub end: Datetime, 104 + // TODO: should we have our own profile view that amalgamates teal,rocksky,bluesky profiles? 105 + // we really don't even need half the info that we get from bluesky, so, probably... 106 + #[serde(borrow)] 107 + pub profile: ProfileViewBasic<'a>, 108 + pub tracks: Vec<super::TopTrackEntry<'a>>, 109 + } 110 + 111 + #[derive(Debug, XrpcRequest, IntoStatic, Serialize, Deserialize)] 112 + #[xrpc( 113 + nsid = "at.parakeet.flashback.summary.getAnnualSummary", 114 + method = Query, 115 + output = GetAnnualSummaryOutput 116 + )] 117 + pub struct GetAnnualSummary<'a> { 118 + #[serde(borrow)] 119 + pub actor: AtIdentifier<'a>, 120 + pub year: i64, 121 + } 122 + 123 + pub struct GetAnnualSummaryRequest; 124 + impl XrpcEndpoint for GetAnnualSummaryRequest { 125 + const PATH: &'static str = "/xrpc/at.parakeet.flashback.summary.getAnnualSummary"; 126 + const METHOD: XrpcMethod = XrpcMethod::Query; 127 + type Request<'de> = GetAnnualSummary<'de>; 128 + type Response = GetAnnualSummaryResponse; 129 + } 130 + 131 + #[lexicon] 132 + #[derive(Debug, IntoStatic, Serialize, Deserialize)] 133 + pub struct GetAnnualSummaryOutput<'a> { 134 + pub year: i64, 135 + // TODO: should we have our own profile view that amalgamates teal,rocksky,bluesky profiles? 136 + // we really don't even need half the info that we get from bluesky, so, probably... 137 + #[serde(borrow)] 138 + pub profile: ProfileViewBasic<'a>, 139 + pub summary: super::Summary<'a>, 140 + } 141 + 142 + #[derive(Debug, XrpcRequest, IntoStatic, Serialize, Deserialize)] 143 + #[xrpc( 144 + nsid = "at.parakeet.flashback.summary.getPeriodSummary", 145 + method = Query, 146 + output = GetPeriodSummaryOutput 147 + )] 148 + pub struct GetPeriodSummary<'a> { 149 + #[serde(borrow)] 150 + pub actor: AtIdentifier<'a>, 151 + pub start: Datetime, 152 + pub end: Datetime, 153 + } 154 + 155 + pub struct GetPeriodSummaryRequest; 156 + impl XrpcEndpoint for GetPeriodSummaryRequest { 157 + const PATH: &'static str = "/xrpc/at.parakeet.flashback.summary.getPeriodSummary"; 158 + const METHOD: XrpcMethod = XrpcMethod::Query; 159 + type Request<'de> = GetPeriodSummary<'de>; 160 + type Response = GetPeriodSummaryResponse; 161 + } 162 + 163 + #[derive(Debug, IntoStatic, Serialize, Deserialize)] 164 + pub struct GetPeriodSummaryOutput<'a> { 165 + pub start: Datetime, 166 + pub end: Datetime, 167 + // TODO: should we have our own profile view that amalgamates teal,rocksky,bluesky profiles? 168 + // we really don't even need half the info that we get from bluesky, so, probably... 169 + #[serde(borrow)] 170 + pub profile: ProfileViewBasic<'a>, 171 + pub summary: super::Summary<'a>, 172 + }
+1
src/main.rs
··· 4 4 5 5 mod config; 6 6 mod ingest; 7 + mod lex; 7 8 mod mbz; 8 9 mod server; 9 10