You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

196 lines
6.5 KiB

2 years ago
"""
This is a telegram bot to fetch random word definition.
Currently supported commands are:
word - get a random word with definition
"""
import datetime
import logging
import os
import psycopg2
import requests
import sqlite3
import telegram
import telegram.ext
2 years ago
import time
from bs4 import BeautifulSoup
from dotenv import load_dotenv
from telegram import ParseMode, InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, CallbackQueryHandler
2 years ago
load_dotenv()
log_file_name = f"logs/wordbot_log_{datetime.date.today().strftime('%Y_%m_%d')}.log"
logging.basicConfig(filename=log_file_name, filemode='a', format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
2 years ago
level=logging.DEBUG)
logger = logging.getLogger()
def connect_db():
database = os.getenv('DATABASE')
if database == 'postgres':
con = psycopg2.connect(
database=os.getenv("DB_NAME"),
user=os.getenv("DB_USER"),
password=os.getenv("DB_PWD"),
host=os.getenv("DB_HOST"),
port=os.getenv("DB_PORT")
)
print("connected to postgres")
elif database == 'sqlite':
con = sqlite3.connect(os.getenv('DB_FILE'))
print("connected to sqlite")
return con
2 years ago
def start(
update: telegram.update.Update,
context: telegram.ext.callbackcontext.CallbackContext
) -> None:
""" Handles the start command to the bot. Responds with help content """
2 years ago
start_text = "Welcome to Random Word Bot. To fetch a word, just send /word"
context.bot.send_message(chat_id=update.effective_chat.id, text=start_text)
def slugify(message: str) -> str:
""" Adds relevant escape characters as per Telegram's markdown parsing rules """
2 years ago
message = message.replace(".", "\\.").replace("*", "\\*").replace("(", "\\(").replace(")", "\\)").replace("-", "\\-")
return message
def scrape_def(word: str) -> dict:
""" Scrapes the definition and audio file link of a word from lexico.com"""
2 years ago
ox_url = f'https://www.lexico.com/definition/{word}?locale=en'
def_page = requests.get(ox_url)
2 years ago
if def_page.status_code != 200:
print(f'Word not found: {selected_word}')
return None
2 years ago
my_dict = {}
def_soup = BeautifulSoup(def_page.content, 'html.parser')
2 years ago
word = def_soup.find_all("span", {"class": "hw"})
my_dict['word'] = slugify(word[0].text.capitalize())
grammatical_type = def_soup.find_all("span", {"class": "pos"})
my_dict['type'] = u'\u2022 ' + grammatical_type[0].text.capitalize()
definition = def_soup.find_all("span", {"class": "ind"})
audio = def_soup.find_all("audio")
my_dict['audio'] = audio[0]['src'].replace('https://lex-audio.useremarkable.com/mp3/', '')
my_dict['definition'] = []
for i, j in enumerate(definition):
my_dict['definition'].append(j.text)
return my_dict
def word_def(
update: telegram.update.Update,
context: telegram.ext.callbackcontext.CallbackContext
) -> None:
""" Responds to the /word command on the bot
Fetches the definition from Lexico.com
Constructs the message and callback keyboard
Sends the message
"""
con = connect_db()
if con is None:
print("Db connection failed")
word_query = 'select * from gre_words order by random() limit 1'
while True:
cur = con.cursor()
cur.execute(word_query)
row = cur.fetchall()
selected_word = row[0][1].lower()
cur.close()
my_dict = scrape_def(selected_word)
if my_dict is not None:
break
2 years ago
msg = "*{word}*\n_{type}_\n{defn}".format(
word=my_dict['word'], type=slugify(my_dict['type']),
defn='\n'.join([slugify(i) for i in my_dict['definition']]))
button_callback_data_audio = f"p~{my_dict['word']}~{my_dict['audio']}"
button_callback_data_synonyms = f"s~{my_dict['word']}"
2 years ago
keyboard = [
[
InlineKeyboardButton("Pronunciation", callback_data=button_callback_data_audio),
InlineKeyboardButton("Synonyms", callback_data=button_callback_data_synonyms)
2 years ago
]
]
2 years ago
print(keyboard)
reply_markup = InlineKeyboardMarkup(keyboard)
msg_response = context.bot.send_message(update.effective_chat.id, text=msg, parse_mode=ParseMode.MARKDOWN_V2, reply_markup=reply_markup)
print(type(msg_response))
def get_synonyms(word: str) -> str:
""" Fetches the synonyms of a word from lexico.com"""
2 years ago
syn_url = 'https://www.lexico.com/synonyms/{}?locale=en'
response = requests.get(syn_url.format(word.lower()))
soup = BeautifulSoup(response.content, 'html.parser')
synonyms = soup.find_all(['strong', 'span'], {'class': 'syn'})
synonym_list = [f'\n*{slugify(i.string)}*' if i.name == 'strong' else slugify(i.string) for i in synonyms]
2 years ago
return (''.join(synonym_list))
def button(
update: telegram.update.Update,
context: telegram.ext.callbackcontext.CallbackContext
) -> None:
""" Responds to callback buttons in the original message.
If the callback type is audio, then responds with already available audio URL
If the callback type is synonyms, then fetchs the synonyms and responds
"""
2 years ago
query = update.callback_query
data = query.data
request_type = data.split('~')[0]
word = data.split('~')[1]
2 years ago
query.answer()
if request_type == 'p':
#query.edit_message_reply_markup(reply_markup=None)
audio = f"https://lex-audio.useremarkable.com/mp3/{data.split('~')[2]}"
2 years ago
context.bot.send_audio(update.effective_chat.id, audio=audio, reply_to_message_id=query.message.message_id)
elif request_type == 's':
#query.edit_message_reply_markup(reply_markup=None)
synonyms = get_synonyms(word)
print(synonyms)
context.bot.send_message(update.effective_chat.id, text=synonyms, reply_to_message_id=query.message.message_id, parse_mode=ParseMode.MARKDOWN_V2)
def main() -> None:
""" Runs the bot and keeps it running """
2 years ago
wordbot_token = os.getenv('WORDBOT_TOKEN')
updater = Updater(token=wordbot_token, use_context=True)
dispatcher = updater.dispatcher
dispatcher.add_handler(CommandHandler('start', start))
dispatcher.add_handler(CommandHandler('word', word_def))
dispatcher.add_handler(CallbackQueryHandler(button))
updater.start_polling()
updater.idle()
if __name__ == '__main__':
print('word_bot is running. Press ctrl+c to stop.')
main()