def setUp(self): """Create/connect to development database""" self.SQL_SCRIPT = Path(DB_SQL_SCRIPT).read_text() # read a PosixPath file as str # db connection & creation self.db = DBHelper(filename="test.db") print("connecting to db... done.") self.db.setup()
def send_schedule(db: DBHelper): # Order = 0 1 2 3 4 5 6 weekdays = ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") study_days = (5, 6, 0, 1, 2) today = datetime.today().weekday() # What is today? if today in study_days: schedule = db.get_schedule_of(weekdays[today]) # get schedule of today # ================== formating the message to send msg_schedule_part = "" for idx, entry in enumerate(schedule): msg_schedule_part += str( idx + 1) + '. ' + entry[1] + ' at ' + entry[0] + '\n' msg = "Good morning, \n" \ "today is {0} and the schedule is: \n\n" \ "{1}".format(weekdays[today].title(), msg_schedule_part) users = db.get_users() # get list of all users for user in users: if user.active: log.info(f"Sending today's schedule to: {user}") send_message(user.chat_id, msg) # send today's schedule time.sleep( 0.5) # sleep for .5 second before sending to the next user
def handle_updates(updates: list, db: DBHelper): """Handles incoming updates to the bot""" global current_command # use current_command var from global scope for update in updates: # loop through updates # db.add_message((id: int, update_id: int, user_id: int, chat_id: int, date: int(unix_timestamp), text: str)) # TODO: common message and user data from the same update # Skip edited messages if not update.get("message"): continue # getting message data msg_id = update.get("message").get("message_id") # message id msg_update_id = update.get("update_id") # update id of this message msg_user_id = update.get("message").get("from").get("id") # sending user msg_chat_id = update.get("message").get("chat").get("id") # chat id of the message msg_date = update.get("message").get("date") # message date msg_text = update.get("message").get("text", "") # message text log.info("collecting message data... done") # Create Message object from incoming data msg = Message(msg_id, msg_update_id, msg_user_id, msg_chat_id, msg_date, msg_text) log.info("creating message object from collected data... done") if not db.get_message(msg.id): # if message doesn't exist already db.add_message(msg) log.info("New message saved.") # db.add_user((id: int, is_bot: int, is_admin: int, first_name: str, last_name: str, # username: str, language_code: str, active: int(0|1), created: int(unix_timestamp), # updated: int(unix_timestamp), last_command: str)) user_id = update.get("message").get("from").get("id") user_is_bot = update.get("message").get("from").get("is_bot") user_is_admin = False user_first_name = update.get("message").get("from").get("first_name") user_last_name = update.get("message").get("from").get("last_name") user_username = update.get("message").get("from").get("username") user_language_code = update.get("message").get("from").get("language_code", "en") user_active = True user_created = time.time() user_updated = time.time() user_last_command = None user_chat_id = update.get("message").get("chat").get("id") log.info("collecting user data... done") # if user doesn't exist, add him/her to db if not db.get_user(user_id): user = User(user_id, user_is_bot, user_is_admin, user_first_name, user_last_name, user_username, user_language_code, user_active, user_created, user_updated, user_last_command, user_chat_id) db.add_user(user) log.info("New user saved.") log.info("Old user..") # Create user object from saved data user = db.get_user(user_id) if not user.chat_id: log.info("User does't have a chat_id yet!") db.set_user_chat_id(user.id, time.time(), user_chat_id) log.info("Updated user's chat_id") # get user again after updating chat_id user = db.get_user(user_id) log.info("creating user object from collected data... done") user_last_command = user.last_command text = None # msg text chat = msg_chat_id # chat id log.debug("user: "******" sent a message - chat_id: " + str(msg_chat_id)) if msg_text: # handle text messages only text = msg_text.strip() # extract msg text if text and chat: # make sure we have txt msg and chat_id log.info('text message and chat_id are extracted.') if text.startswith("/"): # if command if is_available_command(text): # if command is available current_command = text # set current command log.info('command: "' + current_command + '" is available.') db.set_user_last_command(user.id, time.time(), current_command) # update user's last command if command_takes_input(current_command): # if command operates on inputs hint_message = get_hint_message(current_command) # get command hint message send_message(chat, hint_message) # send a help message to receive inputs later log.info('sending hint message to user... done') else: # if command is available and does not operate on inputs log.info('command: "' + current_command + '" has no argument.') # execute command directly if current_command == "/stop": get_command_handler(current_command)(db, user_id, time.time(), False) current_command = None elif current_command == "/start": get_command_handler(current_command)(db, user_id, time.time(), True) current_command = None else: send_message(chat, get_command_handler(current_command)()) # then unset current_command, commands_without_args execute once! current_command = None log.info("updating user current command.. one time cmd") db.set_user_last_command(user.id, time.time(), current_command) else: # if command is not available log.info('Undefined Command') send_message(chat, "Use a defined command.") else: # if sent message does not start with a slash log.info("working on user's last command.. " + str(user.last_command)) last_command = user.last_command if command_takes_input(last_command): # should be an argument if current_command is set log.info('received command arguments from user...') send_message(chat, get_command_handler(last_command)(text)) log.info('sending message to user... done') elif current_command == "/start" or current_command == "/stop": continue # skip else: log.info('Undefined Command.') send_message(chat, "Use a defined command.") else: # if no text message log.debug("A non text message is sent by user: "******" - chat id: " + str(chat)) send_message(chat, "I handle text messages only!")
try: log.info("getting updates...") updates = get_updates(updates_offset) # get new updates after last handled one if "result" in updates: # to prevent KeyError exception if len(updates["result"]) > 0: # make sure updates list is longer than 0 updates_offset = last_update_id(updates) + 1 # to remove handled updates handle_updates(updates["result"], db) # handle new (unhandled) updates else: log.info('no updates to be handled') time.sleep(0.5) # if it's 8 in the morning # # What is today? # # get schedule of today # # get list of all users # # for user in all_users # # # send today's schedule # # # sleep for .5 second except KeyboardInterrupt: # exit on Ctrl-C log.info("\nquiting...") exit(0) if __name__ == "__main__": # Setting DB db = DBHelper() db.setup() log.info("Running bot...") main(db)
def main(db: DBHelper): """The entry point""" updates_offset = None # track last_update_id to use it as offset while True: # infinitely listen to new updates (as long as the script is running) try: # =============================== Handling Schedule ============================================== # Order = 0 1 2 3 4 5 6 weekdays = ("monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday") study_days = (5, 6, 0, 1, 2) today = datetime.today().weekday() # What is today? now_in_egypt = str(format(datetime.utcnow() + timedelta(hours=2), "%H:%M:%S")) # Cairo time = UTC+2 now_in_egypt = dtime(*[int(x) for x in now_in_egypt.split(":")]) # to datetime.time object before_eight = dtime(7, 59, 45) # get before 8:00AM with 5 seconds after_eight = dtime(8, 0, 15) # get after 8:00AM with 5 seconds # if it's in range (07:59:55 |08:00| 08:00:05) in the morning if time_in_range(before_eight, after_eight, now_in_egypt): if today in study_days: schedule = db.get_schedule_of(weekdays[today]) # get schedule of today # ================== formating the message to send msg_schedule_part = "" for idx, entry in enumerate(schedule): msg_schedule_part += str(idx+1) + '. ' + entry[1] + ' at ' + entry[0] + '\n' msg = "Good morning, \n" \ "today is {0} and the schedule is: \n\n" \ "{1}".format(weekdays[today].title(), msg_schedule_part) users = db.get_users() # get list of all users for user in users: if user.active: log.info(f"Sending today's schedule to: {user}") send_message(user.chat_id, msg) # send today's schedule time.sleep(0.5) # sleep for .5 second before sending to the next user # =============================== Handling Announcements ========================================= # last_check: nonlocal var will be used to check for future announcement each 2 hours # for less resources consumption # get future announcements (where done column is) # if ann.done != "once" nor "twice" and ann.cancelled != true # send announcement to each active user # set ann.done = "once" # if ann.done="once" check timedelta # if timedelta < 24hrs # send the another announcement reminder, and mark ann.done="twice" # else: pass anns = db.get_announcements() for ann in anns: if ann.done == "": users = db.get_users() # get list of all users for user in users: if user.active: log.info(f"Sending announcement: {ann} schedule to: {user}") send_message(user.chat_id, ann.description) ann.done = "once" db.update_announcement(ann.id) time.sleep(0.5) # sleep for .5 second before sending to the next user elif ann.done == "once": # if ann.time - current_time = 1 day "YYYY-MM-DD HH:MM" if ann.time - timedelta: pass # =============================== Handling incoming messages ===================================== log.info("getting updates...") updates = get_updates(updates_offset) # get new updates after last handled one if "result" in updates: # to prevent KeyError exception if len(updates["result"]) > 0: # make sure updates list is longer than 0 updates_offset = last_update_id(updates) + 1 # to remove handled updates handle_updates(updates["result"], db) # handle new (unhandled) updates else: log.info('no updates to be handled') time.sleep(0.5) # delay the loop a .5 second except KeyboardInterrupt: # exit on Ctrl-C log.info("\nquiting...") exit(0)
class DBHelperTest(unittest.TestCase): def setUp(self): """Create/connect to development database""" self.SQL_SCRIPT = Path(DB_SQL_SCRIPT).read_text() # read a PosixPath file as str # db connection & creation self.db = DBHelper(filename="test.db") print("connecting to db... done.") self.db.setup() def tearDown(self): """Drop DB tables, close connection and remove the db later""" self.db.destroy() def test_add_message(self): # inserting the message msg = Message(1, 2, 3, 4, 5, "message 1") self.assertTrue(self.db.add_message(msg)) # making sure it was inserted right sql = "SELECT * FROM Message" got_msg = Message(*self.db.cur.execute(sql).fetchone()) self.assertEqual(msg.id, got_msg.id) self.assertEqual(msg.update_id, got_msg.update_id) self.assertEqual(msg.user_id, got_msg.user_id) self.assertEqual(msg.chat_id, got_msg.chat_id) self.assertEqual(msg.date, got_msg.date) self.assertEqual(msg.text, got_msg.text) print("testing add_message... done.") def test_get_message(self): # inserting the message sql = "INSERT INTO Message VALUES (?, ?, ?, ?, ?, ?)" params = (1, 2, 3, 4, 5, "message") self.db.cur.execute(sql, params) # making sure we get it right msg = self.db.get_message(1) self.assertTrue(isinstance(msg, Message)) self.assertEqual(msg.id, params[0]) self.assertEqual(msg.update_id, params[1]) self.assertEqual(msg.user_id, params[2]) self.assertEqual(msg.chat_id, params[3]) self.assertEqual(msg.date, params[4]) self.assertEqual(msg.text, params[5]) not_msg = self.db.get_message(2) self.assertFalse(not_msg) print("testing get_message... done.") def test_add_user(self): # insert new user user = User(70437390, False, True, "Ahmed", "Shahwan", "ash753", "en", True, 1555512911.45624, 1556303495.79887, "/calculate", 332324) self.assertTrue(self.db.add_user(user)) # testing if it's inserted correctly sql = "SELECT * FROM User" got_user = User(*self.db.cur.execute(sql).fetchone()) self.assertEqual(user.id, got_user.id) self.assertEqual(user.is_bot, got_user.is_bot) self.assertEqual(user.is_admin, got_user.is_admin) self.assertEqual(user.first_name, got_user.first_name) self.assertEqual(user.last_name, got_user.last_name) self.assertEqual(user.username, got_user.username) self.assertEqual(user.language_code, got_user.language_code) self.assertEqual(user.active, got_user.active) self.assertEqual(user.created, got_user.created) self.assertEqual(user.updated, got_user.updated) self.assertEqual(user.last_command, got_user.last_command) self.assertEqual(user.chat_id, got_user.chat_id) def test_get_user(self): # inserting user to db using sql sql = "INSERT INTO User VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" params = (70437390, False, True, "Ahmed", "Shahwan", "ash753", "en", True, 1555512911.45624, 1556303495.79887, "/calculate", 2345) self.db.cur.execute(sql, params) # user exists user = self.db.get_user(params[0]) self.assertTrue(isinstance(user, User)) self.assertEqual(user.id, params[0]) self.assertEqual(user.is_bot, params[1]) self.assertEqual(user.is_admin, params[2]) self.assertEqual(user.first_name, params[3]) self.assertEqual(user.last_name, params[4]) self.assertEqual(user.username, params[5]) self.assertEqual(user.language_code, params[6]) self.assertEqual(user.active, params[7]) self.assertEqual(user.created, params[8]) self.assertEqual(user.updated, params[9]) self.assertEqual(user.last_command, params[10]) self.assertEqual(user.chat_id, params[11]) # user doesn't exist not_user = self.db.get_user(111) self.assertTrue(not_user is None) def test_get_users(self): # add users using sql, then get them using the function # inserting users using sql sql = "INSERT INTO User VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)" user1 = (43739, False, False, "Ahme", "Shahwa", "ash75", "en", True, 51291, 30349, "/calculate", 33) user2 = (4373, False, True, "Ahm", "Shahw", "ash7", "en", False, 5129, 3034, "/translate", 555) user3 = (437, False, False, "Ah", "Shah", "ash", "en", True, 512, 303, "/ocr_url", 556) self.db.cur.execute(sql, user1) self.db.cur.execute(sql, user2) self.db.cur.execute(sql, user3) got_users = self.db.get_users() self.assertTrue(isinstance(got_users, list)) self.assertTrue(len(got_users) == 3) self.assertTrue(isinstance(got_users[0], User)) self.assertTrue(isinstance(got_users[1], User)) self.assertTrue(isinstance(got_users[2], User)) def test_set_user_last_command(self): # create a user in db with tested functions user = User(7043739, False, False, "Ahme", "Shahwa", "ash75", "en", True, 51291, 30349, "/calculate", 5554) self.db.add_user(user) # alter user's last command self.assertTrue(self.db.set_user_last_command(user.id, time.time(), "/translate")) # test alteration success got_user = self.db.get_user(user.id) self.assertEqual("/translate", got_user.last_command) def test_set_user_status(self): # create a user user = User(7043739, False, False, "Ahme", "Shahwa", "ash75", "en", True, 51291, 30349, "/calculate", 5556) self.db.add_user(user) # alter user's status self.assertTrue(self.db.set_user_status(user.id, time.time(), False)) # test alteration success got_user = self.db.get_user(user.id) self.assertEqual(False, got_user.active) def test_set_user_chat_id(self): # create a user without caht_id user = User(7043739, False, False, "Ahme", "Shahwa", "ash75", "en", True, 51291, 30349, "/calculate", None) self.db.add_user(user) # alter user's chat_id new_chat_id = 3456 self.db.set_user_chat_id(user.id, time.time(), new_chat_id) # test alternation got_user = self.db.get_user(user.id) self.assertEqual(got_user.chat_id, new_chat_id) def test_get_schedule(self): # get entries entries_list = self.db.get_schedule() self.assertTrue(isinstance(entries_list, list)) self.assertTrue(len(entries_list) == 14) self.assertTrue(isinstance(entries_list[0], ScheduleEntry)) self.assertTrue(entries_list[0].id, 1) self.assertTrue(isinstance(entries_list[1], ScheduleEntry)) self.assertTrue(entries_list[1].id, 2) self.assertTrue(isinstance(entries_list[2], ScheduleEntry)) self.assertTrue(entries_list[2].id, 3) print(entries_list[0], entries_list[1], entries_list[2]) def test_get_schedule_of(self): schedule = self.db.get_schedule_of("saturday") self.assertTrue(isinstance(schedule, list)) self.assertEqual(len(schedule), 3) self.assertTrue(isinstance(schedule[0], tuple)) self.assertTrue(isinstance(schedule[1], tuple)) self.assertTrue(isinstance(schedule[2], tuple)) print("=++Schedule++= ", schedule) def test_add_announcement(self): # add announcement ann = Announcement("08:30", "DSP Assignment 10 should be delivered tomorrow", "once") ann1 = Announcement("10:10", "Another test announcement", "twice") self.assertTrue(self.db.add_announcement(ann)) self.assertTrue(self.db.add_announcement(ann1)) # get announcement to test sql = "SELECT * FROM Announcement" result = self.db.cur.execute(sql) got_ann = result.fetchone() got_ann1 = result.fetchone() self.assertEqual(1, got_ann[0]) self.assertEqual(ann.time, got_ann[1]) self.assertEqual(ann.description, got_ann[2]) self.assertEqual(ann.done, got_ann[3]) self.assertEqual(2, got_ann1[0]) self.assertEqual(ann1.time, got_ann1[1]) self.assertEqual(ann1.description, got_ann1[2]) self.assertEqual(ann1.done, got_ann1[3]) def test_get_announcements(self): # add some announcements ann1 = Announcement("21:30", "DSP Assignment 10 should be delivered tomorrow", "") ann2 = Announcement("10:30", "Communication Lecture is cancelled", "once") ann3 = Announcement("09:30", "Dr Tamer is not coming again", "twice") self.db.add_announcement(ann1) self.db.add_announcement(ann2) self.db.add_announcement(ann3) # get what've been added and test it anns = self.db.get_announcements() self.assertTrue(isinstance(anns, list)) self.assertTrue(isinstance(anns[0], Announcement)) self.assertTrue(isinstance(anns[1], Announcement)) self.assertTrue(isinstance(anns[2], Announcement)) def test_update_announcement(self): # add an announcment self.db.add_announcement(Announcement("21:30", "DSP Assignment 10 should be delivered tomorrow", "")) ann = self.db.get_announcements()[0] # get announcment before update to use its id self.db.update_announcement(ann.id, "once") # update ann_updated = self.db.get_announcements()[0] # get announcment after update self.assertEqual(ann_updated.done, "once") # test updated value