I'm working on a bot that uses PRAW to scan all new comments in a user defined subreddit and searches for specific phrases to reply to. I've used PySimpleGUI for the user interface, as you can see here:
Image of GUI with text inputs for Reddit API authorization
Because of an issue where it freezes during long operations, I've had to implement threading. My code runs well as long as I'm running it in my IDLE (PyCharm), but after converting it to an executable, running it, entering my credentials along with the relevant terms, and clicking "RUN", I get a threading error:
ERROR Unable to complete operation on element with key 0
File "PySimpleGUI\PySimpleGUI.py"
line 20892
in_error_popup_with_traceback
You cannot perform operations (such as calling update) on an Element until:
window.read() is called or finalize=True when Window created.
Adding a "finalize=True" parameter to your Window creation will likely fix this.
The PySimpleGUI internal reporting function is _widget_was_created
The error originated from:
File "threading.py"
line 1319
in invoke_excepthook
Screenshot of this error message.
I've been working on this for two days now with no further progress, although I am brand new to python so I'm sure I'm missing something. Here is my full code:
#importing the necessary libraries
from threading import Thread
import praw
import subprocess
import sys
import time
from datetime import datetime
import PySimpleGUI as psg
#worker function
def bot(id, secret, key, agent, user, sub, keywords, body):
#initializes PRAW with user's credentials
reddit = praw.Reddit(
client_id=id,
client_secret=secret,
password=key,
user_agent=agent,
username=user)
#specifies which subreddit to perform the operation on
subreddit = reddit.subreddit(sub)
#initiates a live feed of new comments and checks for the user defined search term using for loops
for comment in subreddit.stream.comments():
if comment:
normalized_comment = comment.body.lower()
for phrase in keywords:
if phrase in normalized_comment:
#if the phrase is found, prints a timestamp and the comment to the console
current_time = datetime.now()
print(str(current_time) + " - Replying to: " + '"' + comment.body + '"')
#tries to reply to the comment found
try:
comment.reply(body=body)
# A reply has been made so do not attempt to match other phrases.
break
#if the bot is flagged for a cooldown, sleep for two minutes
except:
current_time = datetime.now()
print(str(current_time) + " - Rate limited, retrying in two minutes.")
time.sleep(120)
#this function creates the GUI
def gui():
psg.theme("Dark")
#layout for input window
layout = [[psg.Text("Personal Use Script", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Secret", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Bot Password", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Bot Call Sign", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Bot Username", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Subreddit to Search", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Trigger Phrase", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Text("Comment to Send", size=(15, 1), font='Lucida', justification='right'), psg.Input()],
[psg.Button("RUN", font=("Times New Roman", 12))]]
#this line initiates the GUI window
win = psg.Window("Find & Reply v 0.1", layout, finalize=True, element_justification='c')
#this line listens for any interactions with the elements on the window
e, v = win.read()
while True:
#closes the program if the window is closed
if e in (psg.WIN_CLOSED, 'Exit'):
break
#listens for the "RUN" button to be clicked and executes the bot function thread
if e == 'RUN':
#this line closes the initial input window
win.close()
bot_thread = Thread(target=bot, args=(v[0],v[1],v[2],v[3],v[4],v[5],v[6],v[7]))
bot_thread.start()
# layout for a secondary output window so the user can read the console
layout1 = [[psg.Output(size=(60, 15))]]
#initiates the secondary window
win1 = psg.Window("Scanning all new comments...", layout1, finalize=True, element_justification='c')
#listens for any interactions with the elements on the window
event, values = win1.Read()
#closes the program in the event the window is close (doesn't work)
if event in (psg.WIN_CLOSED, 'Exit'):
break
if event == 'Run': # the two lines of code needed to get button and run command
runCommand(cmd=values['_IN_'], window=win1)
win1.close()
#this function sends the console text to the secondary output window for the user to read
def runCommand(cmd, timeout=None, window=None):
p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
output = ''
for line in p.stdout:
line = line.decode(errors='replace' if (sys.version_info) < (3, 5) else 'backslashreplace').rstrip()
output += line
print(line)
window.Refresh() if window else None # yes, a 1-line if, so shoot me
retval = p.wait(timeout)
return (retval, output) # also return the output just for fun
if __name__ == '__main__':
#this is where I initiate the GUI thread
gui_thread = Thread(target=gui)
gui_thread.start()