bot.py (6356B)
1 #!/usr/bin/python 2 """ 3 This is a Telegram Bot which returns rendered images of LaTeX using an API. 4 """ 5 6 import telegram 7 from telegram.ext import Updater, MessageHandler, CommandHandler, Filters 8 from telegram.error import (TelegramError, Unauthorized, BadRequest, 9 TimedOut, ChatMigrated, NetworkError) 10 import datetime 11 import urllib 12 import logging 13 import os 14 import signal 15 import random 16 17 logging.basicConfig( 18 filename="log", filemode='a', 19 format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 20 level=logging.INFO, 21 ) 22 23 24 # CONSTANTS 25 26 VERSION = "0.0.1" 27 28 try: 29 with open('token') as TokenFile: 30 TOKEN = TokenFile.read().splitlines()[0] 31 except FileNotFoundError: 32 print( 33 """ 34 You need a bot token to run an instance of this Telegram bot. 35 Please make a file named 'token' with your bot token in there, 36 in the same folder as this bot file. 37 38 Learn more at t.me/botfather 39 """ ) 40 quit() 41 42 IMAGE_TYPE = "png" 43 IMAGE_DPI = "512" 44 API = f"https://latex.codecogs.com/{IMAGE_TYPE}.latex?%%5C{IMAGE_DPI}dpi%%20%s" 45 EXAMPLE_LATEX = r"\text{The quadratic formula} \\ " + "\n" + \ 46 r"x = \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}" 47 48 49 # UTIL 50 51 # Returns a iso-formatted string of datetime at moment of call. 52 dt_now = lambda: datetime.datetime.now().isoformat(timespec="seconds") 53 54 def logger_dec(old_cb_func): 55 56 def new_cb_func(upd, ctx): 57 msg = upd.message.text 58 name = upd.message.from_user.name 59 log_msg = f"{name}\t:: {msg}" 60 logging.info(log_msg) 61 print(log_msg) 62 63 return old_cb_func(upd, ctx) 64 65 return new_cb_func 66 67 68 def get_random_example(): 69 example_lines = [] 70 recording = False 71 72 with open("examples.tex", 'r') as File: 73 line = next(File) 74 if not line.startswith("%TOTAL"): 75 raise Exception("No '%TOTAL N' on first line of examples.tex") 76 else: 77 total_examples = int( line.split()[1] ) 78 example_number = random.randint(1, total_examples) 79 80 for line in File: 81 if line.startswith(f"%BEGIN {example_number}"): 82 recording = True 83 continue 84 85 if line.startswith("%END") and recording: 86 recording = False 87 break 88 89 if recording: 90 example_lines += [line] 91 92 example = ''.join(example_lines) 93 return example 94 95 96 # CALLBACK HANDLERS 97 98 @logger_dec 99 def cb_start(upd, ctx): 100 bot_username = ctx.bot.get_me().username.replace('_', '\\_') 101 102 # outgoing text 103 out_msg = f"""\ 104 @{bot_username} renders _LaTeX_ text and formulae. 105 106 Try: `\\texttt{{Hello, world!}}` 107 108 See /random for examples 109 See /help for more 110 See /about 111 """ 112 113 # send message 114 upd.message.reply_text( 115 out_msg, 116 parse_mode=telegram.ParseMode.MARKDOWN, 117 ) 118 119 120 def cb_help(upd, ctx): 121 cb_start(upd, ctx) 122 123 124 @logger_dec 125 def cb_about(upd, ctx): 126 """ 127 /about callback 128 """ 129 bot_username = ctx.bot.get_me().username.replace('_', '\\_') 130 131 # Outgoing text 132 out_msg = f"""\ 133 @{bot_username} (@LatexImgBot\_updates) 134 135 Version 136 {VERSION} 137 Source code 138 https://sr.ht/~torresjrjr/linkchanbot 139 Maintainer 140 @torresjrjr <b@torresjrjr.com> 141 License 142 GNU Affero General Public License 143 """ 144 145 # Send message 146 upd.message.reply_text( 147 out_msg, 148 parse_mode=telegram.ParseMode.MARKDOWN, 149 ) 150 151 152 @logger_dec 153 def cb_random(upd, ctx): 154 latex = get_random_example() 155 156 encoded_latex = urllib.parse.quote(latex) 157 latex_url = API % encoded_latex 158 159 caption = f"`{latex}`" 160 161 ctx.bot.send_photo( 162 chat_id = upd.effective_chat.id, 163 photo = latex_url, 164 caption = caption, 165 parse_mode = telegram.ParseMode.MARKDOWN, 166 ) 167 168 169 @logger_dec 170 def handler(upd, ctx, kind="standard"): 171 msg = upd.message.text 172 173 if kind == "link": latex = msg.replace("/link","") 174 else : latex = msg 175 176 encoded_latex = urllib.parse.quote(latex) 177 latex_url = API % encoded_latex 178 179 if kind == "standard": caption = f"`{latex}`" 180 elif kind == "link" : caption = f"`{latex}`\n" + latex_url 181 182 ctx.bot.send_photo( 183 chat_id = upd.effective_chat.id, 184 photo = latex_url, 185 caption = caption, 186 parse_mode = telegram.ParseMode.MARKDOWN, 187 ) 188 189 190 cb_link = lambda upd, ctx: handler(upd, ctx, kind="link") 191 cb_handler = lambda upd, ctx: handler(upd, ctx) 192 193 @logger_dec 194 def cb_admin(upd, ctx): 195 msg = upd.message.text 196 name = upd.message.from_user.name 197 log_msg = f"{name}\t:: {msg}" 198 print(log_msg) 199 200 username = upd.message.from_user.username 201 if username == "torresjrjr": 202 upd.message.reply_text( 203 "Admin authorised. Sending SIGINT...", 204 parse_mode=telegram.ParseMode.MARKDOWN, 205 ) 206 os.kill(os.getpid(), signal.SIGINT) 207 208 209 def cb_error(update, context): 210 try: 211 raise context.error 212 except Unauthorized as e: 213 print("Unauthorized Error:", e) # remove update.message.chat_id from conversation list 214 except BadRequest as e: 215 print("BadRequest Error:", e) # handle malformed requests - read more below! 216 except TimedOut as e: 217 print("TimedOut Error:", e) # handle slow connection problems 218 except NetworkError as e: 219 print("NetworkError Error:", e) # handle other connection problems 220 except ChatMigrated as e: 221 print("ChatMigrated Error:", e) # the chat_id of a group has changed, use e.new_chat_id instead 222 except TelegramError as e: 223 print("TelegramError Error:", e) # handle all other telegram related errors 224 225 226 # MAIN 227 228 def main(): 229 print("Starting bot...") 230 231 updater = Updater(TOKEN, use_context=True) 232 233 dp = updater.dispatcher 234 dp.add_error_handler(cb_error) 235 dp.add_handler(CommandHandler('start' , cb_start)) 236 dp.add_handler(CommandHandler('help' , cb_help)) 237 dp.add_handler(CommandHandler('about' , cb_about)) 238 dp.add_handler(CommandHandler('link' , cb_link)) 239 dp.add_handler(CommandHandler('random' , cb_random)) 240 dp.add_handler(CommandHandler('admin' , cb_admin)) 241 dp.add_handler(MessageHandler(Filters.text, cb_handler)) 242 243 print(dt_now(), "Serving...") 244 245 updater.start_polling() 246 updater.idle() 247 248 print(dt_now(), "Ended.") 249 250 251 if __name__=='__main__': 252 main()