···
1
1
-
#!/usr/bin/env python3
2
1
import os
3
2
import json
4
3
import logging
4
4
+
import requests
5
5
+
import magic
5
6
from datetime import datetime
6
7
from dotenv import load_dotenv
7
8
from atproto import Client, models
9
9
+
from atproto.exceptions import BadRequestError
8
10
9
11
# Define the paths
10
12
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
···
24
26
console_handler.setFormatter(formatter)
25
27
logging.getLogger().addHandler(console_handler)
26
28
29
29
+
30
30
+
def ensure_https(url):
31
31
+
if not url.startswith("http://") and not url.startswith("https://"):
32
32
+
return "https://" + url
33
33
+
if url.startswith("http://"):
34
34
+
return "https://" + url[7:]
35
35
+
return url
36
36
+
37
37
+
38
38
+
def is_endpoint_alive(url):
39
39
+
health_url = f"{url.rstrip('/')}/xrpc/_health"
40
40
+
try:
41
41
+
response = requests.get(health_url, timeout=5)
42
42
+
return response.status_code == 200
43
43
+
except requests.RequestException as e:
44
44
+
logging.error(f"Health check failed for {health_url}: {e}")
45
45
+
return False
46
46
+
47
47
+
48
48
+
def fetch_blob(did, cid, endpoint):
49
49
+
url = f"{endpoint}/xrpc/com.atproto.sync.getBlob?did={did}&cid={cid}"
50
50
+
try:
51
51
+
response = requests.get(url, timeout=5)
52
52
+
response.raise_for_status()
53
53
+
return response.content
54
54
+
except requests.RequestException as e:
55
55
+
logging.error(f"Failed to fetch blob {cid} for DID {did}: {e}")
56
56
+
return None
57
57
+
58
58
+
59
59
+
def get_blob_metadata(cid, did, endpoint):
60
60
+
blob_data = fetch_blob(did, cid, endpoint)
61
61
+
if blob_data is None:
62
62
+
return None
63
63
+
64
64
+
mime = magic.Magic(mime=True)
65
65
+
mime_type = mime.from_buffer(blob_data)
66
66
+
size = len(blob_data)
67
67
+
68
68
+
return {
69
69
+
"$type": "blob",
70
70
+
"ref": {"$link": cid},
71
71
+
"mimeType": mime_type,
72
72
+
"size": size,
73
73
+
}
74
74
+
27
75
def main():
28
76
logging.info("Starting avatar update script...")
29
77
···
36
84
endpoint = os.getenv("ENDPOINT")
37
85
handle = os.getenv("HANDLE")
38
86
password = os.getenv("PASSWORD")
87
87
+
did = os.getenv("DID")
39
88
40
40
-
if not (endpoint and handle and password):
41
41
-
logging.error("Missing environment variables. Ensure ENDPOINT, HANDLE, and PASSWORD are set in .env file.")
89
89
+
if not (endpoint and handle and password and did):
90
90
+
logging.error(
91
91
+
"Missing environment variables. Ensure ENDPOINT, HANDLE, PASSWORD, and DID are set in .env file."
92
92
+
)
93
93
+
return
94
94
+
95
95
+
endpoint = ensure_https(endpoint)
96
96
+
if not is_endpoint_alive(endpoint):
97
97
+
logging.error(f"Endpoint {endpoint} is not responding.")
42
98
return
43
99
44
100
try:
···
60
116
client = Client(endpoint)
61
117
62
118
try:
63
63
-
profile = client.login(handle, password)
64
64
-
logging.info(f"Authentication successful. Welcome, {profile.display_name}")
65
65
-
did = profile.did
66
66
-
logging.info(f"User DID: {did}")
119
119
+
client.login(handle, password)
120
120
+
logging.info("Authentication successful.")
67
121
except Exception as e:
68
122
logging.error(f"Authentication failed: {e}")
69
123
return
70
124
71
71
-
updated_profile_data = {
72
72
-
"$type": "app.bsky.actor.profile",
73
73
-
"avatar": {
74
74
-
"cid": new_blob_cid
75
75
-
}
76
76
-
}
125
125
+
try:
126
126
+
current_profile_record = client.app.bsky.actor.profile.get(
127
127
+
client.me.did, "self"
128
128
+
)
129
129
+
current_profile = current_profile_record.value
130
130
+
swap_record_cid = current_profile_record.cid
131
131
+
except BadRequestError:
132
132
+
current_profile = swap_record_cid = None
133
133
+
134
134
+
old_description = old_display_name = None
135
135
+
if current_profile:
136
136
+
old_description = current_profile.description
137
137
+
old_display_name = current_profile.display_name
138
138
+
139
139
+
blob_metadata = get_blob_metadata(new_blob_cid, did, endpoint)
77
140
78
78
-
logging.debug(f"Updated profile data: {updated_profile_data}")
141
141
+
if blob_metadata is None:
142
142
+
logging.error(f"Could not retrieve metadata for blob CID: {new_blob_cid}")
143
143
+
return
79
144
80
145
try:
81
146
client.com.atproto.repo.put_record(
82
82
-
repo=did,
83
83
-
collection="app.bsky.actor.profile",
84
84
-
rkey="self",
85
85
-
record=updated_profile_data
147
147
+
models.ComAtprotoRepoPutRecord.Data(
148
148
+
collection=models.ids.AppBskyActorProfile,
149
149
+
repo=client.me.did,
150
150
+
rkey="self",
151
151
+
swap_record=swap_record_cid,
152
152
+
record=models.AppBskyActorProfile.Record(
153
153
+
avatar=blob_metadata,
154
154
+
banner=current_profile.banner if current_profile else None,
155
155
+
description=old_description,
156
156
+
display_name=old_display_name,
157
157
+
),
158
158
+
)
86
159
)
87
160
logging.info("Avatar updated successfully!")
88
161
except Exception as e:
89
162
logging.error(f"Failed to update profile record: {e}")
163
163
+
90
164
91
165
if __name__ == "__main__":
92
166
main()