diff options
author | clarkzjw <[email protected]> | 2023-02-23 16:46:25 -0800 |
---|---|---|
committer | clarkzjw <[email protected]> | 2023-02-23 16:46:25 -0800 |
commit | af56ead63269aff81c7b23251fca3ca650eb94fe (patch) | |
tree | b7b9491817e83452f69750655fe240702ab44e0e | |
parent | 20b8220a12fc92f95ecae267ce4eb5a80584f564 (diff) | |
parent | b98b8bf3ced39afcb04e705d500cd5184d8b5254 (diff) | |
download | swarm2fediverse-af56ead63269aff81c7b23251fca3ca650eb94fe.tar.gz |
Merge branch 'feature/oauth-pleroma'
- bot: support pleroma instances
- status_update() in mastodon.py currently doesn't support content_type, thus it still fallbacks to text/plain on pleroma instances when adding comments
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | bot.py | 8 | ||||
-rw-r--r-- | callback.py | 47 | ||||
-rw-r--r-- | dbstore/peewee_store.py | 1 | ||||
-rw-r--r-- | foursquare/poi.py | 21 |
5 files changed, 64 insertions, 15 deletions
@@ -14,7 +14,7 @@ Foursquare Swarm like Telegram bot to check in at places and post to Fediverse ( | |||
14 | 14 | ||
15 | - Supported Fediverse software | 15 | - Supported Fediverse software |
16 | - [x] Mastodon | 16 | - [x] Mastodon |
17 | - [ ] Pleroma | 17 | - [x] Pleroma |
18 | - [ ] Misskey | 18 | - [ ] Misskey |
19 | 19 | ||
20 | - [ ] Option to set individual checkin visibility | 20 | - [ ] Option to set individual checkin visibility |
@@ -99,12 +99,16 @@ async def process_oauth_login_callback(update: FediLoginCallbackUpdate, context: | |||
99 | home_instance = user.home_instance | 99 | home_instance = user.home_instance |
100 | 100 | ||
101 | if len(user.access_key) == 0: | 101 | if len(user.access_key) == 0: |
102 | mastodon_client = Mastodon(client_id=client_id, client_secret=client_secret, api_base_url=home_instance) | 102 | mastodon_client = Mastodon(client_id=client_id, client_secret=client_secret, |
103 | api_base_url=home_instance, version_check_mode="none") | ||
103 | access_token = mastodon_client.log_in( | 104 | access_token = mastodon_client.log_in( |
104 | code=update.code, | 105 | code=update.code, |
105 | redirect_uri="{}{}".format(BOT_DOMAIN, FEDI_LOGIN_CALLBACK_URL), | 106 | redirect_uri="{}{}".format(BOT_DOMAIN, FEDI_LOGIN_CALLBACK_URL), |
106 | scopes=BOT_SCOPE | 107 | scopes=BOT_SCOPE |
107 | ) | 108 | ) |
109 | instance_info = mastodon_client.instance_nodeinfo() | ||
110 | if instance_info["software"]["name"] == "pleroma": | ||
111 | user.home_instance_type = "pleroma" | ||
108 | user.access_key = encrypt(access_token, ENCRYPT_KEY) | 112 | user.access_key = encrypt(access_token, ENCRYPT_KEY) |
109 | user.save() | 113 | user.save() |
110 | 114 | ||
@@ -207,7 +211,7 @@ async def main() -> None: | |||
207 | app=starlette_app, | 211 | app=starlette_app, |
208 | port=BOT_PORT, | 212 | port=BOT_PORT, |
209 | use_colors=False, | 213 | use_colors=False, |
210 | host="127.0.0.1", | 214 | host="100.93.242.2", |
211 | ) | 215 | ) |
212 | ) | 216 | ) |
213 | 217 | ||
diff --git a/callback.py b/callback.py index a1b4b2d..ecce2bf 100644 --- a/callback.py +++ b/callback.py | |||
@@ -7,8 +7,7 @@ from telegram.ext import CallbackContext | |||
7 | 7 | ||
8 | from command import * | 8 | from command import * |
9 | from dbstore.peewee_store import get_poi_by_fsq_id | 9 | from dbstore.peewee_store import get_poi_by_fsq_id |
10 | from foursquare.poi import OSM_ENDPOINT | 10 | from foursquare.poi import query_poi, query_poi_by_fsq_id, OSM_ENDPOINT |
11 | from foursquare.poi import query_poi | ||
12 | from config import BOT_SCOPE, ENCRYPT_KEY | 11 | from config import BOT_SCOPE, ENCRYPT_KEY |
13 | from dbstore.peewee_store import User, db, TOOT_VISIBILITY_PRIVATE, TOOT_VISIBILITY_PUBLIC, TOOT_VISIBILITY_UNLISTED | 12 | from dbstore.peewee_store import User, db, TOOT_VISIBILITY_PRIVATE, TOOT_VISIBILITY_PUBLIC, TOOT_VISIBILITY_UNLISTED |
14 | import uuid | 13 | import uuid |
@@ -24,7 +23,10 @@ def get_mastodon_client(user_id: int): | |||
24 | with db.connection_context(): | 23 | with db.connection_context(): |
25 | user = User.get(User.telegram_user_id == user_id) | 24 | user = User.get(User.telegram_user_id == user_id) |
26 | if user.home_instance and user.access_key: | 25 | if user.home_instance and user.access_key: |
27 | return Mastodon(access_token=decrypt(user.access_key, ENCRYPT_KEY), api_base_url=user.home_instance) | 26 | feature_set = "pleroma" if user.home_instance_type == "pleroma" else "mainline" |
27 | |||
28 | return Mastodon(access_token=decrypt(user.access_key, ENCRYPT_KEY), | ||
29 | api_base_url=user.home_instance, feature_set=feature_set, version_check_mode='none', ) | ||
28 | 30 | ||
29 | 31 | ||
30 | def generate_toot_text(poi_name, poi_locality, poi_region, poi_lat, poi_lon): | 32 | def generate_toot_text(poi_name, poi_locality, poi_region, poi_lat, poi_lon): |
@@ -94,11 +96,12 @@ async def callback_generate_fedi_login_url(update: Update, context: ContextTypes | |||
94 | u.save() | 96 | u.save() |
95 | 97 | ||
96 | oauth_url = m.auth_request_url(redirect_uris="{}{}".format(BOT_DOMAIN, FEDI_LOGIN_CALLBACK_URL), | 98 | oauth_url = m.auth_request_url(redirect_uris="{}{}".format(BOT_DOMAIN, FEDI_LOGIN_CALLBACK_URL), |
97 | scopes=BOT_SCOPE, | 99 | scopes=BOT_SCOPE, |
98 | state=state) | 100 | state=state) |
99 | 101 | ||
100 | msg = await update.message.reply_text(PROMPT_FEDI_LOGIN, | 102 | msg = await update.message.reply_text(PROMPT_FEDI_LOGIN, |
101 | reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Login", url=oauth_url)]]), | 103 | reply_markup=InlineKeyboardMarkup( |
104 | [[InlineKeyboardButton("Login", url=oauth_url)]]), | ||
102 | parse_mode=ParseMode.MARKDOWN) | 105 | parse_mode=ParseMode.MARKDOWN) |
103 | 106 | ||
104 | context.user_data[PROMPT_FEDI_LOGIN] = msg.message_id | 107 | context.user_data[PROMPT_FEDI_LOGIN] = msg.message_id |
@@ -112,9 +115,18 @@ async def callback_location_sharing(update: Update, context: ContextTypes.DEFAUL | |||
112 | context.user_data["latitude"] = update.message.venue.location.latitude | 115 | context.user_data["latitude"] = update.message.venue.location.latitude |
113 | context.user_data["longitude"] = update.message.venue.location.longitude | 116 | context.user_data["longitude"] = update.message.venue.location.longitude |
114 | 117 | ||
115 | poi = get_poi_by_fsq_id(context.user_data.get("fsq_id")) | 118 | poi = query_poi_by_fsq_id(context.user_data.get("fsq_id")) |
116 | content = generate_toot_text(poi["name"], poi["locality"], poi["region"], poi["latitude"], poi["longitude"]) | 119 | content = generate_toot_text(poi["name"], poi["locality"], poi["region"], poi["latitude"], poi["longitude"]) |
117 | status = get_mastodon_client(update.effective_user.id).status_post(content, visibility=TOOT_VISIBILITY_PRIVATE, media_ids=[]) | 120 | |
121 | # TODO | ||
122 | # mastodon.py status_update() currently does not support content_type parameter | ||
123 | with db.connection_context(): | ||
124 | u = User.get(User.telegram_user_id == update.effective_user.id) | ||
125 | content_type = "text/markdown" if u.home_instance_type == "pleroma" else "text/plain" | ||
126 | status = get_mastodon_client(update.effective_user.id).status_post(content, | ||
127 | visibility=TOOT_VISIBILITY_PRIVATE, | ||
128 | content_type=content_type, | ||
129 | media_ids=[]) | ||
118 | 130 | ||
119 | context.user_data[KEY_TOOT_STATUS_ID] = status["id"] | 131 | context.user_data[KEY_TOOT_STATUS_ID] = status["id"] |
120 | context.user_data[KEY_TOOT_STATUS_CONTENT] = content | 132 | context.user_data[KEY_TOOT_STATUS_CONTENT] = content |
@@ -158,7 +170,8 @@ async def callback_location_keyword_search(update: Update, context: ContextTypes | |||
158 | await context.bot.delete_message(update.effective_chat.id, context.user_data.get(PROMPT_LOCATION_KEYWORD)) | 170 | await context.bot.delete_message(update.effective_chat.id, context.user_data.get(PROMPT_LOCATION_KEYWORD)) |
159 | key = update.effective_message.text | 171 | key = update.effective_message.text |
160 | 172 | ||
161 | keyboard = await _process_location_search(key, context.user_data.get("latitude"), context.user_data.get("longitude")) | 173 | keyboard = await _process_location_search(key, context.user_data.get("latitude"), |
174 | context.user_data.get("longitude")) | ||
162 | 175 | ||
163 | if len(keyboard) == 0: | 176 | if len(keyboard) == 0: |
164 | msg = await update.message.reply_text(PROMPT_WAIT_LOCATION_CONFIRMATION_NO_NEARBY_POI) | 177 | msg = await update.message.reply_text(PROMPT_WAIT_LOCATION_CONFIRMATION_NO_NEARBY_POI) |
@@ -188,9 +201,17 @@ async def _process_location_selection(context: ContextTypes.DEFAULT_TYPE) -> int | |||
188 | poi_name = poi["name"] | 201 | poi_name = poi["name"] |
189 | content = generate_toot_text(poi["name"], poi["locality"], poi["region"], poi["latitude"], poi["longitude"]) | 202 | content = generate_toot_text(poi["name"], poi["locality"], poi["region"], poi["latitude"], poi["longitude"]) |
190 | else: | 203 | else: |
191 | content = generate_toot_text(poi_name, "", "", context.user_data.get("latitude"), context.user_data.get("longitude")) | 204 | content = generate_toot_text(poi_name, "", "", context.user_data.get("latitude"), |
205 | context.user_data.get("longitude")) | ||
192 | 206 | ||
193 | status = get_mastodon_client(context.user_data["user_id"]).status_post(content, visibility=TOOT_VISIBILITY_PRIVATE, media_ids=[]) | 207 | with db.connection_context(): |
208 | u = User.get(User.telegram_user_id == context.user_data["user_id"]) | ||
209 | content_type = "text/markdown" if u.home_instance_type == "pleroma" else "text/plain" | ||
210 | |||
211 | status = get_mastodon_client(context.user_data["user_id"]).status_post(content, | ||
212 | visibility=TOOT_VISIBILITY_PRIVATE, | ||
213 | content_type=content_type, | ||
214 | media_ids=[]) | ||
194 | 215 | ||
195 | context.user_data[KEY_TOOT_STATUS_ID] = status["id"] | 216 | context.user_data[KEY_TOOT_STATUS_ID] = status["id"] |
196 | context.user_data[KEY_TOOT_STATUS_CONTENT] = content | 217 | context.user_data[KEY_TOOT_STATUS_CONTENT] = content |
@@ -238,11 +259,13 @@ async def _process_comment(context: ContextTypes.DEFAULT_TYPE) -> int: | |||
238 | 259 | ||
239 | async def callback_add_comment(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: | 260 | async def callback_add_comment(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int: |
240 | context.user_data["chat_id"] = update.effective_chat.id | 261 | context.user_data["chat_id"] = update.effective_chat.id |
262 | context.user_data["user_id"] = update.effective_user.id | ||
241 | await context.bot.delete_message(update.effective_chat.id, context.user_data.get(PROMPT_ADD_COMMENT)) | 263 | await context.bot.delete_message(update.effective_chat.id, context.user_data.get(PROMPT_ADD_COMMENT)) |
242 | 264 | ||
243 | comment = update.effective_message.text | 265 | comment = update.effective_message.text |
244 | get_mastodon_client(update.effective_user.id).status_update(id=context.user_data.get(KEY_TOOT_STATUS_ID), | 266 | get_mastodon_client(update.effective_user.id).status_update(id=context.user_data.get(KEY_TOOT_STATUS_ID), |
245 | status=f"{comment} " + context.user_data.get(KEY_TOOT_STATUS_CONTENT)) | 267 | status=f"{comment} " + context.user_data.get( |
268 | KEY_TOOT_STATUS_CONTENT)) | ||
246 | context.user_data[KEY_TOOT_STATUS_CONTENT] = f"{comment} " + context.user_data.get(KEY_TOOT_STATUS_CONTENT) | 269 | context.user_data[KEY_TOOT_STATUS_CONTENT] = f"{comment} " + context.user_data.get(KEY_TOOT_STATUS_CONTENT) |
247 | 270 | ||
248 | return await _process_comment(context) | 271 | return await _process_comment(context) |
diff --git a/dbstore/peewee_store.py b/dbstore/peewee_store.py index 445816e..ed52257 100644 --- a/dbstore/peewee_store.py +++ b/dbstore/peewee_store.py | |||
@@ -18,6 +18,7 @@ class User(BaseModel): | |||
18 | telegram_user_id = CharField(unique=True, primary_key=True) | 18 | telegram_user_id = CharField(unique=True, primary_key=True) |
19 | access_key = CharField(max_length=256) | 19 | access_key = CharField(max_length=256) |
20 | home_instance = CharField(max_length=256) | 20 | home_instance = CharField(max_length=256) |
21 | home_instance_type = CharField(max_length=128, default="mastodon") | ||
21 | state = CharField(max_length=128) | 22 | state = CharField(max_length=128) |
22 | client_id = CharField(max_length=128) | 23 | client_id = CharField(max_length=128) |
23 | client_secret = CharField(max_length=128) | 24 | client_secret = CharField(max_length=128) |
diff --git a/foursquare/poi.py b/foursquare/poi.py index 39895b7..5e31dc8 100644 --- a/foursquare/poi.py +++ b/foursquare/poi.py | |||
@@ -4,6 +4,7 @@ from config import FSQ_API_KEY | |||
4 | from dbstore.peewee_store import create_or_update_poi | 4 | from dbstore.peewee_store import create_or_update_poi |
5 | 5 | ||
6 | POI_API_ENDPOINT = "https://api.foursquare.com/v3/places/nearby?ll={}%2C{}&limit=10" | 6 | POI_API_ENDPOINT = "https://api.foursquare.com/v3/places/nearby?ll={}%2C{}&limit=10" |
7 | POI_DETAIL_ENDPOINT = "https://api.foursquare.com/v3/places/{}" | ||
7 | POI_SEARCH_API_ENDPOINT = "https://api.foursquare.com/v3/places/search?query={}&ll={}%2C{}&radius=2000&limit=10" | 8 | POI_SEARCH_API_ENDPOINT = "https://api.foursquare.com/v3/places/search?query={}&ll={}%2C{}&radius=2000&limit=10" |
8 | POI_PHOTO_ENDPOINT = "https://api.foursquare.com/v3/places/{}/photos?sort=POPULAR&limit=10" | 9 | POI_PHOTO_ENDPOINT = "https://api.foursquare.com/v3/places/{}/photos?sort=POPULAR&limit=10" |
9 | OSM_ENDPOINT = "https://www.openstreetmap.org/?mlat={}&mlon={}&zoom=15&layers=M" | 10 | OSM_ENDPOINT = "https://www.openstreetmap.org/?mlat={}&mlon={}&zoom=15&layers=M" |
@@ -35,6 +36,26 @@ def query_poi(search, latitude, longitude): | |||
35 | return locations | 36 | return locations |
36 | 37 | ||
37 | 38 | ||
39 | def query_poi_by_fsq_id(fsq_id): | ||
40 | url = POI_DETAIL_ENDPOINT.format(fsq_id) | ||
41 | response = requests.get(url, headers=headers) | ||
42 | |||
43 | poi = json.loads(response.text) | ||
44 | |||
45 | loc = { | ||
46 | "fsq_id": fsq_id, | ||
47 | "name": poi["name"], | ||
48 | "locality": poi["location"]["locality"] if "locality" in poi["location"] else "", | ||
49 | "region": poi["location"]["region"] if "region" in poi["location"] else "", | ||
50 | "latitude": poi["geocodes"]["main"]["latitude"], | ||
51 | "longitude": poi["geocodes"]["main"]["longitude"], | ||
52 | "osm_url": OSM_ENDPOINT.format(poi["geocodes"]["main"]["latitude"], poi["geocodes"]["main"]["longitude"]) | ||
53 | } | ||
54 | create_or_update_poi(loc) | ||
55 | |||
56 | return loc | ||
57 | |||
58 | |||
38 | def get_poi_top_photo(fsq_id): | 59 | def get_poi_top_photo(fsq_id): |
39 | url = POI_PHOTO_ENDPOINT.format(fsq_id) | 60 | url = POI_PHOTO_ENDPOINT.format(fsq_id) |
40 | response = requests.get(url, headers=headers) | 61 | response = requests.get(url, headers=headers) |