1
+ # Copyright 2024 Ryan Benson
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # https://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import base64
16
+ import re
17
+
18
+ import logging
19
+ log = logging .getLogger (__name__ )
20
+
21
+ bluesky_edge = {
22
+ 'color' : {
23
+ 'color' : '#1185fe'
24
+ },
25
+ 'title' : 'Bluesky TID' ,
26
+ 'label' : '🦋'
27
+ }
28
+
29
+ tid_re = re .compile (r'[2-7a-z]{13}' )
30
+
31
+ # Create a mapping from "base32-sortable" alphabet to standard base32 alphabet
32
+ BASE32_SORTABLE_ALPHABET = "234567abcdefghijklmnopqrstuvwxyz"
33
+ STANDARD_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"
34
+ BASE32_SORTABLE_TRANS = str .maketrans (BASE32_SORTABLE_ALPHABET , STANDARD_ALPHABET )
35
+
36
+ def parse_bluesky_tid (unfurl , node ):
37
+ # Ref: https://atproto.com/specs/record-key#record-key-type-tid
38
+ assert tid_re .fullmatch (node .value ), "Bluesky TID is not in the expected format (base32-sortable)"
39
+ assert not ord (node .value [0 ]) & 0x40 , "Bluesky TID high bit is set; it must be 0"
40
+
41
+ # Translate the base32-sortable string to standard base32, then decode it to 8 raw bytes
42
+ translated_str = node .value .translate (BASE32_SORTABLE_TRANS )
43
+ decoded_bytes = base64 .b32decode (translated_str + "===" )
44
+
45
+ # The first bit is 0, then the next 53 bits are the timestamp (microseconds since the UNIX epoch).
46
+ # The last 10 are a random "clock identifier", so shift those out to get the timestamp.
47
+ timestamp = int .from_bytes (decoded_bytes , byteorder = "big" ) >> 9
48
+
49
+ unfurl .add_to_queue (
50
+ data_type = 'epoch-microseconds' , key = None , value = timestamp , label = f'TID Timestamp: { timestamp } ' ,
51
+ hover = 'Bluesky uses <i>timestamp identifiers</i> ("TIDs") as a way to reference records, '
52
+ 'which contain an embedded timestamp.' ,
53
+ parent_id = node .node_id , incoming_edge_config = bluesky_edge )
54
+
55
+
56
+ def run (unfurl , node ):
57
+ if isinstance (node .value , str ) and re .fullmatch (tid_re , node .value ):
58
+ if node .data_type == 'url.path.segment' :
59
+ preceding_domain = unfurl .find_preceding_domain (node )
60
+ if preceding_domain in ['bsky.app' ]:
61
+ parse_bluesky_tid (unfurl , node )
62
+
63
+ # If it's the "root" node and in the format of a TID, parse it.
64
+ # This case covers someone parsing just an ID, not a full URL.
65
+ elif node .node_id == 1 :
66
+ parse_bluesky_tid (unfurl , node )
0 commit comments