from utils import helpers from datetime import datetime import arrow import tweepy from tkinter import messagebox import tkinter as tk import tkinter.ttk as ttk import shelve import sys sys.path.insert(0, "../utils") twitter_api = {} # neccesary global bool for the scheduler already_ran_bool = False def close_window(window, twitter_state, window_key): twitter_state[window_key] = 0 twitter_state.sync window.destroy() def build_window(root, window, title_text): def onFrameConfigure(canvas): '''Reset the scroll region to encompass the inner frame''' canvas.configure(scrollregion=canvas.bbox('all')) canvas = tk.Canvas(window, width=750, height=1000) frame = tk.Frame(canvas) scrollbar = tk.Scrollbar(window, command=canvas.yview) canvas.configure(yscrollcommand=scrollbar.set) scrollbar.pack(side=tk.RIGHT, fill=tk.Y) canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) canvas.create_window((4, 4), window=frame, anchor="nw") title_label = tk.Label( frame, text=title_text, font=('arial', 30)) frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas)) title_label.grid( row=0, column=0, columnspan=2, sticky='w') ttk.Separator(frame, orient=tk.HORIZONTAL).grid( row=2, columnspan=2, sticky='ew', pady=5) return frame def check_for_existence(string, twitter_state, value): """ Initialize a key/value pair if it doesn't already exist. :param string: the key :param twitter_state: dictionary holding reddit settings :param value: the value :return: none """ if string not in twitter_state: twitter_state[string] = value def set_twitter_login(consumer_key, consumer_secret, access_token, access_token_secret, login_confirm_text, twitter_state): """ Logs into twitter using tweepy, gives user an error on failure :param consumer_key: input received from the UI :param consumer_secret: input received from the UI :param access_token: input received from the UI :param access_token_secret: input received from the UI :param login_confirm_text: confirmation text - shown to the user in the UI :param twitter_state: dictionary holding twitter settings :return: none """ global twitter_api auth = tweepy.OAuthHandler(consumer_key, consumer_secret) auth.set_access_token(access_token, access_token_secret) api = tweepy.API(auth) twitter_username = api.me().screen_name login_confirm_text.set(f'Logged in to Twitter as {twitter_username}') twitter_api = api twitter_state['login_info'] = { 'consumer_key': consumer_key, 'consumer_secret': consumer_secret, 'access_token': access_token, 'access_token_secret': access_token_secret } check_for_existence('time_to_save', twitter_state, arrow.utcnow().replace(hours=0)) check_for_existence('max_favorites', twitter_state, 0) check_for_existence('max_retweets', twitter_state, 0) check_for_existence('whitelisted_tweets', twitter_state, {}) check_for_existence('whitelisted_favorites', twitter_state, {}) check_for_existence('scheduled_time', twitter_state, 0) twitter_state['scheduler_bool'] = 0 twitter_state['whitelist_window_open'] = 0 twitter_state['confirmation_window_open'] = 0 twitter_state.sync def set_twitter_time_to_save(hours_to_save, days_to_save, weeks_to_save, years_to_save, current_time_to_save, twitter_state): """ See set_time_to_save function in utils/helpers.py """ twitter_state['hours'] = hours_to_save twitter_state['days'] = days_to_save twitter_state['weeks'] = weeks_to_save twitter_state['years'] = years_to_save twitter_state['time_to_save'] = helpers.set_time_to_save( hours_to_save, days_to_save, weeks_to_save, years_to_save, current_time_to_save) twitter_state.sync def set_twitter_max_favorites(max_favorites, current_max_favorites, twitter_state): """ See set_max_score function in utils/helpers.py """ twitter_state['max_favorites'] = helpers.set_max_score( max_favorites, current_max_favorites, 'favorites') twitter_state.sync def set_twitter_max_retweets(max_retweets, current_max_retweets, twitter_state): """ See set_max_score function in helpers.py """ twitter_state['max_retweets'] = helpers.set_max_score( max_retweets, current_max_retweets, 'retweets') twitter_state.sync def gather_items(item_getter): """ Keeps making calls to twitter to gather all the items the API can index and build an array of them :param item_getter: the function call being made to twitter to get tweets or favorites from the user's account :return user_items: an array of the items gathered """ user_items = [] new_items = item_getter(count=200) user_items.extend(new_items) oldest = user_items[-1].id - 1 while len(new_items) > 0: new_items = item_getter(count=200, max_id=oldest) user_items.extend(new_items) oldest = user_items[-1].id - 1 return user_items def delete_twitter_tweets(root, currently_deleting_text, deletion_progress_bar, num_deleted_items_text, twitter_state, scheduled_bool): """ Deletes user's tweets according to user configurations. :param root: the reference to the actual tkinter GUI window :param currently_deleting_text: Describes the item that is currently being deleted. :param deletion_progress_bar: updates as the items are looped through :param num_deleted_items_text: updates as X out of Y comments are looped through :param twitter_state: dictionary holding twitter settings :param scheduled_bool: True if a scheduled run, False if triggered manually :return: none """ if twitter_state['confirmation_window_open'] == 1 and not scheduled_bool: return global twitter_api confirmation_window = tk.Toplevel(root) twitter_state['confirmation_window_open'] = 1 twitter_state.sync confirmation_window.protocol( 'WM_DELETE_WINDOW', lambda: close_window(confirmation_window, twitter_state, 'confirmation_window_open')) frame = build_window(root, confirmation_window, f"The following tweets will be deleted") user_tweets = gather_items(twitter_api.user_timeline) total_tweets = len(user_tweets) num_deleted_items_text.set(f'0/{str(total_tweets)} items processed so far') button_frame = tk.Frame(frame) button_frame.grid(row=1, column=0, sticky='w') def delete_tweets(): close_window(confirmation_window, twitter_state, 'confirmation_window_open') count = 1 for tweet in user_tweets: tweet_snippet = helpers.format_snippet(tweet.text, 50) time_created = arrow.Arrow.fromdatetime(tweet.created_at) if time_created > twitter_state['time_to_save']: currently_deleting_text.set( f'Tweet: `{tweet_snippet}` is more recent than cutoff, skipping.') elif tweet.favorite_count >= twitter_state['max_favorites']: currently_deleting_text.set( f'Tweet: `{tweet_snippet}` has more favorites than max favorites, skipping.') elif tweet.retweet_count >= twitter_state['max_retweets'] and not tweet.retweeted: currently_deleting_text.set( f'Tweet: `{tweet_snippet}` has more retweets than max retweets, skipping.') elif tweet.id in twitter_state['whitelisted_tweets'] and twitter_state['whitelisted_tweets'][tweet.id]: currently_deleting_text.set( f'Tweet: `{tweet_snippet}` is whitelisted, skipping.') else: currently_deleting_text.set( f'Deleting tweet: `{tweet_snippet}`') twitter_api.destroy_status(tweet.id) num_deleted_items_text.set( f'{str(count)}/{str(total_tweets)} items processed.') deletion_progress_bar['value'] = round( (count / total_tweets) * 100, 1) root.update() count += 1 proceed_button = tk.Button( button_frame, text='Proceed', command=lambda: delete_tweets()) cancel_button = tk.Button(button_frame, text='Cancel', command=lambda: close_window(confirmation_window, twitter_state, 'confirmation_window_open')) proceed_button.grid(row=1, column=0, sticky='nsew') cancel_button.grid(row=1, column=1, sticky='nsew') counter = 3 for tweet in user_tweets: time_created = arrow.Arrow.fromdatetime(tweet.created_at) if time_created > twitter_state['time_to_save']: pass elif tweet.favorite_count >= twitter_state['max_favorites']: pass elif tweet.retweet_count >= twitter_state['max_retweets'] and not tweet.retweeted: pass elif tweet.id in twitter_state['whitelisted_tweets'] and twitter_state['whitelisted_tweets'][tweet.id]: pass else: tk.Label(frame, text=helpers.format_snippet( tweet.text, 100)).grid(row=counter, column=0) ttk.Separator(frame, orient=tk.HORIZONTAL).grid( row=counter+1, columnspan=2, sticky='ew', pady=5) counter = counter + 2 def delete_twitter_favorites(root, currently_deleting_text, deletion_progress_bar, num_deleted_items_text, twitter_state, scheduled_bool): """ Deletes users's favorites according to user configurations. :param root: the reference to the actual tkinter GUI window :param currently_deleting_text: Describes the item that is currently being deleted. :param deletion_progress_bar: updates as the items are looped through :param num_deleted_items_text: updates as X out of Y comments are looped through :param twitter_state: dictionary holding twitter settings :param scheduled_bool: True if a scheduled run, False if triggered manually :return: none """ if twitter_state['confirmation_window_open'] == 1 and not scheduled_bool: return global twitter_api confirmation_window = tk.Toplevel(root) twitter_state['confirmation_window_open'] = 1 twitter_state.sync confirmation_window.protocol( 'WM_DELETE_WINDOW', lambda: close_window(confirmation_window, twitter_state, 'confirmation_window_open')) frame = build_window(root, confirmation_window, f"The following favorites will be removed") user_favorites = gather_items(twitter_api.favorites) total_favorites = len(user_favorites) num_deleted_items_text.set( f'0/{str(total_favorites)} items processed so far') button_frame = tk.Frame(frame) button_frame.grid(row=1, column=0, sticky='w') def delete_favorites(): close_window(confirmation_window, twitter_state, 'confirmation_window_open') count = 1 for favorite in user_favorites: favorite_snippet = helpers.format_snippet(favorite.text, 50) currently_deleting_text.set( f'Deleting favorite: `{favorite_snippet}`') time_created = arrow.Arrow.fromdatetime(favorite.created_at) if time_created > twitter_state['time_to_save']: currently_deleting_text.set( f'Favorite: `{favorite_snippet}` is more recent than cutoff, skipping.') elif favorite.id in twitter_state['whitelisted_favorites'] and twitter_state['whitelisted_favorites'][favorite.id]: currently_deleting_text.set( f'Favorite: `{favorite_snippet}` is whitelisted, skipping.') else: currently_deleting_text.set( f'Deleting favorite: `{favorite_snippet}`') twitter_api.destroy_favorite(favorite.id) num_deleted_items_text.set( f'{str(count)}/{str(total_favorites)} items processed.') deletion_progress_bar['value'] = round( (count / total_favorites) * 100, 1) root.update() count += 1 proceed_button = tk.Button( button_frame, text='Proceed', command=lambda: delete_favorites()) cancel_button = tk.Button(button_frame, text='Cancel', command=lambda: close_window(confirmation_window, twitter_state, 'confirmation_window_open')) proceed_button.grid(row=1, column=0, sticky='nsew') cancel_button.grid(row=1, column=1, sticky='nsew') counter = 3 for favorite in user_favorites: time_created = arrow.Arrow.fromdatetime(favorite.created_at) if time_created > twitter_state['time_to_save']: pass elif favorite.id in twitter_state['whitelisted_favorites'] and twitter_state['whitelisted_favorites'][favorite.id]: pass else: tk.Label(frame, text=helpers.format_snippet( favorite.text, 100)).grid(row=counter, column=0) ttk.Separator(frame, orient=tk.HORIZONTAL).grid( row=counter+1, columnspan=2, sticky='ew', pady=5) counter = counter + 2 def set_twitter_scheduler(root, scheduler_bool, hour_of_day, string_var, progress_var, current_time_text, twitter_state): """ The scheduler that users can use to have social amnesia wipe tweets/favorites at a set point in time, repeatedly. :param root: tkinkter window :param scheduler_bool: true if set to run, false otherwise :param hour_of_day: int 0-23, sets hour of day to run on :param string_var, progress_var - empty Vars needed to run the deletion functions :param current_time_text: The UI text saying "currently set to TIME" :param twitter_state: dictionary holding twitter settings :return: none """ twitter_state['scheduler_bool'] = scheduler_bool.get() twitter_state.sync global already_ran_bool if not scheduler_bool.get(): already_ran_bool = False return twitter_state['scheduled_time'] = hour_of_day twitter_state.sync current_time_text.set(f'Currently set to: {hour_of_day}') current_time = datetime.now().time().hour if current_time == hour_of_day and not already_ran_bool: messagebox.showinfo( 'Scheduler', 'Social Amnesia is now erasing your past on twitter.') delete_twitter_tweets( root, string_var, progress_var, string_var, twitter_state, True) delete_twitter_favorites( root, string_var, progress_var, string_var, twitter_state, True) already_ran_bool = True if current_time < 23 and current_time == hour_of_day + 1: already_ran_bool = False elif current_time == 0: already_ran_bool = False root.after(1000, lambda: set_twitter_scheduler( root, scheduler_bool, hour_of_day, string_var, progress_var, current_time_text, twitter_state)) def set_twitter_whitelist(root, tweet_bool, twitter_state): """ Creates a window to let users select which tweets or favorites to whitelist :param root: the reference to the actual tkinter GUI window :param tweet_bool: true for tweets, false for favorites :param twitter_state: dictionary holding twitter settings :return: none """ global twitter_api # TODO: update this to get whether checkbox is selected or unselected instead of blindly flipping from true to false def flip_whitelist_dict(id, identifying_text): whitelist_dict = twitter_state[f'whitelisted_{identifying_text}'] whitelist_dict[id] = not whitelist_dict[id] twitter_state[f'whitelisted_{identifying_text}'] = whitelist_dict twitter_state.sync def onFrameConfigure(canvas): '''Reset the scroll region to encompass the inner frame''' canvas.configure(scrollregion=canvas.bbox("all")) def closeWindow(whitelist_window): twitter_state['whitelist_window_open'] = 0 whitelist_window.destroy() if tweet_bool: identifying_text = 'tweets' item_array = gather_items(twitter_api.user_timeline) else: identifying_text = 'favorites' item_array = gather_items(twitter_api.favorites) whitelist_window = tk.Toplevel(root) twitter_state['whitelist_window_open'] = 1 whitelist_window.protocol( 'WM_DELETE_WINDOW', lambda: closeWindow(whitelist_window)) frame = build_window(root, whitelist_window, f'Pick {identifying_text} to save') counter = 3 for item in item_array: if (item.id not in twitter_state[f'whitelisted_{identifying_text}']): # I wish I could tell you why I need to copy the dictionary of whitelisted items, and then modify it, and then # reassign it back to the persistant shelf. I don't know why this is needed, but it works. whitelist_dict = twitter_state[f'whitelisted_{identifying_text}'] whitelist_dict[item.id] = False twitter_state[f'whitelisted_{identifying_text}'] = whitelist_dict twitter_state.sync whitelist_checkbutton = tk.Checkbutton(frame, command=lambda id=item.id: flip_whitelist_dict(id, identifying_text)) if (twitter_state[f'whitelisted_{identifying_text}'][item.id]): whitelist_checkbutton.select() else: whitelist_checkbutton.deselect() whitelist_checkbutton.grid(row=counter, column=0) tk.Label(frame, text=helpers.format_snippet(item.text, 100)).grid(row=counter, column=1) ttk.Separator(frame, orient=tk.HORIZONTAL).grid( row=counter+1, columnspan=2, sticky=(tk.E, tk.W), pady=5) counter = counter + 2