diff --git a/bot2/api/__init__.py b/bot2/api/__init__.py index 924f953..d5f9e9b 100644 --- a/bot2/api/__init__.py +++ b/bot2/api/__init__.py @@ -1,4 +1,5 @@ -from . import google from . import keys from . import reddit -from . import weather \ No newline at end of file +from . import weather +from . import reddit +from . import search \ No newline at end of file diff --git a/bot2/api/google.py b/bot2/api/google.py deleted file mode 100644 index 2ff701c..0000000 --- a/bot2/api/google.py +++ /dev/null @@ -1,20 +0,0 @@ -import googlesearch - - -def query(params): - param_string = "" - for word in params: - param_string += word + "+" - param_string = param_string[:-1] - search_url = "https://google.com/search?q=" + param_string - - try: - res = googlesearch.search(param_string.replace("+"," ") ,num=5,start=0,stop=5) - send_string = "Results for " + param_string.replace("+"," ") + ":\n\n" - for url in res: - send_string += url + "\n\n" - send_string += "Search url:\n" + search_url - except: - send_string = "Search url:\n" + search_url - - return send_string diff --git a/bot2/api/reddit.py b/bot2/api/reddit.py index 49ee0d3..2cd5112 100644 --- a/bot2/api/reddit.py +++ b/bot2/api/reddit.py @@ -1,50 +1,56 @@ import praw -try: - import bot.api.keys as keys -except: - import keys - -stream = praw.Reddit(client_id = keys.reddit_id, client_secret = keys.reddit_secret, user_agent=keys.reddit_user_agent) - -def get_top(subreddit, number, return_type="text"): - if return_type == "text": - message = "" - try: - for submission in stream.subreddit(subreddit).top(limit=number): - if not submission.stickied: - message += "" + submission.title + "" + "\n" + submission.selftext + "\n\n\n" - return message - except: - return "Api call failed, sorry" - else: - images = [] - try: - for submission in stream.subreddit(subreddit).top(limit=number): - if not submission.stickied: - t = {"image": submission.url, "caption": submission.title} - images.append(t) - return images - except: - return ["Api call failed, sorry"] -def get_random_rising(subreddit, number, return_type="text"): - if return_type == "text": - message = "" - try: - for submission in stream.subreddit(subreddit).random_rising(limit=number): - if not submission.stickied: - message += "" + submission.title + "" + "\n" + submission.selftext + "\n\n\n" - return message - except: - return "Api call failed, sorry" - else: - images = [] - try: - for submission in stream.subreddit(subreddit).random_rising(limit=number): - if not submission.stickied: - t = {"image": submission.url, "caption": submission.title} - images.append(t) - return images - except: - return ["Api call failed, sorry"] + +class RedditFetch(): + def __init__(self, key): + self.stream = praw.Reddit(client_id = key["id"], client_secret = key["secret"], user_agent=key["user_agent"]) + + def get_top(self, subreddit, number, return_type="text"): + if return_type == "text": + posts = [] + try: + for submission in self.stream.subreddit(subreddit).top(limit=number): + p = {} + if not submission.stickied: + p["title"] = submission.title + p["content"] = submission.selftext + posts.append(p) + return posts + except: + return [] + else: + images = [] + try: + for submission in self.stream.subreddit(subreddit).top(limit=number): + if not submission.stickied: + t = {"image": submission.url, "caption": submission.title} + images.append(t) + return images + except: + return [] + + + def get_random_rising(self, subreddit, number, return_type="text"): + if return_type == "text": + posts = [] + try: + for submission in self.stream.subreddit(subreddit).random_rising(limit=number): + p = {} + if not submission.stickied: + p["title"] = submission.title + p["content"] = submission.selftext + posts.append(p) + return posts + except: + return [] + else: + images = [] + try: + for submission in self.stream.subreddit(subreddit).random_rising(limit=number): + if not submission.stickied: + t = {"image": submission.url, "caption": submission.title} + images.append(t) + return images + except: + return [] diff --git a/bot2/api/search.py b/bot2/api/search.py new file mode 100644 index 0000000..adf1ee6 --- /dev/null +++ b/bot2/api/search.py @@ -0,0 +1,21 @@ +import duckduckpy + +class WebSearch(): + def __init__(self): + self.search = duckduckpy.query + + def get_result(self, query): + try: + res = [] + response = self.search(query, container = "dict")["related_topics"] + for r in response: + if "text" in r: + res.append({ + "text" : r["text"], + "url": r["first_url"] + }) + except: + res = ["Connection error"] + return res + +# TODO: this api has more potential. Extract images or quick facts! \ No newline at end of file diff --git a/bot2/commands/__init__.py b/bot2/commands/__init__.py index 561818f..7483e3d 100644 --- a/bot2/commands/__init__.py +++ b/bot2/commands/__init__.py @@ -1,2 +1,2 @@ # Placeholder -from . import clock, help, weather, status, zvv, lists, alias, plaintext +from . import clock, help, weather, status, zvv, lists, alias, plaintext, reddit, search diff --git a/bot2/commands/help.py b/bot2/commands/help.py index 4597cec..4ed8646 100644 --- a/bot2/commands/help.py +++ b/bot2/commands/help.py @@ -1,6 +1,6 @@ from .template import * -FIRST = 1 +FIRST, EXECUTE = range(2) class Help(BotFunc): @@ -20,6 +20,7 @@ class Help(BotFunc): CallbackQueryHandler(self.choose_specific, pattern="^specific$"), CallbackQueryHandler(self.print_one, pattern='func-'), ], + EXECUTE :[CallbackQueryHandler(self.execute_now)], # ConversationHandler.TIMEOUT : [ # CallbackQueryHandler(self.timeout) # ] @@ -46,7 +47,10 @@ class Help(BotFunc): ] ] reply_markup = InlineKeyboardMarkup(keyboard) - update.message.reply_text("What exactly do you want?", reply_markup=reply_markup) + if update.message: + update.message.reply_text("What exactly do you want?", reply_markup=reply_markup) + else: + update._effective_chat.send_message("What exactly do you want?", reply_markup=reply_markup) return FIRST @@ -82,13 +86,28 @@ class Help(BotFunc): query.answer() message = name + ": `" + self.available_commands[name] + "`" + + keyboard = [[InlineKeyboardButton("Call " + name + " now", callback_data=name),]] + reply_markup = InlineKeyboardMarkup(keyboard) + query.edit_message_text( text= message, + #reply_markup = reply_markup, parse_mode = ParseMode.MARKDOWN_V2 ) - return ConversationHandler.END - + return ConversationHandler.END #EXECUTE + def execute_now(self, update: Update, context: CallbackContext) -> None: + query = update.callback_query + name = query.data + query.answer() + funcs = context.dispatcher.handlers[0] + for func in funcs: + if name == func.entry_points[0].command[0]: + break + callback = func.entry_points[0].callback + func.callback(update, context) + return FIRST def timeout(self, update: Update, context: CallbackContext) -> None: """For dying conversation. Currently unused.""" diff --git a/bot2/commands/reddit.py b/bot2/commands/reddit.py new file mode 100644 index 0000000..bfaadeb --- /dev/null +++ b/bot2/commands/reddit.py @@ -0,0 +1,177 @@ +from .template import * + + +CHOOSE_NUM = 1 +class Joke(BotFunc): + """Tells a joke from reddit.""" + + def __init__(self, api, prst): + super().__init__(prst) + self.available_commands = {} + self.api = api + + + def create_handler(self): + conv_handler = ConversationHandler( + entry_points=[CommandHandler('joke', self.entry_point)], + states={ + CHOOSE_NUM: [CallbackQueryHandler(self.get_jokes),], + }, + fallbacks=[CommandHandler('joke', self.entry_point)], + # conversation_timeout=5, + ) + return conv_handler + + + def entry_point(self, update: Update, context: CallbackContext) -> None: + super().entry_point() + keyboard = [[InlineKeyboardButton(str(i), callback_data=str(i)) for i in range(1,11)]] + reply_markup = InlineKeyboardMarkup(keyboard) + update.message.reply_text("How many jokes?", reply_markup=reply_markup) + return CHOOSE_NUM + + + def get_jokes(self, update: Update, context: CallbackContext) -> None: + query = update.callback_query + number = int(query.data) + query.answer() + jokes = self.api.get_random_rising("jokes", number, "text") + # formating + message = "" + for j in jokes: + message += "" + j["title"] + " \n" + j["content"] + "\n\n" + if message == "": + message += "Could not fetch jokes." + query.edit_message_text(text = message, parse_mode = ParseMode.HTML) + return ConversationHandler.END + + + + +CHOOSE_TOPIC = 0 +class Meme(BotFunc): + """Gets the latest memes from reddit""" + + def __init__(self, api, prst): + super().__init__(prst) + self.available_commands = {} + self.api = api + + + def create_handler(self): + conv_handler = ConversationHandler( + entry_points=[CommandHandler('meme', self.entry_point)], + states={ + CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)], + CHOOSE_NUM :[CallbackQueryHandler(self.get_memes)], + }, + fallbacks=[CommandHandler('meme', self.entry_point)], + ) + return conv_handler + + + def entry_point(self, update: Update, context: CallbackContext) -> None: + super().entry_point() + + keyboard = [ + [InlineKeyboardButton("General", callback_data="memes"),], + [InlineKeyboardButton("Dank memes", callback_data="dankmemes"),], + [InlineKeyboardButton("Maths", callback_data="mathmemes"),], + [InlineKeyboardButton("Physics", callback_data="physicsmemes"),], + [InlineKeyboardButton("Biology", callback_data="biologymemes"),], + ] + reply_markup = InlineKeyboardMarkup(keyboard) + update.message.reply_text("What kind of memes?", reply_markup=reply_markup) + return CHOOSE_TOPIC + + + def choose_topic(self, update: Update, context: CallbackContext) -> None: + super().entry_point() + query = update.callback_query + d = query.data + query.answer() + + keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]] + reply_markup = InlineKeyboardMarkup(keyboard) + query.edit_message_text("How many memes?", reply_markup=reply_markup) + return CHOOSE_NUM + + + def get_memes(self, update: Update, context: CallbackContext) -> None: + query = update.callback_query + data = query.data.split("-") + query.answer() + + memes = self.api.get_random_rising(data[0], int(data[1]), "photo") + if len(memes) != 0: + for m in memes: + update.effective_chat.send_photo(photo = m["image"],caption = m["caption"]) + else: + update.effective_chat.send_message("Sorry, the meme won't yeet.") + return ConversationHandler.END + + + + +# class News(BotFunc): +# """Gets the latest news from reddit""" + +# def __init__(self, api, prst): +# super().__init__(prst) +# self.available_commands = {} +# self.api = api + + +# def create_handler(self): +# conv_handler = ConversationHandler( +# entry_points=[CommandHandler('news', self.entry_point)], +# states={ +# CHOOSE_TOPIC: [CallbackQueryHandler(self.choose_topic)], +# CHOOSE_NUM :[CallbackQueryHandler(self.get_news)], +# }, +# fallbacks=[CommandHandler('news', self.entry_point)], +# ) +# return conv_handler + + +# def entry_point(self, update: Update, context: CallbackContext) -> None: +# super().entry_point() + +# keyboard = [ +# [InlineKeyboardButton("World", callback_data="worldnews"),], +# [InlineKeyboardButton("Germany", callback_data="germannews"),], +# [InlineKeyboardButton("France", callback_data="francenews"),], +# [InlineKeyboardButton("Europe", callback_data="eunews"),], +# [InlineKeyboardButton("USA", callback_data="usanews"),], +# ] +# reply_markup = InlineKeyboardMarkup(keyboard) +# update.message.reply_text("What kind of news?", reply_markup=reply_markup) +# return CHOOSE_TOPIC + + +# def choose_topic(self, update: Update, context: CallbackContext) -> None: +# super().entry_point() +# query = update.callback_query +# d = query.data +# query.answer() + +# keyboard = [[InlineKeyboardButton(str(i), callback_data=d + "-" + str(i)) for i in range(1,11)]] +# reply_markup = InlineKeyboardMarkup(keyboard) +# query.edit_message_text("How many entries?", reply_markup=reply_markup) +# return CHOOSE_NUM + + +# def get_news(self, update: Update, context: CallbackContext) -> None: +# query = update.callback_query +# data = query.data.split("-") +# query.answer() +# #try: +# news = self.api.get_top(data[0], data[1], "text") +# # formating +# message = "" +# for j in news: +# message += "" + j["title"] + " \n" + j["content"] + "\n\n" +# if message == "": +# message += "Could not fetch news." +# query.edit_message_text(news, paresemode=ParseMode.HTML) +# return ConversationHandler.END \ No newline at end of file diff --git a/bot2/commands/search.py b/bot2/commands/search.py new file mode 100644 index 0000000..c8a679c --- /dev/null +++ b/bot2/commands/search.py @@ -0,0 +1,58 @@ +from .template import * + + +SEARCH, MORE = range(2) +class Search(BotFunc): + """Browse the web for a topic.""" + + def __init__(self, api, prst): + super().__init__(prst) + self.available_commands = {} + self.api = api + + + def create_handler(self): + conv_handler = ConversationHandler( + entry_points=[CommandHandler('search', self.entry_point)], + states={ + SEARCH: [MessageHandler(Filters.text, self.get_results),], + MORE: [CallbackQueryHandler(self.show_more, pattern="^more$"),], + }, + fallbacks=[CommandHandler('search', self.entry_point)], + conversation_timeout=20, + ) + return conv_handler + + + def entry_point(self, update: Update, context: CallbackContext) -> None: + super().entry_point() + + update.message.reply_text("What are we searching?") + return SEARCH + + + def get_results(self, update: Update, context: CallbackContext) -> None: + search = update.message.text + results = self.api.get_result(search) + keyboard = [[InlineKeyboardButton("More!", callback_data="more")]] + reply_markup = InlineKeyboardMarkup(keyboard) + + # formating + self.results = results + first = results[0] + message = first["text"] + "\n(" + first["url"] + ")\n" + + update.message.reply_text(text = message, reply_markup=reply_markup) + return MORE + + + def show_more(self, update: Update, context: CallbackContext) -> None: + query = update.callback_query + query.answer() + + message = "" + for r in self.results: + message += r["text"] + "\n(" + r["url"] + ")\n" + + query.edit_message_text(message) + return ConversationHandler.END \ No newline at end of file diff --git a/bot2/commands/status.py b/bot2/commands/status.py index 03ac9bd..c762414 100644 --- a/bot2/commands/status.py +++ b/bot2/commands/status.py @@ -34,7 +34,6 @@ class Status(BotFunc): def entry_point(self, update: Update, context: CallbackContext) -> None: super().entry_point() - user = update.message.from_user keyboard = [ [ InlineKeyboardButton("And the log?", callback_data="full"), @@ -72,7 +71,10 @@ class Status(BotFunc): tot_e = np.array(self.persistence["bot"]["execute_activity"]["count"]).sum() message += "Commands executed `" + str(tot_e) + "`\n" - update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) + if update.message: + update.message.reply_text(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) + else: + update._effective_chat.send_message(message, reply_markup=reply_markup, parse_mode=ParseMode.MARKDOWN) return FIRST diff --git a/bot2/main.py b/bot2/main.py index 932d958..95c1812 100644 --- a/bot2/main.py +++ b/bot2/main.py @@ -21,7 +21,8 @@ class ChatBot(): self.persistence = prst # Import submodules self.api_weather = api.weather.WeatherFetch(api.keys.weather_api) - # self.reddit_api = api.reddit.RedditFetch() + self.api_reddit = api.reddit.RedditFetch(api.keys.reddit_api) + self.api_search = api.search.WebSearch() # and so on self.telegram = Updater(api.keys.telegram_api, use_context=True) @@ -37,17 +38,15 @@ class ChatBot(): "status" : self.commands.status.Status(name, version, prst), "zvv" : self.commands.zvv.Zvv(prst), "list" : self.commands.lists.Lists(prst), - #"alias" : commands.alias.Alias(self.dispatcher, prst), + # "alias" : commands.alias.Alias(self.dispatcher, prst), + "joke" : self.commands.reddit.Joke(self.api_reddit, prst), + "meme" : self.commands.reddit.Meme(self.api_reddit, prst), + # "news" : self.commands.reddit.News(self.api_reddit, prst), + "search" : self.commands.search.Search(self.api_search, prst), + + "plaintext" : self.commands.plaintext.Plain(prst) # for handling non-command messages that should simply contribute to statistics } - - # "events" : self.bot_print_events, - # "wikipedia" : self.bot_show_wikipedia, - # "cronjob" : self.bot_cronjob, - # "joke" : self.bot_tell_joke, - # "meme" : self.bot_send_meme, - # "news" : self.bot_send_news, - # } # must be a class that has a method create_handler def add_commands(self): diff --git a/launcher.py b/launcher.py index 195e8c2..d7bdd72 100644 --- a/launcher.py +++ b/launcher.py @@ -41,12 +41,13 @@ class Launcher(): self.dashboard_module = dashboard.main.DashBoard(host_ip="0.0.0.0", prst=self.persistence) self.modules = { - "clock" : self.clock_module, "bot" : self.bot_module, + "clock" : self.clock_module, "dashboard" : self.dashboard_module, } for module in self.modules.values(): + self.logger.info("Starting module "+ module.__class__.__name__) module.modules = self.modules module.start()