aboutsummaryrefslogtreecommitdiff
path: root/bot.py
diff options
context:
space:
mode:
authorclarkzjw <[email protected]>2023-02-22 14:01:16 -0800
committerclarkzjw <[email protected]>2023-02-23 12:07:26 -0800
commitae99c2d7237021e2abb20d4b41a24e0b73028519 (patch)
tree4c4a2a4d954a51148890d5bf589c938f24633f28 /bot.py
parent75b88bc06d354df64c12497330f124392fa7fc57 (diff)
downloadswarm2fediverse-ae99c2d7237021e2abb20d4b41a24e0b73028519.tar.gz
bot: support Mastodon OAuth2 login
test callback test mastodon callback test callback clean customwebhook example bot: test oauth login test callback url test callback
Diffstat (limited to 'bot.py')
-rw-r--r--bot.py121
1 files changed, 117 insertions, 4 deletions
diff --git a/bot.py b/bot.py
index e73fe1b..6198bc4 100644
--- a/bot.py
+++ b/bot.py
@@ -1,6 +1,26 @@
1#!/usr/bin/env python 1#!/usr/bin/env python
2 2
3import asyncio
3import logging 4import logging
5from dataclasses import dataclass
6from http import HTTPStatus
7from config import BOT_TOKEN, TELEGRAM_WEBHOOK_URL, HEALTHCHECK_URL, FEDI_LOGIN_CALLBACK_URL, BOT_DOMAIN, BOT_PORT
8
9import uvicorn
10from starlette.applications import Starlette
11from starlette.requests import Request
12from starlette.responses import PlainTextResponse, Response
13from starlette.routing import Route
14
15
16from telegram import Update
17from telegram.ext import (
18 Application,
19 CallbackContext,
20 ContextTypes,
21 ExtBot,
22 TypeHandler,
23)
4 24
5from telegram.ext import ( 25from telegram.ext import (
6 Application, 26 Application,
@@ -12,6 +32,7 @@ from telegram.ext import (
12) 32)
13 33
14from callback import ( 34from callback import (
35 callback_generate_fedi_login_url,
15 callback_skip_media, 36 callback_skip_media,
16 callback_location_sharing, 37 callback_location_sharing,
17 callback_manual_location, 38 callback_manual_location,
@@ -24,10 +45,12 @@ from callback import (
24) 45)
25from command import ( 46from command import (
26 start_command, 47 start_command,
48 fedi_login_command,
27 cancel_command, 49 cancel_command,
28 help_command 50 help_command
29) 51)
30from config import ( 52from config import (
53 FEDI_LOGIN,
31 WAIT_LOCATION, 54 WAIT_LOCATION,
32 LOCATION_SEARCH_KEYWORD, 55 LOCATION_SEARCH_KEYWORD,
33 LOCATION_CONFIRMATION, 56 LOCATION_CONFIRMATION,
@@ -36,21 +59,57 @@ from config import (
36 BOT_TOKEN 59 BOT_TOKEN
37) 60)
38 61
62# Enable logging
39logging.basicConfig( 63logging.basicConfig(
40 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO 64 format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
41) 65)
42logger = logging.getLogger(__name__) 66logger = logging.getLogger(__name__)
43 67
44 68
45def main() -> None: 69@dataclass
46 application = Application.builder().token(BOT_TOKEN).build() 70class FediLoginCallbackUpdate:
71 code: str
72 state: int
73
74
75class FediLoginCallbackContext(CallbackContext[ExtBot, dict, dict, dict]):
76 @classmethod
77 def from_update(
78 cls,
79 update: object,
80 application: "Application",
81 ) -> "FediLoginCallbackContext":
82 if isinstance(update, FediLoginCallbackUpdate):
83 return cls(application=application, user_id=update.state)
84 return super().from_update(update, application)
85
86
87async def process_oauth_login_callback(update: FediLoginCallbackUpdate, context: FediLoginCallbackContext) -> None:
88 combined_payloads = update.code
89 text = "Login success, your code is: {}".format(combined_payloads)
90 print(text)
91 print(update.state)
92 await context.bot.send_message(chat_id=update.state, text=text)
93
94
95async def main() -> None:
96 context_types = ContextTypes(context=FediLoginCallbackContext)
97 # Here we set updater to None because we want our custom webhook server to handle the updates
98 # and hence we don't need an Updater instance
99 application = (
100 Application.builder().token(BOT_TOKEN).context_types(context_types).build()
101 )
47 102
48 checkin_handler = ConversationHandler( 103 checkin_handler = ConversationHandler(
49 entry_points=[ 104 entry_points=[
50 CommandHandler("start", start_command), 105 CommandHandler("start", start_command),
106 CommandHandler("login", fedi_login_command),
51 MessageHandler(filters.LOCATION, callback_location_sharing), 107 MessageHandler(filters.LOCATION, callback_location_sharing),
52 ], 108 ],
53 states={ 109 states={
110 FEDI_LOGIN: [
111 MessageHandler(filters.TEXT & ~filters.COMMAND, callback_generate_fedi_login_url),
112 ],
54 WAIT_LOCATION: [ 113 WAIT_LOCATION: [
55 MessageHandler(filters.LOCATION, callback_location_sharing), 114 MessageHandler(filters.LOCATION, callback_location_sharing),
56 ], 115 ],
@@ -74,10 +133,64 @@ def main() -> None:
74 allow_reentry=True, 133 allow_reentry=True,
75 ) 134 )
76 135
136 # register handlers
77 application.add_handler(CommandHandler("help", help_command)) 137 application.add_handler(CommandHandler("help", help_command))
78 application.add_handler(checkin_handler) 138 application.add_handler(checkin_handler)
79 application.run_polling() 139 application.add_handler(TypeHandler(type=FediLoginCallbackUpdate, callback=process_oauth_login_callback))
140
141 # Pass webhook settings to telegram
142 await application.bot.set_webhook(url=f"{BOT_DOMAIN}{TELEGRAM_WEBHOOK_URL}")
143
144 # Set up webserver
145 async def telegram_webhook(request: Request) -> Response:
146 """Handle incoming Telegram updates by putting them into the `update_queue`"""
147 await application.update_queue.put(
148 Update.de_json(data=await request.json(), bot=application.bot)
149 )
150 return Response()
151
152 async def fedi_oauth_login_callback(request: Request) -> PlainTextResponse:
153 """
154 Handle incoming webhook updates by also putting them into the `update_queue` if
155 the required parameters were passed correctly.
156 """
157 try:
158 code = request.query_params["code"]
159 state = int(request.query_params.get("state"))
160 except KeyError:
161 return PlainTextResponse(
162 status_code=HTTPStatus.BAD_REQUEST,
163 content="Mastodon callback request doesn't contain a valid OAuth code",
164 )
165
166 await application.update_queue.put(FediLoginCallbackUpdate(state=state, code=code))
167 return PlainTextResponse("Thank you for login! Now you can close the browser")
168
169 async def healthcheck(_: Request) -> PlainTextResponse:
170 return PlainTextResponse(content="OK")
171
172 starlette_app = Starlette(
173 routes=[
174 Route(TELEGRAM_WEBHOOK_URL, telegram_webhook, methods=["POST"]),
175 Route(HEALTHCHECK_URL, healthcheck, methods=["GET"]),
176 Route(FEDI_LOGIN_CALLBACK_URL, fedi_oauth_login_callback, methods=["POST", "GET"]),
177 ]
178 )
179 webserver = uvicorn.Server(
180 config=uvicorn.Config(
181 app=starlette_app,
182 port=BOT_PORT,
183 use_colors=False,
184 host="127.0.0.1",
185 )
186 )
187
188 # Run application and webserver together
189 async with application:
190 await application.start()
191 await webserver.serve()
192 await application.stop()
80 193
81 194
82if __name__ == "__main__": 195if __name__ == "__main__":
83 main() 196 asyncio.run(main())
Powered by cgit v1.2.3 (git 2.41.0)