aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-24 21:08:59 -0800
committerclarkzjw <[email protected]>2023-02-28 15:58:02 -0800
commit353b83d415061bff2881cbe324273409740be64c (patch)
tree420fa402e7eda3425fd69b24959cffb0b2cc5039
parent0041eb4f9893687565e444be9d50648f49aa4d91 (diff)
downloadswarm2fediverse-feature/delayed_checkin.tar.gz
bot: implement delayed checkin, (kind of), some TODO leftfeature/delayed_checkin
-rw-r--r--bot.py31
-rw-r--r--callback.py54
-rw-r--r--command.py23
-rw-r--r--config.py13
-rw-r--r--dbstore/peewee_store.py10
-rw-r--r--prompt/string.py4
6 files changed, 119 insertions, 16 deletions
diff --git a/bot.py b/bot.py
index 364a85f..fe45970 100644
--- a/bot.py
+++ b/bot.py
@@ -11,6 +11,7 @@ from starlette.requests import Request
11from starlette.responses import PlainTextResponse, Response, JSONResponse 11from starlette.responses import PlainTextResponse, Response, JSONResponse
12from starlette.routing import Route 12from starlette.routing import Route
13from telegram import Update 13from telegram import Update
14from telegram.error import BadRequest
14from telegram.ext import ( 15from telegram.ext import (
15 Application, 16 Application,
16 CallbackContext, 17 CallbackContext,
@@ -33,7 +34,8 @@ from callback import (
33 callback_skip_location_keyword_search, 34 callback_skip_location_keyword_search,
34 callback_add_comment, 35 callback_add_comment,
35 callback_skip_comment, 36 callback_skip_comment,
36 callback_add_media 37 callback_add_media,
38 callback_delayed_checkin
37) 39)
38from command import ( 40from command import (
39 start_command, 41 start_command,
@@ -44,7 +46,8 @@ from command import (
44 toggle_visibility_command, 46 toggle_visibility_command,
45 callback_toggle_visibility, 47 callback_toggle_visibility,
46 logout_command, 48 logout_command,
47 list_command 49 list_command,
50 delayed_checkin_command,
48) 51)
49from config import ( 52from config import (
50 FEDI_LOGIN, 53 FEDI_LOGIN,
@@ -57,7 +60,8 @@ from config import (
57 BOT_TOKEN, 60 BOT_TOKEN,
58 BOT_SCOPE, 61 BOT_SCOPE,
59 MAIN_MENU, 62 MAIN_MENU,
60 WAIT_VISIBILITY 63 WAIT_VISIBILITY,
64 DELAYED_CHECKIN,
61) 65)
62 66
63from prompt.string import PROMPT_CHOOSE_ACTION 67from prompt.string import PROMPT_CHOOSE_ACTION
@@ -114,10 +118,14 @@ async def process_oauth_login_callback(update: FediLoginCallbackUpdate, context:
114 user.access_key = encrypt(access_token, ENCRYPT_KEY) 118 user.access_key = encrypt(access_token, ENCRYPT_KEY)
115 user.save() 119 user.save()
116 120
117 text = "You have successfully logged in to your Mastodon account!" 121 text = "You have successfully logged in to your Mastodon account!"
122 try:
118 await context.bot.delete_message(chat_id=user.telegram_user_id, message_id=context.user_data[PROMPT_FEDI_LOGIN]) 123 await context.bot.delete_message(chat_id=user.telegram_user_id, message_id=context.user_data[PROMPT_FEDI_LOGIN])
119 await context.bot.send_message(chat_id=user.telegram_user_id, text=text) 124 await context.bot.send_message(chat_id=user.telegram_user_id, text=text)
120 await context.bot.send_message(chat_id=user.telegram_user_id, text=PROMPT_CHOOSE_ACTION, reply_markup=MAIN_MENU) 125 await context.bot.send_message(chat_id=user.telegram_user_id, text=PROMPT_CHOOSE_ACTION, reply_markup=MAIN_MENU)
126 except BadRequest as e:
127 if "not found" in str(e.message):
128 pass
121 129
122 130
123async def main() -> None: 131async def main() -> None:
@@ -176,11 +184,26 @@ async def main() -> None:
176 allow_reentry=True, 184 allow_reentry=True,
177 ) 185 )
178 186
187 delayed_checkin_handler = ConversationHandler(
188 entry_points=[
189 CommandHandler("delay", delayed_checkin_command),
190 ],
191 states={
192 DELAYED_CHECKIN: [
193 MessageHandler(filters.TEXT & ~filters.COMMAND, callback_delayed_checkin),
194 ],
195 },
196 fallbacks=[CommandHandler("cancel", cancel_command)],
197 per_message=False,
198 allow_reentry=True,
199 )
200
179 application.add_handler(CommandHandler("logout", logout_command)) 201 application.add_handler(CommandHandler("logout", logout_command))
180 application.add_handler(CommandHandler("list", list_command)) 202 application.add_handler(CommandHandler("list", list_command))
181 application.add_handler(CommandHandler("Help", help_command)) 203 application.add_handler(CommandHandler("Help", help_command))
182 application.add_handler(TypeHandler(type=FediLoginCallbackUpdate, callback=process_oauth_login_callback)) 204 application.add_handler(TypeHandler(type=FediLoginCallbackUpdate, callback=process_oauth_login_callback))
183 205
206 application.add_handler(delayed_checkin_handler, 3)
184 application.add_handler(visibility_conversation_handler, 2) 207 application.add_handler(visibility_conversation_handler, 2)
185 application.add_handler(checkin_handler, 1) 208 application.add_handler(checkin_handler, 1)
186 209
diff --git a/callback.py b/callback.py
index 5fe4593..cec6188 100644
--- a/callback.py
+++ b/callback.py
@@ -13,6 +13,8 @@ from dbstore.peewee_store import User, db, TOOT_VISIBILITY_PRIVATE, TOOT_VISIBIL
13import uuid 13import uuid
14from mastodon import Mastodon 14from mastodon import Mastodon
15from util import decrypt, check_user 15from util import decrypt, check_user
16from datetime import datetime, timedelta
17from config import KEY_IS_SCHEDULED_TOOT
16 18
17 19
18def generate_uuid(): 20def generate_uuid():
@@ -131,9 +133,15 @@ async def callback_location_sharing(update: Update, context: ContextTypes.DEFAUL
131 133
132 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None 134 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None
133 135
136 if user["delayed_checkin"] > 0:
137 scheduled_at = datetime.now() + timedelta(minutes=user["delayed_checkin"])
138 else:
139 scheduled_at = None
140
134 status = get_mastodon_client(update.effective_user.id).status_post(content, 141 status = get_mastodon_client(update.effective_user.id).status_post(content,
135 visibility=user["tool_visibility"], 142 visibility=user["tool_visibility"],
136 content_type=content_type, 143 content_type=content_type,
144 scheduled_at=scheduled_at,
137 media_ids=[]) 145 media_ids=[])
138 146
139 context.user_data[KEY_TOOT_STATUS_ID] = status["id"] 147 context.user_data[KEY_TOOT_STATUS_ID] = status["id"]
@@ -216,17 +224,32 @@ async def _process_location_selection(context: ContextTypes.DEFAULT_TYPE, user:
216 224
217 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None 225 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None
218 226
227 if user["delayed_checkin"] > 0:
228 scheduled_at = datetime.now() + timedelta(minutes=user["delayed_checkin"])
229 else:
230 scheduled_at = None
231
219 status = get_mastodon_client(context.user_data["user_id"]).status_post(content, 232 status = get_mastodon_client(context.user_data["user_id"]).status_post(content,
220 visibility=user["toot_visibility"], 233 visibility=user["toot_visibility"],
221 content_type=content_type, 234 content_type=content_type,
235 scheduled_at=scheduled_at,
222 media_ids=[]) 236 media_ids=[])
237 if "scheduled_at" in status:
238 context.user_data[KEY_TOOT_STATUS_ID] = status["id"]
239 context.user_data[KEY_TOOT_STATUS_CONTENT] = content
240 context.user_data[KEY_IS_SCHEDULED_TOOT] = True
223 241
224 context.user_data[KEY_TOOT_STATUS_ID] = status["id"] 242 await context.bot.send_message(chat_id=context.user_data.get("chat_id"),
225 context.user_data[KEY_TOOT_STATUS_CONTENT] = content 243 text=f"Selected place: {poi_name}, \nToot scheduled at: {status['scheduled_at']}",
244 parse_mode=ParseMode.MARKDOWN)
245 elif "url" in status:
246 context.user_data[KEY_TOOT_STATUS_ID] = status["id"]
247 context.user_data[KEY_TOOT_STATUS_CONTENT] = content
248 context.user_data[KEY_IS_SCHEDULED_TOOT] = False
226 249
227 await context.bot.send_message(chat_id=context.user_data.get("chat_id"), 250 await context.bot.send_message(chat_id=context.user_data.get("chat_id"),
228 text=f"Selected place: {poi_name}, \nPosted to Mastodon: {status['url']}", 251 text=f"Selected place: {poi_name}, \nPosted to Mastodon: {status['url']}",
229 parse_mode=ParseMode.MARKDOWN) 252 parse_mode=ParseMode.MARKDOWN)
230 253
231 msg = await context.bot.send_message(chat_id=context.user_data.get("chat_id"), 254 msg = await context.bot.send_message(chat_id=context.user_data.get("chat_id"),
232 text=PROMPT_ADD_COMMENT, 255 text=PROMPT_ADD_COMMENT,
@@ -276,10 +299,23 @@ async def callback_add_comment(update: Update, context: ContextTypes.DEFAULT_TYP
276 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None 299 content_type = "text/markdown" if user["home_instance_type"] == "pleroma" else None
277 300
278 comment = update.effective_message.text 301 comment = update.effective_message.text
279 get_mastodon_client(update.effective_user.id).status_update(id=context.user_data.get(KEY_TOOT_STATUS_ID), 302 if context.user_data.get(KEY_IS_SCHEDULED_TOOT):
280 content_type=content_type, 303 # TODO:
281 status=f"{comment}\n\n" + context.user_data.get( 304 # Mastodon don't have API to update the content of scheduled toots
282 KEY_TOOT_STATUS_CONTENT)) 305 # we need to store scheduled toots in memory before posting to the server
306 pass
307 # get_mastodon_client(update.effective_user.id).scheduled_status_update(
308 # id=context.user_data.get(KEY_TOOT_STATUS_ID),
309 # content_type=content_type,
310 # status=f"{comment}\n\n" + context.user_data.get(
311 # KEY_TOOT_STATUS_CONTENT))
312
313 else:
314 get_mastodon_client(update.effective_user.id).status_update(id=context.user_data.get(KEY_TOOT_STATUS_ID),
315 content_type=content_type,
316 status=f"{comment}\n\n" + context.user_data.get(
317 KEY_TOOT_STATUS_CONTENT))
318
283 context.user_data[KEY_TOOT_STATUS_CONTENT] = f"{comment} " + context.user_data.get(KEY_TOOT_STATUS_CONTENT) 319 context.user_data[KEY_TOOT_STATUS_CONTENT] = f"{comment} " + context.user_data.get(KEY_TOOT_STATUS_CONTENT)
284 320
285 return await _process_comment(context) 321 return await _process_comment(context)
diff --git a/command.py b/command.py
index e92dc1c..5ae99f6 100644
--- a/command.py
+++ b/command.py
@@ -3,6 +3,7 @@ from telegram.constants import ParseMode
3from telegram.error import BadRequest 3from telegram.error import BadRequest
4from telegram.ext import ContextTypes, ConversationHandler 4from telegram.ext import ContextTypes, ConversationHandler
5from dbstore.peewee_store import get_user_access_key, get_user_home_instance, delete_user_by_id, update_user_visibility 5from dbstore.peewee_store import get_user_access_key, get_user_home_instance, delete_user_by_id, update_user_visibility
6from dbstore.peewee_store import get_user_by_id, update_delayed_checkin
6from dbstore.peewee_store import TOOT_VISIBILITY_PRIVATE, TOOT_VISIBILITY_UNLISTED, TOOT_VISIBILITY_PUBLIC 7from dbstore.peewee_store import TOOT_VISIBILITY_PRIVATE, TOOT_VISIBILITY_UNLISTED, TOOT_VISIBILITY_PUBLIC
7from config import * 8from config import *
8from util import check_user 9from util import check_user
@@ -54,7 +55,27 @@ async def logout_command(update: Update, context: ContextTypes.DEFAULT_TYPE, use
54 await update.message.reply_text(PROMPT_LOGOUT_SUCCESS, parse_mode=ParseMode.HTML, reply_markup=LOGIN_MENU) 55 await update.message.reply_text(PROMPT_LOGOUT_SUCCESS, parse_mode=ParseMode.HTML, reply_markup=LOGIN_MENU)
55 56
56 57
57@check_user 58async def delayed_checkin_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
59 await update.message.reply_text(PROMPT_DELAYED_CHECKIN, parse_mode=ParseMode.HTML)
60 return DELAYED_CHECKIN
61
62
63async def callback_delayed_checkin(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
64 try:
65 delayed_minutes = int(update.effective_message.text)
66 except ValueError:
67 await update.message.edit_text(text="Integer expected, try again")
68 return DELAYED_CHECKIN
69
70 if delayed_minutes < 5:
71 delayed_minutes = 0
72
73 update_delayed_checkin(str(update.effective_user.id), delayed_minutes)
74 u = get_user_by_id(str(update.effective_user.id))
75 await update.message.reply_text(text=f"Delayed check-in set to {u['delayed_checkin']} minutes")
76 return ConversationHandler.END
77
78
58async def toggle_visibility_command(update: Update, context: ContextTypes.DEFAULT_TYPE, user: User) -> int: 79async def toggle_visibility_command(update: Update, context: ContextTypes.DEFAULT_TYPE, user: User) -> int:
59 visibility_menu = InlineKeyboardMarkup([ 80 visibility_menu = InlineKeyboardMarkup([
60 [InlineKeyboardButton("Private", callback_data=TOOT_VISIBILITY_PRIVATE)], 81 [InlineKeyboardButton("Private", callback_data=TOOT_VISIBILITY_PRIVATE)],
diff --git a/config.py b/config.py
index fe69bd9..bff9313 100644
--- a/config.py
+++ b/config.py
@@ -18,7 +18,11 @@ BOT_PORT = int(config["API"]["BOT_PORT"])
18 18
19MEDIA_GROUP_TIMEOUT = 3 19MEDIA_GROUP_TIMEOUT = 3
20 20
21FEDI_LOGIN, WAIT_VISIBILITY, WAIT_LOCATION, LOCATION_SEARCH_KEYWORD, LOCATION_CONFIRMATION, ADD_MEDIA, ADD_COMMENT = range(7) 21FEDI_LOGIN, \
22 WAIT_VISIBILITY, \
23 DELAYED_CHECKIN, \
24 WAIT_LOCATION, LOCATION_SEARCH_KEYWORD, LOCATION_CONFIRMATION, \
25 ADD_MEDIA, ADD_COMMENT = range(8)
22 26
23MAIN_MENU = ReplyKeyboardMarkup([ 27MAIN_MENU = ReplyKeyboardMarkup([
24 [KeyboardButton(text="Check-in here", request_location=True)], 28 [KeyboardButton(text="Check-in here", request_location=True)],
@@ -46,5 +50,10 @@ class MsgDict(TypedDict):
46 50
47KEY_TOOT_STATUS_ID = "toot_status_id" 51KEY_TOOT_STATUS_ID = "toot_status_id"
48KEY_TOOT_STATUS_CONTENT = "toot_status_content" 52KEY_TOOT_STATUS_CONTENT = "toot_status_content"
53KEY_IS_SCHEDULED_TOOT = "is_scheduled_toot"
49 54
50BOT_SCOPE = ['read:accounts', 'write:media', 'write:statuses'] 55# TODO:
56# use the first scope as default
57# if user set delayed post, ask user consent and request OAuth token again
58# DEFAULT_BOT_SCOPE = ['read:accounts', 'write:media', 'write:statuses']
59BOT_SCOPE = ['read:accounts', 'read:statuses', 'write:media', 'write:statuses']
diff --git a/dbstore/peewee_store.py b/dbstore/peewee_store.py
index d05c642..ebf3536 100644
--- a/dbstore/peewee_store.py
+++ b/dbstore/peewee_store.py
@@ -23,6 +23,15 @@ class User(BaseModel):
23 client_id = CharField(max_length=128) 23 client_id = CharField(max_length=128)
24 client_secret = CharField(max_length=128) 24 client_secret = CharField(max_length=128)
25 toot_visibility = CharField(max_length=128, default=TOOT_VISIBILITY_PRIVATE) 25 toot_visibility = CharField(max_length=128, default=TOOT_VISIBILITY_PRIVATE)
26 # delayed checkin in minutes
27 delayed_checkin = IntegerField(default=0)
28
29
30def update_delayed_checkin(telegram_user_id: str, delayed_checkin: int) -> int:
31 with db.connection_context():
32 return User.update(delayed_checkin=delayed_checkin).where(
33 User.telegram_user_id == telegram_user_id
34 ).execute()
26 35
27 36
28def update_user_visibility(telegram_user_id: str, visibility: str) -> int: 37def update_user_visibility(telegram_user_id: str, visibility: str) -> int:
@@ -45,6 +54,7 @@ def get_user_by_id(telegram_user_id: str) -> dict:
45 "client_id": user.client_id, 54 "client_id": user.client_id,
46 "client_secret": user.client_secret, 55 "client_secret": user.client_secret,
47 "toot_visibility": user.toot_visibility, 56 "toot_visibility": user.toot_visibility,
57 "delayed_checkin": user.delayed_checkin,
48 } 58 }
49 except DoesNotExist: 59 except DoesNotExist:
50 return {} 60 return {}
diff --git a/prompt/string.py b/prompt/string.py
index 3cc0a24..32f9aee 100644
--- a/prompt/string.py
+++ b/prompt/string.py
@@ -37,9 +37,13 @@ PROMPT_HELP = "Available commands:" \
37 "\n`/list` - list current linked Fediverse accounts" \ 37 "\n`/list` - list current linked Fediverse accounts" \
38 "\n`/logout` - logout from specified Fediverse account, default logout from all" \ 38 "\n`/logout` - logout from specified Fediverse account, default logout from all" \
39 "\n`/vis` - toggle visibility of your checkins on specified instances, default=private" \ 39 "\n`/vis` - toggle visibility of your checkins on specified instances, default=private" \
40 "\n`/delay` - set delayed checkin in minutes, default=0" \
40 "\n`/tos` - show ToS message" \ 41 "\n`/tos` - show ToS message" \
41 "\n`/cancel` - cancel current operation during checkins" 42 "\n`/cancel` - cancel current operation during checkins"
42 43
44PROMPT_DELAYED_CHECKIN = "How many minutes to delay your checkin? By default, checkin will be posted immediately.\n" \
45 "If you wish to set delayed checkin, at least <b>5</b> minutes is required. \n" \
46 "Input less than 5 will be set to 0 (immediate checkin)."
43PROMPT_LIST = "You are linked with the following Fediverse accounts:" 47PROMPT_LIST = "You are linked with the following Fediverse accounts:"
44PROMPT_LIST_NO_RESULT = "You are not linked with any Fediverse account yet. \n\n Input <code>/login</code> to login." 48PROMPT_LIST_NO_RESULT = "You are not linked with any Fediverse account yet. \n\n Input <code>/login</code> to login."
45PROMPT_LOGOUT = "Choose Fediverse account to logout" 49PROMPT_LOGOUT = "Choose Fediverse account to logout"
Powered by cgit v1.2.3 (git 2.41.0)