Import your Last.fm and Spotify listening history to the AT Protocol network using the fm.teal.alpha.feed.play lexicon.
0
fork

Configure Feed

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

fix: generate spec-compliant TIDs with proper padding

- Replace TID.fromTime usage with manual s32 encoding
- Pad timestamp and clock ID to required 13-character TID length
- Ensure deterministic TID generation from millisecond-precision clocks
- Correct spec reference and documentation

+31 -9
+31 -9
src/utils/tid.ts
··· 1 1 /** 2 2 * TID (Timestamp Identifier) generation for ATProto 3 - * Based on: https://atproto.com/specs/record-key#record-key-type-tid 3 + * Based on: https://atproto.com/specs/tid 4 4 * 5 - * This uses the official ATProto TID implementation from @atproto/common-web 6 - * to ensure compatibility and avoid precision issues with large numbers. 5 + * Per the spec: "If the local clock has only millisecond precision, the timestamp 6 + * should be padded." Our implementation pads to ensure 11 characters for the timestamp 7 + * portion and 2 characters for the clockid, for a total of exactly 13 characters. 8 + * 9 + * This implementation uses the official ATProto s32encode function and properly handles 10 + * historical dates (like 2005 scrobbles) by padding to the required length. 7 11 */ 8 12 9 - import { TID } from '@atproto/common-web'; 13 + import { s32encode } from '@atproto/common-web/dist/util.js'; 10 14 11 15 /** 12 16 * Generate a TID from a Date object 13 - * Uses the official ATProto TID.fromTime method 17 + * 18 + * TID format (13 characters total): 19 + * - Characters 0-10: timestamp in microseconds, base32-encoded 20 + * - Characters 11-12: clock ID, base32-encoded 21 + * 22 + * The timestamp is padded with '2' (representing 0 in base32) to ensure exactly 11 characters. 23 + * The clockid is similarly padded to ensure exactly 2 characters. 24 + * 25 + * @param date - The date to generate a TID from 26 + * @returns A valid 13-character TID string 14 27 */ 15 28 export function generateTID(date: Date): string { 16 - // Convert to Unix microseconds 17 - // The ATProto implementation expects microseconds 29 + // Convert to Unix microseconds (JS Date.getTime() returns milliseconds) 30 + // Per spec: multiply by 1000 to pad millisecond precision 18 31 const unixMicros = date.getTime() * 1000; 19 32 20 33 // Use a fixed clockid of 0 for deterministic TID generation from timestamps 21 - // This ensures the same playedTime always generates the same TID 34 + // This ensures the same playedTime always generates the same TID (important for deduplication) 22 35 const clockid = 0; 23 36 24 - return TID.fromTime(unixMicros, clockid).toString(); 37 + // Encode timestamp and clockid with proper padding 38 + // Timestamp should be 11 characters, clockid should be 2 characters 39 + // Padding character is '2' which represents 0 in base32 40 + const timestampStr = s32encode(unixMicros).padStart(11, '2'); 41 + const clockidStr = s32encode(clockid).padStart(2, '2'); 42 + 43 + return timestampStr + clockidStr; 25 44 } 26 45 27 46 /** 28 47 * Generate a TID from an ISO 8601 timestamp string 48 + * 49 + * @param isoString - ISO 8601 formatted datetime string 50 + * @returns A valid 13-character TID string 29 51 */ 30 52 export function generateTIDFromISO(isoString: string): string { 31 53 return generateTID(new Date(isoString));