From 4dca99e092b94126b41e037ea9f47f3cd97338ea Mon Sep 17 00:00:00 2001 From: unknown <74205780+Chenneth@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:27:22 -0500 Subject: [PATCH 01/36] Added example config file --- example_praw.ini | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 example_praw.ini diff --git a/example_praw.ini b/example_praw.ini new file mode 100644 index 0000000..7807c82 --- /dev/null +++ b/example_praw.ini @@ -0,0 +1,4 @@ +[reddit storage bot] +client_id = p-jcoLKBynTLew +client_secret = gko_LXELoV07ZBNUXrvWZfzE3aI +user_agent = platform:app_id:versin_id (By u/reddit) \ No newline at end of file From 6283424fb102b5659f941e02e44567d88dc90e56 Mon Sep 17 00:00:00 2001 From: unknown <74205780+Chenneth@users.noreply.github.com> Date: Tue, 28 Mar 2023 15:28:23 -0500 Subject: [PATCH 02/36] Changed prints to use python3 syntax --- RedditStorage.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index d58c030..86077d0 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -75,11 +75,11 @@ def subredditListener(self, subredditName, username, password): index = 0 self.myRowDict = {} #dictionary used for retrival later for x in posts: - print str(x) + print (str(x)) self.fileList.InsertStringItem(index, x.title) self.myRowDict[index] = x.title index+=1 - print "done" + print ("done") @@ -345,7 +345,7 @@ def onClickGetItem(self, e): self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) def onClickGetRedditList(self, e): if(self.subredditField.IsEmpty() is False): - print self.subredditField.GetValue() + print (self.subredditField.GetValue()) frame = RedditList() pub.sendMessage("subredditListener", subredditName = self.subredditField.GetValue() , username = self.usernameField.GetValue(), password = self.passwordField.GetValue()) From b0e669b6914b643c591986889abad7e99692bb56 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Tue, 28 Mar 2023 16:50:16 -0500 Subject: [PATCH 03/36] Changed functions to match updated praw functions --- RedditStorage.py | 307 +++++++++++++++++++++++------------------------ reddit.py | 29 +++-- 2 files changed, 165 insertions(+), 171 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 86077d0..ad34c4e 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -14,89 +14,85 @@ from Crypto.Cipher import AES from Crypto import Random -from redditglobals import * +from redditglobals import * import wx -from wx.lib.pubsub import pub +from wx.lib.pubsub import pub # cleanup dist and build directory first (for new py2exe version) -#if os.path.exists("dist/prog"): - # shutil.rmtree("dist/prog") +# if os.path.exists("dist/prog"): +# shutil.rmtree("dist/prog") -#if os.path.exists("dist/lib"): - # shutil.rmtree("dist/lib") +# if os.path.exists("dist/lib"): +# shutil.rmtree("dist/lib") -#if os.path.exists("build"): - # shutil.rmtree("build") +# if os.path.exists("build"): +# shutil.rmtree("build") wildcard = "All files (*.*)|*.*" - + + class RedditList(wx.Frame): - def __init__(self): - wx.Frame.__init__(self,None,wx.ID_ANY,"Files Stored", size = (300, 400)) + wx.Frame.__init__(self, None, wx.ID_ANY, "Files Stored", size=(300, 400)) self.panel = wx.Panel(self) pub.subscribe(self.subredditListener, "subredditListener") self.InitUI() def InitUI(self): + self.fileList = wx.ListCtrl(self.panel, size=(-1, 300), style=wx.LC_REPORT + | wx.EXPAND) + self.fileList.InsertColumn(0, "File Name", width=500) - self.fileList = wx.ListCtrl(self.panel, size=(-1,300), style = wx.LC_REPORT - |wx.EXPAND) - self.fileList.InsertColumn(0,"File Name", width = 500) - - self.gs = wx.GridSizer(1,2,0,0) + self.gs = wx.GridSizer(1, 2, 0, 0) - submitButton = wx.Button(self.panel, label = "Submit") + submitButton = wx.Button(self.panel, label="Submit") submitButton.Bind(wx.EVT_BUTTON, self.onSubmit) - closeButton = wx.Button(self.panel, label= "Close") + closeButton = wx.Button(self.panel, label="Close") closeButton.Bind(wx.EVT_BUTTON, self.onClose) saveButton = wx.Button(self.panel, label="Save Fields") - generateButton = wx.Button(self.panel, label = "Generate Subreddit") + generateButton = wx.Button(self.panel, label="Generate Subreddit") self.gs.Add(submitButton) self.gs.Add(closeButton) sizer = wx.BoxSizer(wx.VERTICAL) - flags = wx.ALL|wx.EXPAND - sizer.Add(self.fileList, 0, wx.ALL|wx.EXPAND, 5) - - sizer.Add(self.gs, flag=wx.ALIGN_BOTTOM|wx.EXPAND|wx.ALL, border=5) + flags = wx.ALL | wx.EXPAND + sizer.Add(self.fileList, 0, wx.ALL | wx.EXPAND, 5) + + sizer.Add(self.gs, flag=wx.ALIGN_BOTTOM | wx.EXPAND | wx.ALL, border=5) self.panel.SetSizer(sizer) + #used by onClickGetRedditList def subredditListener(self, subredditName, username, password): - r = praw.Reddit("reddit storage bot " + username) - r.login(username, password) + r = praw.Reddit("reddit storage bot") subreddit = r.get_subreddit(subredditName) global posts posts = subreddit.get_new(limit=1000) index = 0 - self.myRowDict = {} #dictionary used for retrival later + self.myRowDict = {} # dictionary used for retrival later for x in posts: - print (str(x)) + print(str(x)) self.fileList.InsertStringItem(index, x.title) self.myRowDict[index] = x.title - index+=1 - print ("done") - - + index += 1 + print("done") def onClose(self, event): self.Close() def onSubmit(self, event): - pub.sendMessage("fileListener", fileName = self.myRowDict[get_selected_items(self.fileList)[0]]) + pub.sendMessage("fileListener", fileName=self.myRowDict[get_selected_items(self.fileList)[0]]) self.Close() def get_selected_items(list_control): - - #Gets the selected items for the list control. - #Selection is returned as a list of selected indices, - #low to high. - + # Gets the selected items for the list control. + # Selection is returned as a list of selected indices, + # low to high. + selection = [] # start at -1 to get the first selected item @@ -109,18 +105,19 @@ def get_selected_items(list_control): selection.append(next) current = next + def GetNextSelected(list_control, current): - #Returns next selected item, or -1 when no more + # Returns next selected item, or -1 when no more return list_control.GetNextItem(current, - wx.LIST_NEXT_ALL, - wx.LIST_STATE_SELECTED) + wx.LIST_NEXT_ALL, + wx.LIST_STATE_SELECTED) class PostPanel(wx.Panel): - def __init__(self,parent): - wx.Panel.__init__(self,parent=parent,id=wx.ID_ANY) + def __init__(self, parent): + wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) self.InitUI() @@ -130,46 +127,44 @@ def InitUI(self): hbox = wx.BoxSizer(wx.VERTICAL) - fgs = wx.FlexGridSizer(5,2,9,15) - gs = wx.GridSizer(2,2,9,10) + fgs = wx.FlexGridSizer(5, 2, 9, 15) + gs = wx.GridSizer(2, 2, 9, 10) global username username = wx.StaticText(self, label="Username") password = wx.StaticText(self, label="Password") subreddit = wx.StaticText(self, label="Subreddit") - KEYPASS = wx.StaticText(self, label = "Encryption key") + KEYPASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") - post = wx.Button(self, ID_POST_BUTTON, "Post") - browseFile=wx.Button(self, ID_BROWSE_FILE_BUTTON, "Browse File") + post = wx.Button(self, ID_POST_BUTTON, "Post") + browseFile = wx.Button(self, ID_BROWSE_FILE_BUTTON, "Browse File") global postMessage - postMessage = wx.StaticText(self,label = "") + postMessage = wx.StaticText(self, label="") self.usernameField = wx.TextCtrl(self) - self.passwordField = wx.TextCtrl(self, style = wx.TE_PASSWORD) + self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) self.subredditField = wx.TextCtrl(self) - self.keypassField = wx.TextCtrl(self) + self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - - fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), - (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (KEYPASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), - (self.filepathField, 1, wx.EXPAND)]) + fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), + (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + (KEYPASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), + (self.filepathField, 1, wx.EXPAND)]) - gs.AddMany([(post,1,wx.EXPAND),(browseFile,1,wx.EXPAND), (postMessage)]) + gs.AddMany([(post, 1, wx.EXPAND), (browseFile, 1, wx.EXPAND), (postMessage)]) - fgs.AddGrowableCol(1, 1) - hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) - hbox.Add(gs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) - #hbox.Add(postMessage, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) + hbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + hbox.Add(gs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + # hbox.Add(postMessage, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) self.SetSizer(hbox) post.Bind(wx.EVT_BUTTON, self.onClickPostItem, post) browseFile.Bind(wx.EVT_BUTTON, self.onClickBrowseFile, browseFile) - - def onClickBrowseFile(self,e): + + def onClickBrowseFile(self, e): # Create the dialog. In this case the current directory is forced as the starting # directory for the dialog, and no default file name is forced. This can easilly # be changed in your program. This is an 'open' dialog, and allows multitple @@ -179,33 +174,32 @@ def onClickBrowseFile(self,e): # dialog is set up to change the current working directory to the path chosen. dlg = wx.FileDialog( self, message="Choose a file", - defaultDir=os.getcwd(), + defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, - style=wx.OPEN | wx.MULTIPLE | wx.CHANGE_DIR - ) + style=wx.FD_OPEN | wx.FD_MULTIPLE | wx.FD_CHANGE_DIR + ) # Show the dialog and retrieve the user response. If it is the OK response, # process the data. if dlg.ShowModal() == wx.ID_OK: - #Use GetPaths() for multiple files - paths = dlg.GetPath() - self.filepathField.SetValue(paths) + # Use GetPaths() for multiple files + paths = dlg.GetPaths() + self.filepathField.SetValue(paths[0]) - #self.log.WriteText('You selected %d files:' % len(paths)) + # self.log.WriteText('You selected %d files:' % len(paths)) - #for path in paths: + # for path in paths: # self.log.WriteText(' %s\n' % path) # Compare this with the debug above; did we change working dirs? - #self.log.WriteText("CWD: %s\n" % os.getcwd()) + # self.log.WriteText("CWD: %s\n" % os.getcwd()) # Destroy the dialog. Don't do this until you are done with it! # BAD things can happen otherwise! dlg.Destroy() - - def onClickPostItem(self,e): + def onClickPostItem(self, e): if (self.usernameField.IsEmpty()): postMessage.SetLabel("No Username Specified") elif (self.passwordField.IsEmpty()): @@ -218,12 +212,13 @@ def onClickPostItem(self,e): postMessage.SetLabel("No Filepath Specified") else: postItem(self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.keypassField.GetValue()) + self.filepathField.GetValue(), self.keypassField.GetValue()) + class GetPanel(wx.Panel): - def __init__(self,parent): - wx.Panel.__init__(self,parent=parent,id=wx.ID_ANY) + def __init__(self, parent): + wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) pub.subscribe(self.fileListener, "fileListener") self.InitUI() @@ -237,9 +232,8 @@ def InitUI(self): hbox = wx.BoxSizer(wx.VERTICAL) - fgs = wx.FlexGridSizer(6,2,9,15) - gs = wx.GridSizer(2,2,9,10) - + fgs = wx.FlexGridSizer(6, 2, 9, 15) + gs = wx.GridSizer(2, 2, 9, 10) global username username = wx.StaticText(self, label="Username") @@ -248,52 +242,48 @@ def InitUI(self): file_to_get = wx.StaticText(self, label="File to get") KEYPASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") - get = wx.Button(self, ID_GET_BUTTON, "Get") - saveFile = wx.Button(self,ID_SAVE_FILE_BUTTON, "Save File As") + get = wx.Button(self, ID_GET_BUTTON, "Get") + saveFile = wx.Button(self, ID_SAVE_FILE_BUTTON, "Save File As") getRedditList = wx.Button(self, ID_GET_REDDIT_LIST_BUTTON, "Retrieve List of Stored Files ") global postMessage1 - postMessage1 = wx.StaticText(self,label = "") + postMessage1 = wx.StaticText(self, label="") self.usernameField = wx.TextCtrl(self) - self.passwordField = wx.TextCtrl(self, style = wx.TE_PASSWORD) + self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) self.subredditField = wx.TextCtrl(self) self.fileToGetField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) + fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), + (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), + (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) - fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), - (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), - (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) - - gs.AddMany([(get,1,wx.EXPAND),(saveFile,1,wx.EXPAND), (getRedditList,1,wx.EXPAND), (postMessage1)]) + gs.AddMany([(get, 1, wx.EXPAND), (saveFile, 1, wx.EXPAND), (getRedditList, 1, wx.EXPAND), (postMessage1)]) - - fgs.AddGrowableCol(1, 1) - hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) - hbox.Add(gs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) - - + hbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + hbox.Add(gs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + self.SetSizer(hbox) get.Bind(wx.EVT_BUTTON, self.onClickGetItem) - saveFile.Bind(wx.EVT_BUTTON,self.onClickSaveItem) + saveFile.Bind(wx.EVT_BUTTON, self.onClickSaveItem) getRedditList.Bind(wx.EVT_BUTTON, self.onClickGetRedditList) - def onClickSaveItem(self,e): - #self.log.WriteText("CWD: %s\n" % os.getcwd()) + def onClickSaveItem(self, e): + # self.log.WriteText("CWD: %s\n" % os.getcwd()) # Create the dialog. In this case the current directory is forced as the starting # directory for the dialog, and no default file name is forced. This can easilly # be changed in your program. This is an 'save' dialog. dlg = wx.FileDialog( - self, message="Save file as ...", defaultDir=os.getcwd(), + self, message="Save file as ...", defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, style=wx.SAVE, - ) + ) dlg.SetFilename(self.fileToGetField.GetValue()) # This sets the default filter that the user will initially see. Otherwise, # the first filter in the list will be used by default. @@ -304,7 +294,7 @@ def onClickSaveItem(self,e): if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.filepathField.SetValue(path) - +#todo: what does this comment block imply? # Normally, at this point you would save your data using the file and path # data that the user provided to you, but since we didn't actually start # with any data to work with, that would be difficult. @@ -320,13 +310,12 @@ def onClickSaveItem(self,e): # Note that the current working dir didn't change. This is good since # that's the way we set it up. - #self.log.WriteText("CWD: %s\n" % os.getcwd()) + # self.log.WriteText("CWD: %s\n" % os.getcwd()) # Destroy the dialog. Don't do this until you are done with it! # BAD things can happen otherwise! dlg.Destroy() - def onClickGetItem(self, e): if (self.usernameField.IsEmpty()): postMessage1.SetLabel("No Username Specified") @@ -342,26 +331,27 @@ def onClickGetItem(self, e): postMessage1.SetLabel("No Filepath Specified") else: getItem(self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) + self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) + def onClickGetRedditList(self, e): - if(self.subredditField.IsEmpty() is False): - print (self.subredditField.GetValue()) + if self.subredditField.IsEmpty(): + postMessage1.SetLabel("No Subreddit Specified") + else: + print(self.subredditField.GetValue()) frame = RedditList() - pub.sendMessage("subredditListener", subredditName = self.subredditField.GetValue() - , username = self.usernameField.GetValue(), password = self.passwordField.GetValue()) + pub.sendMessage("subredditListener", subredditName=self.subredditField.GetValue()) frame.Show() - else: - postMessage1.SetLabel("No Subreddit Specified") + class MainNotebook(wx.Notebook): def __init__(self, parent): wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style= - wx.BK_DEFAULT - #wx.BK_TOP - #wx.BK_BOTTOM - #wx.BK_LEFT - #wx.BK_RIGHT + wx.BK_DEFAULT + # wx.BK_TOP + # wx.BK_BOTTOM + # wx.BK_LEFT + # wx.BK_RIGHT ) self.InitUI() @@ -372,7 +362,7 @@ def InitUI(self): tabTwo = GetPanel(self) self.AddPage(tabTwo, "Get") - + self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged) self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging) @@ -380,60 +370,62 @@ def OnPageChanged(self, event): old = event.GetOldSelection() new = event.GetSelection() sel = self.GetSelection() - #print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel) + # print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel) event.Skip() def OnPageChanging(self, event): old = event.GetOldSelection() new = event.GetSelection() sel = self.GetSelection() - #print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel) + # print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel) event.Skip() - + + class MainWindow(wx.Frame): - def __init__(self,parent,title): + def __init__(self, parent, title): super(MainWindow, self).__init__(parent, title=title, size=(500, 375)) - + panel = wx.Panel(self) notebook = MainNotebook(panel) sizer = wx.BoxSizer(wx.VERTICAL) - sizer.Add(notebook, 1, wx.ALL|wx.EXPAND, 5) + sizer.Add(notebook, 1, wx.ALL | wx.EXPAND, 5) panel.SetSizer(sizer) self.Layout() - self.Centre() + self.Centre() self.Show() - -#------------------------------------------------------------------------------ + + +# ------------------------------------------------------------------------------ def postItem(username, password, subreddit, filename, KEYPASS): filepath = filename k = filename.rfind("/") - filename = filename[k+1:] + filename = filename[k + 1:] - loginMod(username,password,subreddit) + # loginMod(username, password, subreddit) cipher = AESCipher(KEYPASS) comment = cipher.encrypt_file(filepath) post_encryption(filename, comment) postMessage.SetLabel("Done") postMessage1.SetLabel("Done") -def getItem(username, password, subreddit, filename, file_to_get, KEYPASS): +def getItem(username, password, subreddit, filename, file_to_get, KEYPASS): filepath = filename - #k = filename.rfind("/") - #filename = filename[k+1:] - #filepath = filepath[:k+1] - #temp_fp = filepath - #filepath = filepath + file_to_get - - loginMod(username,password,subreddit) - cipher=AESCipher(KEYPASS) + # k = filename.rfind("/") + # filename = filename[k+1:] + # filepath = filepath[:k+1] + # temp_fp = filepath + # filepath = filepath + file_to_get + + # loginMod(username, password, subreddit) + cipher = AESCipher(KEYPASS) comment = get_decryption(file_to_get) - + """ if filename[-1] == ")": j = filename.rfind("(") - 1 @@ -444,37 +436,36 @@ def getItem(username, password, subreddit, filename, file_to_get, KEYPASS): cipher.decrypt_file(comment, filepath) postMessage1.SetLabel("Done") postMessage.SetLabel("Done") - -def loginMod(username, password, subreddit): - trying = True - while trying: - try: - _login(username,password) - trying = False - except praw.errors.InvalidUserPass: - postMessage.SetLabel("Wrong Password") - postMessage1.SetLabel("Wrong Password") - while checkForMod(username, subreddit): - postMessage.SetLabel("Not a Moderator of the Subreddit") - postMessage1.SetLabel("Not a Moderator of the Subreddit") - -def _login(username, password): - r.login(username,password) + +# +# def loginMod(username, password, subreddit): +# trying = True +# while trying: +# try: +# _login(username, password) +# trying = False +# except praw.errors.InvalidUserPass: +# postMessage.SetLabel("Wrong Password") +# postMessage1.SetLabel("Wrong Password") +# while checkForMod(username, subreddit): +# postMessage.SetLabel("Not a Moderator of the Subreddit") +# postMessage1.SetLabel("Not a Moderator of the Subreddit") + + +# def _login(username, password): +# r.login(user=username,passwd=password) def checkForMod(user, subreddit): - subr = r.get_subreddit(subreddit) mods = subr.get_moderators() - for mod in mods: + for mod in mods: if mod == user.lower(): return True - return False - + return False if __name__ == '__main__': - app = wx.App() MainWindow(None, title='subreddit') app.MainLoop() diff --git a/reddit.py b/reddit.py index b22d0a5..f3093c6 100644 --- a/reddit.py +++ b/reddit.py @@ -4,27 +4,28 @@ def post_encryption(filename, encryption): - subreddit = r.get_subreddit(SUBREDDIT) + subreddit = r.subreddit(SUBREDDIT) does_not_exist = True file_submissions = r.search(filename, SUBREDDIT) # getting the submission of the file if it exists already count = 0 + filename_lower = filename.lower for submission in file_submissions: - if filename.lower() in submission.title.lower(): + if filename_lower in submission.title.lower(): # Looks for submissions with filename inside it count += 1 does_not_exist = False - # create submission if does not exist + # create submission if does_not_exist: - file_post = r.submit(SUBREDDIT, filename, " ") - else: - file_post = r.submit(SUBREDDIT, filename + " (" + str(count) + ")", " ") + file_post = subreddit.submit(filename) + else: # if file exists, then add a number to the end of the filename + file_post = subreddit.submit(filename + " (" + str(count) + ")") # going to be splitting the encryption since the comment limit is 10000 characters # this is the first-level comment - current_comment = file_post.add_comment(encryption[:10000]) + current_comment = file_post.reply(encryption[:10000]) encryption = encryption[10000:] #if it does not fit, then we will add a child comment to it and repeat @@ -42,18 +43,20 @@ def post_encryption(filename, encryption): def get_decryption(filename): decryption = '' - subreddit = r.get_subreddit(SUBREDDIT) - comments = subreddit.get_comments() - - file_submissions = r.search(filename, SUBREDDIT) + subreddit = r.subreddit(SUBREDDIT) + file_submissions = subreddit.search(filename) + subm = None # find the corresponding post for the file + filename_lower = filename.lower() for submission in file_submissions: - if submission.title.lower() == filename.lower(): + if submission.title.lower() == filename_lower: subm = submission break - + if subm is None: + # todo: return an error or something + return None # level the comments subm.replace_more_comments(limit=None, threshold=0) comments = praw.helpers.flatten_tree(subm.comments) From da47fdfcf37d9f921e8ff77006e96cd1a25c1bab Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 20:59:50 -0500 Subject: [PATCH 04/36] Fixed byte + str issues; Switched to use cryptodome inherent pad function instead of original --- crypt.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/crypt.py b/crypt.py index 59eba33..cfbd383 100644 --- a/crypt.py +++ b/crypt.py @@ -4,23 +4,20 @@ from Crypto.Cipher import AES from Crypto import Random +from Crypto.Util.Padding import pad, unpad class AESCipher(object): - def __init__(self, key): + def __init__(self, key: str): #self.bs = 32 - self.key = hashlib.sha256(key).digest() #turns the password into a 32char long key - - #need to make our string divisible by 16 for AES encryption + self.key = hashlib.sha256(key.encode('utf-8')).digest() #turns the password into a 32char long key + def pad(self, s): - n = AES.block_size - len(s) % AES.block_size - n = AES.block_size if n == 0 else n - return s + chr(n) * n + return pad(s,16) def remove_pad(self, m): - n = int(m[-1].encode('hex'), AES.block_size) - return m[: -1 * n] + return unpad(m,16) #encrypts plaintext and generates IV (initialization vector) def encrypt(self, plaintext): From e0213def85eeb06bf4e249e43c142ce7ec3c1ed7 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:00:22 -0500 Subject: [PATCH 05/36] Adjusted to use the subreddit obj instead --- reddit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit.py b/reddit.py index f3093c6..68ee6dd 100644 --- a/reddit.py +++ b/reddit.py @@ -6,7 +6,7 @@ def post_encryption(filename, encryption): subreddit = r.subreddit(SUBREDDIT) does_not_exist = True - file_submissions = r.search(filename, SUBREDDIT) + file_submissions = subreddit.search(filename, SUBREDDIT) # getting the submission of the file if it exists already count = 0 @@ -18,9 +18,9 @@ def post_encryption(filename, encryption): # create submission if does_not_exist: - file_post = subreddit.submit(filename) + file_post = subreddit.submit(filename, selftext='') else: # if file exists, then add a number to the end of the filename - file_post = subreddit.submit(filename + " (" + str(count) + ")") + file_post = subreddit.submit(filename + " (" + str(count) + ")",selftext='') # going to be splitting the encryption since the comment limit is 10000 characters # this is the first-level comment From ed55f4956adffbeab6022649c4fb48d58fee8eb9 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:01:08 -0500 Subject: [PATCH 06/36] Changed to wxPython's new window filter namings --- RedditStorage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RedditStorage.py b/RedditStorage.py index ad34c4e..0819546 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -282,7 +282,7 @@ def onClickSaveItem(self, e): # be changed in your program. This is an 'save' dialog. dlg = wx.FileDialog( self, message="Save file as ...", defaultDir=os.getcwd(), - defaultFile="", wildcard=wildcard, style=wx.SAVE, + defaultFile="", wildcard=wildcard, style=wx.FD_SAVE, ) dlg.SetFilename(self.fileToGetField.GetValue()) # This sets the default filter that the user will initially see. Otherwise, From 539d3b79f9fd0f4ed61b243071940f63e1a66d1a Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:01:38 -0500 Subject: [PATCH 07/36] Added extra info; may not need username, password fields anymore in window --- example_praw.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example_praw.ini b/example_praw.ini index 7807c82..b17cb36 100644 --- a/example_praw.ini +++ b/example_praw.ini @@ -1,4 +1,6 @@ [reddit storage bot] client_id = p-jcoLKBynTLew client_secret = gko_LXELoV07ZBNUXrvWZfzE3aI -user_agent = platform:app_id:versin_id (By u/reddit) \ No newline at end of file +user_agent = platform:app_id:versin_id (By u/reddit) +username = my-reddit-username +password = my-reddit-password \ No newline at end of file From 2b24b85cc6fc406e50f6dfccb1cf27eac30e44e1 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:05:46 -0500 Subject: [PATCH 08/36] Removed pathname; only filename is posted now --- reddit.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/reddit.py b/reddit.py index 68ee6dd..df9c86e 100644 --- a/reddit.py +++ b/reddit.py @@ -1,3 +1,5 @@ +import os.path + import praw from redditglobals import * @@ -5,12 +7,13 @@ def post_encryption(filename, encryption): subreddit = r.subreddit(SUBREDDIT) - does_not_exist = True + does_not_exist = True + filename = os.path.basename(filename) file_submissions = subreddit.search(filename, SUBREDDIT) # getting the submission of the file if it exists already count = 0 - filename_lower = filename.lower + filename_lower = filename.lower() for submission in file_submissions: if filename_lower in submission.title.lower(): # Looks for submissions with filename inside it count += 1 From 64509479d74a5c0dcd66f831b27ce29eea63e3a1 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:23:03 -0500 Subject: [PATCH 09/36] Getting files now works. Doesn't resolve filename conflicts though --- reddit.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/reddit.py b/reddit.py index df9c86e..4825802 100644 --- a/reddit.py +++ b/reddit.py @@ -49,23 +49,32 @@ def get_decryption(filename): subreddit = r.subreddit(SUBREDDIT) file_submissions = subreddit.search(filename) - subm = None + subm = [] + submissions_found = 0 # find the corresponding post for the file filename_lower = filename.lower() for submission in file_submissions: - - if submission.title.lower() == filename_lower: - subm = submission - break - if subm is None: - # todo: return an error or something - return None - # level the comments - subm.replace_more_comments(limit=None, threshold=0) - comments = praw.helpers.flatten_tree(subm.comments) - - for comment in comments: - decryption = decryption + comment.body - - return decryption + if filename_lower in submission.title.lower(): + subm.append(submission) + submissions_found += 1 + if not submissions_found: + # todo: get an actual exception type + raise Exception("Couldn't find file") + if submissions_found == 1: # Found only 1 file + # level the comments + subm[0].replace_more(limit=None, threshold=0) + comments = praw.helpers.flatten_tree(subm[0].comments) + + for comment in comments: + decryption = decryption + comment.body + + return decryption + else: # More than 1 similar file found; todo: Need to show a dialog window so they can select which version to dl (or all) + subm[0].comments.replace_more(limit=None, threshold=0) + comments = subm[0].comments.list() + + for comment in comments: + decryption = decryption + comment.body + + return decryption From 3beea76b9a70b7e39b856a13a1127d0b42b04289 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:30:50 -0500 Subject: [PATCH 10/36] Fixed bug from putting full pathname in Get window's fileToGetField field --- RedditStorage.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RedditStorage.py b/RedditStorage.py index 0819546..90face5 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -284,7 +284,11 @@ def onClickSaveItem(self, e): self, message="Save file as ...", defaultDir=os.getcwd(), defaultFile="", wildcard=wildcard, style=wx.FD_SAVE, ) - dlg.SetFilename(self.fileToGetField.GetValue()) + + # For legacy support; files used to be uploaded with the whole pathname + # (or did before I changed the upload names) + temp_fn = os.path.basename(self.fileToGetField.GetValue()) + dlg.SetFilename(temp_fn) # This sets the default filter that the user will initially see. Otherwise, # the first filter in the list will be used by default. dlg.SetFilterIndex(2) From 7622a48f9c9d57708fb1cb90e6167023a7bfe6a8 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 21:51:18 -0500 Subject: [PATCH 11/36] Added some PEP suppressions --- RedditStorage.py | 74 ++++++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 90face5..33fa8bd 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -8,7 +8,8 @@ from crypt import AESCipher import hashlib -import os +# import os +# from os.path import basename import base64 from Crypto.Cipher import AES @@ -18,6 +19,9 @@ import wx from wx.lib.pubsub import pub +import threading +from time import sleep + # cleanup dist and build directory first (for new py2exe version) # if os.path.exists("dist/prog"): # shutil.rmtree("dist/prog") @@ -31,32 +35,33 @@ wildcard = "All files (*.*)|*.*" +# noinspection PyAttributeOutsideInit class RedditList(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "Files Stored", size=(300, 400)) self.panel = wx.Panel(self) - pub.subscribe(self.subredditListener, "subredditListener") - self.InitUI() + pub.subscribe(self.subreddit_listener, "subredditListener") + self._init_UI() - def InitUI(self): + def _init_UI(self): self.fileList = wx.ListCtrl(self.panel, size=(-1, 300), style=wx.LC_REPORT | wx.EXPAND) self.fileList.InsertColumn(0, "File Name", width=500) self.gs = wx.GridSizer(1, 2, 0, 0) - submitButton = wx.Button(self.panel, label="Submit") - submitButton.Bind(wx.EVT_BUTTON, self.onSubmit) - closeButton = wx.Button(self.panel, label="Close") - closeButton.Bind(wx.EVT_BUTTON, self.onClose) + submit_button = wx.Button(self.panel, label="Submit") + submit_button.Bind(wx.EVT_BUTTON, self.onSubmit) + close_button = wx.Button(self.panel, label="Close") + close_button.Bind(wx.EVT_BUTTON, self.onClose) - saveButton = wx.Button(self.panel, label="Save Fields") + save_button = wx.Button(self.panel, label="Save Fields") - generateButton = wx.Button(self.panel, label="Generate Subreddit") + generate_button = wx.Button(self.panel, label="Generate Subreddit") - self.gs.Add(submitButton) - self.gs.Add(closeButton) + self.gs.Add(submit_button) + self.gs.Add(close_button) sizer = wx.BoxSizer(wx.VERTICAL) flags = wx.ALL | wx.EXPAND @@ -65,10 +70,10 @@ def InitUI(self): sizer.Add(self.gs, flag=wx.ALIGN_BOTTOM | wx.EXPAND | wx.ALL, border=5) self.panel.SetSizer(sizer) - #used by onClickGetRedditList - def subredditListener(self, subredditName, username, password): - r = praw.Reddit("reddit storage bot") - subreddit = r.get_subreddit(subredditName) + # Used by onClickGetRedditList + def subreddit_listener(self, subreddit_name, username, password): + reddit = praw.Reddit("reddit storage bot") + subreddit = reddit.subreddit(subreddit_name) global posts posts = subreddit.get_new(limit=1000) index = 0 @@ -114,6 +119,7 @@ def GetNextSelected(list_control, current): wx.LIST_STATE_SELECTED) +# noinspection PyPep8Naming,PyRedundantParentheses class PostPanel(wx.Panel): def __init__(self, parent): @@ -121,11 +127,12 @@ def __init__(self, parent): self.InitUI() + # noinspection PyAttributeOutsideInit def InitUI(self): ID_POST_BUTTON = wx.NewId() ID_BROWSE_FILE_BUTTON = wx.NewId() - hbox = wx.BoxSizer(wx.VERTICAL) + hit_box = wx.BoxSizer(wx.VERTICAL) fgs = wx.FlexGridSizer(5, 2, 9, 15) gs = wx.GridSizer(2, 2, 9, 10) @@ -134,7 +141,7 @@ def InitUI(self): username = wx.StaticText(self, label="Username") password = wx.StaticText(self, label="Password") subreddit = wx.StaticText(self, label="Subreddit") - KEYPASS = wx.StaticText(self, label="Encryption key") + KEY_PASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") post = wx.Button(self, ID_POST_BUTTON, "Post") browseFile = wx.Button(self, ID_BROWSE_FILE_BUTTON, "Browse File") @@ -149,25 +156,25 @@ def InitUI(self): fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (KEYPASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), + (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) gs.AddMany([(post, 1, wx.EXPAND), (browseFile, 1, wx.EXPAND), (postMessage)]) fgs.AddGrowableCol(1, 1) - hbox.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) - hbox.Add(gs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) - # hbox.Add(postMessage, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) - self.SetSizer(hbox) + hit_box.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + hit_box.Add(gs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) + # hit_box.Add(postMessage, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) + self.SetSizer(hit_box) post.Bind(wx.EVT_BUTTON, self.onClickPostItem, post) browseFile.Bind(wx.EVT_BUTTON, self.onClickBrowseFile, browseFile) def onClickBrowseFile(self, e): # Create the dialog. In this case the current directory is forced as the starting - # directory for the dialog, and no default file name is forced. This can easilly - # be changed in your program. This is an 'open' dialog, and allows multitple + # directory for the dialog, and no default file name is forced. This can easily + # be changed in your program. This is an 'open' dialog, and allows multiple # file selections as well. # # Finally, if the directory is changed in the process of getting files, this @@ -215,6 +222,7 @@ def onClickPostItem(self, e): self.filepathField.GetValue(), self.keypassField.GetValue()) +# noinspection PyPep8Naming,PyRedundantParentheses class GetPanel(wx.Panel): def __init__(self, parent): @@ -225,6 +233,7 @@ def __init__(self, parent): def fileListener(self, fileName): self.fileToGetField.SetValue(fileName) + # noinspection PyAttributeOutsideInit def InitUI(self): ID_GET_BUTTON = wx.NewId() ID_SAVE_FILE_BUTTON = wx.NewId() @@ -274,6 +283,7 @@ def InitUI(self): saveFile.Bind(wx.EVT_BUTTON, self.onClickSaveItem) getRedditList.Bind(wx.EVT_BUTTON, self.onClickGetRedditList) + # noinspection PyUnusedLocal def onClickSaveItem(self, e): # self.log.WriteText("CWD: %s\n" % os.getcwd()) @@ -298,7 +308,7 @@ def onClickSaveItem(self, e): if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.filepathField.SetValue(path) -#todo: what does this comment block imply? + # todo: what does this comment block imply? # Normally, at this point you would save your data using the file and path # data that the user provided to you, but since we didn't actually start # with any data to work with, that would be difficult. @@ -320,6 +330,7 @@ def onClickSaveItem(self, e): # BAD things can happen otherwise! dlg.Destroy() + # noinspection PyUnusedLocal def onClickGetItem(self, e): if (self.usernameField.IsEmpty()): postMessage1.SetLabel("No Username Specified") @@ -337,6 +348,7 @@ def onClickGetItem(self, e): getItem(self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) + # noinspection PyUnusedLocal def onClickGetRedditList(self, e): if self.subredditField.IsEmpty(): postMessage1.SetLabel("No Subreddit Specified") @@ -361,11 +373,11 @@ def __init__(self, parent): self.InitUI() def InitUI(self): - tabOne = PostPanel(self) - self.AddPage(tabOne, "Post") + tab_one = PostPanel(self) + self.AddPage(tab_one, "Post") - tabTwo = GetPanel(self) - self.AddPage(tabTwo, "Get") + tab_two = GetPanel(self) + self.AddPage(tab_two, "Get") self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.OnPageChanged) self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging) @@ -405,6 +417,7 @@ def __init__(self, parent, title): # ------------------------------------------------------------------------------ +# noinspection PyUnusedLocal def postItem(username, password, subreddit, filename, KEYPASS): filepath = filename k = filename.rfind("/") @@ -418,6 +431,7 @@ def postItem(username, password, subreddit, filename, KEYPASS): postMessage1.SetLabel("Done") +# noinspection PyUnusedLocal def getItem(username, password, subreddit, filename, file_to_get, KEYPASS): filepath = filename # k = filename.rfind("/") From c70ae7fe945cf854b2a6e92b321c5c97130f541a Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 22:15:16 -0500 Subject: [PATCH 12/36] Updated to stop using deprecated libraries and functions --- RedditStorage.py | 77 +++++++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 33fa8bd..b350c1a 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -17,9 +17,9 @@ from redditglobals import * import wx -from wx.lib.pubsub import pub +from pubsub import pub -import threading +from threading import Thread, Lock, Event from time import sleep # cleanup dist and build directory first (for new py2exe version) @@ -129,8 +129,8 @@ def __init__(self, parent): # noinspection PyAttributeOutsideInit def InitUI(self): - ID_POST_BUTTON = wx.NewId() - ID_BROWSE_FILE_BUTTON = wx.NewId() + ID_POST_BUTTON = wx.NewIdRef(count=1) + ID_BROWSE_FILE_BUTTON = wx.NewIdRef(count=1) hit_box = wx.BoxSizer(wx.VERTICAL) @@ -138,8 +138,8 @@ def InitUI(self): gs = wx.GridSizer(2, 2, 9, 10) global username - username = wx.StaticText(self, label="Username") - password = wx.StaticText(self, label="Password") + # username = wx.StaticText(self, label="Username") + # password = wx.StaticText(self, label="Password") subreddit = wx.StaticText(self, label="Subreddit") KEY_PASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") @@ -148,14 +148,14 @@ def InitUI(self): global postMessage postMessage = wx.StaticText(self, label="") - self.usernameField = wx.TextCtrl(self) - self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) + # self.usernameField = wx.TextCtrl(self) + # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) self.subredditField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), - (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), + (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) @@ -207,19 +207,19 @@ def onClickBrowseFile(self, e): dlg.Destroy() def onClickPostItem(self, e): - if (self.usernameField.IsEmpty()): - postMessage.SetLabel("No Username Specified") - elif (self.passwordField.IsEmpty()): - postMessage.SetLabel("No Password Entered") - elif (self.subredditField.IsEmpty()): + # if (self.usernameField.IsEmpty()): + # postMessage.SetLabel("No Username Specified") + # elif (self.passwordField.IsEmpty()): + # postMessage.SetLabel("No Password Entered") + if (self.subredditField.IsEmpty()): postMessage.SetLabel("No Subreddit Specified") elif (self.keypassField.IsEmpty()): postMessage.SetLabel("No Encryption Key Specified") elif (self.filepathField.IsEmpty()): postMessage.SetLabel("No Filepath Specified") else: - postItem(self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.keypassField.GetValue()) + postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), + self.subredditField.GetValue(), self.filepathField.GetValue(), self.keypassField.GetValue()) # noinspection PyPep8Naming,PyRedundantParentheses @@ -235,9 +235,9 @@ def fileListener(self, fileName): # noinspection PyAttributeOutsideInit def InitUI(self): - ID_GET_BUTTON = wx.NewId() - ID_SAVE_FILE_BUTTON = wx.NewId() - ID_GET_REDDIT_LIST_BUTTON = wx.NewId() + ID_GET_BUTTON = wx.NewIdRef(count=1) + ID_SAVE_FILE_BUTTON = wx.NewIdRef(count=1) + ID_GET_REDDIT_LIST_BUTTON = wx.NewIdRef(count=1) hbox = wx.BoxSizer(wx.VERTICAL) @@ -245,8 +245,8 @@ def InitUI(self): gs = wx.GridSizer(2, 2, 9, 10) global username - username = wx.StaticText(self, label="Username") - password = wx.StaticText(self, label="Password") + # username = wx.StaticText(self, label="Username") + # password = wx.StaticText(self, label="Password") subreddit = wx.StaticText(self, label="Subreddit") file_to_get = wx.StaticText(self, label="File to get") KEYPASS = wx.StaticText(self, label="Encryption key") @@ -258,15 +258,15 @@ def InitUI(self): global postMessage1 postMessage1 = wx.StaticText(self, label="") - self.usernameField = wx.TextCtrl(self) - self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) + # self.usernameField = wx.TextCtrl(self) + # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) self.subredditField = wx.TextCtrl(self) self.fileToGetField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([(username), (self.usernameField, 1, wx.EXPAND), (password), - (self.passwordField, 1, wx.EXPAND), (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), + (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) @@ -332,11 +332,11 @@ def onClickSaveItem(self, e): # noinspection PyUnusedLocal def onClickGetItem(self, e): - if (self.usernameField.IsEmpty()): - postMessage1.SetLabel("No Username Specified") - elif (self.passwordField.IsEmpty()): - postMessage1.SetLabel("No Password Entered") - elif (self.subredditField.IsEmpty()): + # if (self.usernameField.IsEmpty()): + # postMessage1.SetLabel("No Username Specified") + # elif (self.passwordField.IsEmpty()): + # postMessage1.SetLabel("No Password Entered") + if (self.subredditField.IsEmpty()): postMessage1.SetLabel("No Subreddit Specified") elif (self.fileToGetField.IsEmpty()): postMessage1.SetLabel("No File Specified") @@ -345,8 +345,9 @@ def onClickGetItem(self, e): elif (self.filepathField.IsEmpty()): postMessage1.SetLabel("No Filepath Specified") else: - getItem(self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) + getItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), + self.subredditField.GetValue(), self.filepathField.GetValue(), self.fileToGetField.GetValue(), + self.keypassField.GetValue()) # noinspection PyUnusedLocal def onClickGetRedditList(self, e): @@ -417,8 +418,9 @@ def __init__(self, parent, title): # ------------------------------------------------------------------------------ -# noinspection PyUnusedLocal -def postItem(username, password, subreddit, filename, KEYPASS): +# noinspection PyUnusedLocal,PyPep8Naming +def postItem( # username, password, + subreddit, filename, KEYPASS): filepath = filename k = filename.rfind("/") filename = filename[k + 1:] @@ -431,8 +433,9 @@ def postItem(username, password, subreddit, filename, KEYPASS): postMessage1.SetLabel("Done") -# noinspection PyUnusedLocal -def getItem(username, password, subreddit, filename, file_to_get, KEYPASS): +# noinspection PyUnusedLocal,PyPep8Naming +def getItem( # username, password, + subreddit, filename, file_to_get, KEYPASS): filepath = filename # k = filename.rfind("/") # filename = filename[k+1:] From 05338ca840b1af7ec1bbd9768d658d79f8315a6a Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 22:21:02 -0500 Subject: [PATCH 13/36] Removed unused globals --- redditglobals.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/redditglobals.py b/redditglobals.py index f9b7396..252056d 100644 --- a/redditglobals.py +++ b/redditglobals.py @@ -1,11 +1,9 @@ import praw -global USERAGENT,USERNAME,PASSWORD,SUBREDDIT,r +global USERAGENT, SUBREDDIT, REDDIT USERAGENT = "reddit storage bot" -USERNAME = "" -PASSWORD = "" -SUBREDDIT = "redditstoragetest" -#MAXPOSTS = 100 +SUBREDDIT = "subredditname" +# MAXPOSTS = 100 -r = praw.Reddit(USERAGENT) +REDDIT = praw.Reddit(USERAGENT) From dc5f6f3970492a4dfc9fee4bb2267edc3fa56793 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 22:21:43 -0500 Subject: [PATCH 14/36] Renamed vars to more descriptive names --- reddit.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/reddit.py b/reddit.py index 4825802..bd7d04a 100644 --- a/reddit.py +++ b/reddit.py @@ -6,7 +6,7 @@ def post_encryption(filename, encryption): - subreddit = r.subreddit(SUBREDDIT) + subreddit = REDDIT.subreddit(SUBREDDIT) does_not_exist = True filename = os.path.basename(filename) file_submissions = subreddit.search(filename, SUBREDDIT) @@ -46,7 +46,7 @@ def post_encryption(filename, encryption): def get_decryption(filename): decryption = '' - subreddit = r.subreddit(SUBREDDIT) + subreddit = REDDIT.subreddit(SUBREDDIT) file_submissions = subreddit.search(filename) subm = [] @@ -63,7 +63,7 @@ def get_decryption(filename): if submissions_found == 1: # Found only 1 file # level the comments subm[0].replace_more(limit=None, threshold=0) - comments = praw.helpers.flatten_tree(subm[0].comments) + comments = subm[0].comments.list() for comment in comments: decryption = decryption + comment.body From f9d4ec98116f5ea291bf599061e2eb9c5a2ba0ee Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 22:40:48 -0500 Subject: [PATCH 15/36] Removed unnecessary fields in window --- RedditStorage.py | 61 +++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index b350c1a..bb84948 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -140,7 +140,7 @@ def InitUI(self): global username # username = wx.StaticText(self, label="Username") # password = wx.StaticText(self, label="Password") - subreddit = wx.StaticText(self, label="Subreddit") + # subreddit = wx.StaticText(self, label="Subreddit") KEY_PASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") post = wx.Button(self, ID_POST_BUTTON, "Post") @@ -150,12 +150,12 @@ def InitUI(self): # self.usernameField = wx.TextCtrl(self) # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) - self.subredditField = wx.TextCtrl(self) + # self.subredditField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), - (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) @@ -211,15 +211,15 @@ def onClickPostItem(self, e): # postMessage.SetLabel("No Username Specified") # elif (self.passwordField.IsEmpty()): # postMessage.SetLabel("No Password Entered") - if (self.subredditField.IsEmpty()): - postMessage.SetLabel("No Subreddit Specified") - elif (self.keypassField.IsEmpty()): + # elif (self.subredditField.IsEmpty()): + # postMessage.SetLabel("No Subreddit Specified") + if (self.keypassField.IsEmpty()): postMessage.SetLabel("No Encryption Key Specified") elif (self.filepathField.IsEmpty()): postMessage.SetLabel("No Filepath Specified") else: - postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), - self.subredditField.GetValue(), self.filepathField.GetValue(), self.keypassField.GetValue()) + postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), + self.filepathField.GetValue(), self.keypassField.GetValue()) # noinspection PyPep8Naming,PyRedundantParentheses @@ -247,7 +247,7 @@ def InitUI(self): global username # username = wx.StaticText(self, label="Username") # password = wx.StaticText(self, label="Password") - subreddit = wx.StaticText(self, label="Subreddit") + # subreddit = wx.StaticText(self, label="Subreddit") file_to_get = wx.StaticText(self, label="File to get") KEYPASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") @@ -260,13 +260,13 @@ def InitUI(self): # self.usernameField = wx.TextCtrl(self) # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) - self.subredditField = wx.TextCtrl(self) + # self.subredditField = wx.TextCtrl(self) self.fileToGetField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), - (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + fgs.AddMany([ + # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) @@ -336,18 +336,17 @@ def onClickGetItem(self, e): # postMessage1.SetLabel("No Username Specified") # elif (self.passwordField.IsEmpty()): # postMessage1.SetLabel("No Password Entered") - if (self.subredditField.IsEmpty()): - postMessage1.SetLabel("No Subreddit Specified") - elif (self.fileToGetField.IsEmpty()): + # elif (self.subredditField.IsEmpty()): + # postMessage1.SetLabel("No Subreddit Specified") + if (self.fileToGetField.IsEmpty()): postMessage1.SetLabel("No File Specified") elif (self.keypassField.IsEmpty()): postMessage1.SetLabel("No Encryption Key Specified") elif (self.filepathField.IsEmpty()): postMessage1.SetLabel("No Filepath Specified") else: - getItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), - self.subredditField.GetValue(), self.filepathField.GetValue(), self.fileToGetField.GetValue(), - self.keypassField.GetValue()) + getItem( # self.subredditField.GetValue(), + self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) # noinspection PyUnusedLocal def onClickGetRedditList(self, e): @@ -419,8 +418,7 @@ def __init__(self, parent, title): # noinspection PyUnusedLocal,PyPep8Naming -def postItem( # username, password, - subreddit, filename, KEYPASS): +def postItem(filename: str, KEYPASS: str): filepath = filename k = filename.rfind("/") filename = filename[k + 1:] @@ -434,8 +432,7 @@ def postItem( # username, password, # noinspection PyUnusedLocal,PyPep8Naming -def getItem( # username, password, - subreddit, filename, file_to_get, KEYPASS): +def getItem(filename, file_to_get, KEYPASS): filepath = filename # k = filename.rfind("/") # filename = filename[k+1:] @@ -475,16 +472,16 @@ def getItem( # username, password, # def _login(username, password): # r.login(user=username,passwd=password) - -def checkForMod(user, subreddit): - subr = r.get_subreddit(subreddit) - mods = subr.get_moderators() - - for mod in mods: - if mod == user.lower(): - return True - return False - +# +# def checkForMod(user, subreddit): +# subr = reddit.get_subreddit(subreddit) +# mods = subr.get_moderators() +# +# for mod in mods: +# if mod == user.lower(): +# return True +# return False +# if __name__ == '__main__': app = wx.App() From 1ed5b9f908db604ca80036925992052ccbb4f9eb Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 23:09:10 -0500 Subject: [PATCH 16/36] Added praw.ini which stores passwords and secrets --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3e1d2a8..40bd83d 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build/ setup.py filebrowse.py +/praw.ini From 84c12a98ccffb4856b55956573faf9eb1b51e112 Mon Sep 17 00:00:00 2001 From: kenneth-w-chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Thu, 30 Mar 2023 23:09:31 -0500 Subject: [PATCH 17/36] Rewrote to be more descriptive --- README.md | 82 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 62 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 1d817b9..7dd53c5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -#RedditStorage -######Cloud storage that uses Reddit as a backend. +# RedditStorage +###### Cloud storage that uses Reddit as a backend. ============= @@ -7,26 +7,70 @@ RedditStorage is an application that allows you to store on reddit subreddits vi ============= -Requirements: -* reddit account (preferably with over 1 link karma on it) -* private subreddit with your reddit account as a moderator (make sure to set the spam filter strength of self posts and comments to "low") -* praw 2.1.21 -* Python 2.7 -* pycrypto 2.6.1 -* wxPython 3.0+ +## Requirements: +* A Reddit account (preferably with over 1 link karma on it) +* A private subreddit with your reddit account as a moderator (make sure to set the spam filter strength of self posts and comments to "low") +* praw 7.7.0 +* Python 3.5+ (and Pip3) +* pycryptodome 3.17 +* wxPython 3.0 +* Pypubsub 4.3.0 + +### Required Files +You'll need a few things first: +1. A config file named `praw.ini` to be used with `configparser`. See an example here of what the format should look like: [example_praw.ini](/example_praw.ini) +2. Fill out `redditglobals.py` with the subreddit name and useragent you're using in the `praw.ini` file. + 1. Replace `reddit storage bot` with whatever label you set in between the square brackets in your `praw.ini` + 2. Replace `subredditname` with the name of the subreddit you're posting in (don't try and post to r/SUBREDDITNAME, because that works) + +### Python installation: + +#### All Operating Systems: + +Download the latest version of Python from [here](https://www.python.org/downloads/). Pip is included by default. + +#### Linux only + +If you can't use a browser for whatever reason, run this instead: + +```shell +sudo apt-get install python3 python3-pip +``` + +### Package installation: + +```shell +pip install praw pycryptodome wxpython pypubsub +``` + ============= -How to Use: +## Usage: + +### Start-up: +```shell +python RedditStorage.py +``` + +### Posting files -1. RedditStorage uses an AES encryption algorithm which requires you to choose a password(e.g. "bunny"). -2. Run: `python RedditStorage.py` -3. Enter your username, password, subreddit and desired encryption key -4. Choose the file you want to upload -5. When getting the file, choose the file you want to get and how/where you want to save it +1. Enter the encryption key to be used to encrypt the files. Treat this like a normal password. *If you lose this, we can't help you decrypt it* +2. Choose the file you want to upload. +3. Press `Post`. +*The window may say "Not Responding" or freeze if you choose large files. This is normal and you need to wait it out.* -Screenshots +### Downloading files + +1. Enter the name of the file to get. *Some filenames will not show up in Reddit's search tool (e.g., ChromeSetup.exe). You should check that you can find it using Reddit's search feature first before running this.* +2. Enter the encryption key you used to encrypt the file when posting it. +3. Click `Save File As` and select a location and name to save the file as. Alternatively, enter the save location manually. +4. Press `Get`. + +*As before, the window may say "Not Responding" or you may get the beachball of death on MacOS. Again, just wait it out.* + +### Screenshots =========== @@ -37,11 +81,9 @@ Screenshots ![ss4](screenshot4.png "README.md uploaded") ![ss5](screenshot5.png "Big file made up of linked comments") - -To Do +## To Do ============== -* Save username/password between sessions * Upload as webapp -* Auto generate subreddits +* Auto generate subreddits \ No newline at end of file From 2c87532d1a6cd00a95e0256e3b6dcd4144cae1ad Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:31:25 -0500 Subject: [PATCH 18/36] Update gitignore to include project dependency directories Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 40bd83d..4765ea9 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ setup.py filebrowse.py /praw.ini +**/venv +**/.idea \ No newline at end of file From 5086cf5343b3e8ccd5591312d1c8f26ed1d3dc41 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:36:07 -0500 Subject: [PATCH 19/36] Put app initialization into its own function Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- RedditStorage.py | 53 ++++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index bb84948..88e7f6c 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -154,10 +154,10 @@ def InitUI(self): self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), - # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), - (self.filepathField, 1, wx.EXPAND)]) + fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), + # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), + (self.filepathField, 1, wx.EXPAND)]) gs.AddMany([(post, 1, wx.EXPAND), (browseFile, 1, wx.EXPAND), (postMessage)]) @@ -218,8 +218,8 @@ def onClickPostItem(self, e): elif (self.filepathField.IsEmpty()): postMessage.SetLabel("No Filepath Specified") else: - postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.keypassField.GetValue()) + postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), + self.filepathField.GetValue(), self.keypassField.GetValue()) # noinspection PyPep8Naming,PyRedundantParentheses @@ -266,9 +266,9 @@ def InitUI(self): self.filepathField = wx.TextCtrl(self) fgs.AddMany([ - # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), - (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) + # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), + (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), + (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) gs.AddMany([(get, 1, wx.EXPAND), (saveFile, 1, wx.EXPAND), (getRedditList, 1, wx.EXPAND), (postMessage1)]) @@ -309,18 +309,18 @@ def onClickSaveItem(self, e): path = dlg.GetPath() self.filepathField.SetValue(path) # todo: what does this comment block imply? - # Normally, at this point you would save your data using the file and path - # data that the user provided to you, but since we didn't actually start - # with any data to work with, that would be difficult. - # - # The code to do so would be similar to this, assuming 'data' contains - # the data you want to save: - # - # fp = file(path, 'w') # Create file anew - # fp.write(data) - # fp.close() - # - # You might want to add some error checking + # Normally, at this point you would save your data using the file and path + # data that the user provided to you, but since we didn't actually start + # with any data to work with, that would be difficult. + # + # The code to do so would be similar to this, assuming 'data' contains + # the data you want to save: + # + # fp = file(path, 'w') # Create file anew + # fp.write(data) + # fp.close() + # + # You might want to add some error checking # Note that the current working dir didn't change. This is good since # that's the way we set it up. @@ -345,8 +345,8 @@ def onClickGetItem(self, e): elif (self.filepathField.IsEmpty()): postMessage1.SetLabel("No Filepath Specified") else: - getItem( # self.subredditField.GetValue(), - self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) + getItem( # self.subredditField.GetValue(), + self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) # noinspection PyUnusedLocal def onClickGetRedditList(self, e): @@ -455,6 +455,7 @@ def getItem(filename, file_to_get, KEYPASS): postMessage1.SetLabel("Done") postMessage.SetLabel("Done") + # # def loginMod(username, password, subreddit): # trying = True @@ -483,7 +484,11 @@ def getItem(filename, file_to_get, KEYPASS): # return False # -if __name__ == '__main__': +def StartApp(): app = wx.App() MainWindow(None, title='subreddit') app.MainLoop() + + +if __name__ == '__main__': + StartApp() From c709e1659a7c365b66f3d362c03f273dc148168e Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 14:36:40 -0500 Subject: [PATCH 20/36] Create 'main.py'; this is the new script to run Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- README.md | 2 +- main.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 main.py diff --git a/README.md b/README.md index 7dd53c5..740fe08 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ pip install praw pycryptodome wxpython pypubsub ### Start-up: ```shell -python RedditStorage.py +python main.py ``` ### Posting files diff --git a/main.py b/main.py new file mode 100644 index 0000000..28d10ec --- /dev/null +++ b/main.py @@ -0,0 +1,3 @@ +from RedditStorage import StartApp +if __name__ == '__main__': + StartApp() From 6e306551b6fcbf98ab792e1da33e4cf89cc88530 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:26:39 -0500 Subject: [PATCH 21/36] Renamed some vars to be more descriptive Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- RedditStorage.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 88e7f6c..817e95d 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -425,15 +425,15 @@ def postItem(filename: str, KEYPASS: str): # loginMod(username, password, subreddit) cipher = AESCipher(KEYPASS) - comment = cipher.encrypt_file(filepath) - post_encryption(filename, comment) + encrypt_items = cipher.encrypt_file(filepath) + post_encryption(filename, encrypt_items) postMessage.SetLabel("Done") postMessage1.SetLabel("Done") # noinspection PyUnusedLocal,PyPep8Naming -def getItem(filename, file_to_get, KEYPASS): - filepath = filename +def getItem(save_location, file_to_get, ENCRYPT_KEY): + filepath = save_location # k = filename.rfind("/") # filename = filename[k+1:] # filepath = filepath[:k+1] @@ -441,7 +441,7 @@ def getItem(filename, file_to_get, KEYPASS): # filepath = filepath + file_to_get # loginMod(username, password, subreddit) - cipher = AESCipher(KEYPASS) + cipher = AESCipher(ENCRYPT_KEY) comment = get_decryption(file_to_get) """ From fea2aa9562c95442a07e66202c8c4cc46fdb2957 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:27:42 -0500 Subject: [PATCH 22/36] Password is hashed with argon2id; now using AES-GCM Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- crypt.py | 61 +++++++++++++++++++++++++------------------------------- 1 file changed, 27 insertions(+), 34 deletions(-) diff --git a/crypt.py b/crypt.py index cfbd383..4f39cae 100644 --- a/crypt.py +++ b/crypt.py @@ -1,54 +1,47 @@ -import hashlib -import os -import base64 +from argon2 import PasswordHasher + +from base64 import b64decode, b64encode from Crypto.Cipher import AES -from Crypto import Random +from Crypto import Random as crand from Crypto.Util.Padding import pad, unpad class AESCipher(object): + hasher = PasswordHasher() def __init__(self, key: str): - #self.bs = 32 - self.key = hashlib.sha256(key.encode('utf-8')).digest() #turns the password into a 32char long key + # self.bs = 32 + # self.key = hashlib.sha256(key.encode('utf-8')).digest() #turns the password into a 32char long key + self.argon2 = self.hasher.hash(key).split('$') - def pad(self, s): - return pad(s,16) + # argon2-cffi encodes the values in base64, so we decode it here to get our byte values + self.salt = b64decode(self.argon2[5] + '==') # Should be 16 bytes long by default + self.key = b64decode(self.argon2[5] + '=') # Should be 32 bytes long - def remove_pad(self, m): - return unpad(m,16) - - #encrypts plaintext and generates IV (initialization vector) + # encrypts plaintext and generates IV (initialization vector) def encrypt(self, plaintext): - plaintext = self.pad(plaintext) - iv = Random.new().read(AES.block_size) - cipher = AES.new(self.key, AES.MODE_CBC, iv) - return iv + cipher.encrypt(plaintext) - - #derypts ciphertexts - def decrypt(self, ciphertext): - iv = ciphertext[:AES.block_size] - cipher = AES.new(self.key, AES.MODE_CBC, iv) - plaintext = cipher.decrypt(ciphertext[AES.block_size:]) - return self.remove_pad(plaintext) - - #encrypts a file and returns a comment to be posted - def encrypt_file(self, file_path): + cipher = AES.new(self.key, AES.MODE_GCM) + return cipher.encrypt_and_digest(plaintext) + # decrypts ciphertexts + def decrypt(self, ciphertext, mac_tag): + cipher = AES.new(self.key, AES.MODE_GCM) + return cipher.decrypt_and_verify(ciphertext, mac_tag) + + # encrypts a file and returns a comment to be posted + def encrypt_file(self, file_path): with open(file_path, 'rb') as fo: plaintext = fo.read() enc = self.encrypt(plaintext) - comment = base64.b64encode(enc) - #comment = enc.decode('ISO-8859-1').encode('ascii') - return comment - - #takes in a comment to be posted and decrypts it into a file - def decrypt_file(self, comment, file_path): + # comment = enc.decode('ISO-8859-1').encode('ascii') + return [b64encode(enc[0]), b64encode(enc[1])] + + # takes in a comment to be posted and decrypts it into a file + def decrypt_file(self, comment, file_path): ciphertext = base64.b64decode(comment) - #ciphertext = comment.decode('ascii').encode('ISO-8859-1') + # ciphertext = comment.decode('ascii').encode('ISO-8859-1') dec = self.decrypt(ciphertext) with open(file_path, 'wb') as fo: fo.write(dec) - From d63f9361dc467e2d61cb88e29696bee6e463d354 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Fri, 7 Apr 2023 23:28:36 -0500 Subject: [PATCH 23/36] Update encrypt to include MAC in post content; adjust encrypt logic Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- reddit.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/reddit.py b/reddit.py index bd7d04a..c0e452d 100644 --- a/reddit.py +++ b/reddit.py @@ -5,7 +5,7 @@ from redditglobals import * -def post_encryption(filename, encryption): +def post_encryption(filename, encrypt_items): subreddit = REDDIT.subreddit(SUBREDDIT) does_not_exist = True filename = os.path.basename(filename) @@ -19,28 +19,25 @@ def post_encryption(filename, encryption): count += 1 does_not_exist = False - # create submission + # create submission. Post text will be the MAC if does_not_exist: - file_post = subreddit.submit(filename, selftext='') + file_post = subreddit.submit(filename, selftext=encrypt_items[1]) else: # if file exists, then add a number to the end of the filename - file_post = subreddit.submit(filename + " (" + str(count) + ")",selftext='') + file_post = subreddit.submit(filename + " (" + str(count) + ")", selftext=encrypt_items[1]) # going to be splitting the encryption since the comment limit is 10000 characters # this is the first-level comment - current_comment = file_post.reply(encryption[:10000]) - encryption = encryption[10000:] + current_comment = file_post.reply(encrypt_items[0][:10000]) + cur_index = 10000 + ciphertext_len = len(encrypt_items[0]) - #if it does not fit, then we will add a child comment to it and repeat - if len(encryption) != 0: - - while len(encryption) > 10000: - #to-do - current_comment = current_comment.reply(encryption[:10000]) - encryption = encryption[10000:] - - if len(encryption) > 0: - current_comment.reply(encryption) + # Tries to reply with max chars for comments (10,000) until there isn't enough in the buffer + while ciphertext_len - cur_index >= 10000: # Keep replying with max chars until we can't + current_comment = current_comment.reply(encrypt_items[0][cur_index:cur_index+10000]) + cur_index += 10000 + if ciphertext_len > cur_index: + current_comment.reply(encrypt_items[0][cur_index:]) def get_decryption(filename): From 1d363a352a6320d1bfe4fc80a5ff414bb999770c Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:00:39 -0500 Subject: [PATCH 24/36] Decrypt set up; Added DocString; Refactoring to be more descriptive Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- crypt.py | 44 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/crypt.py b/crypt.py index 4f39cae..b33808f 100644 --- a/crypt.py +++ b/crypt.py @@ -5,14 +5,20 @@ from Crypto.Cipher import AES from Crypto import Random as crand from Crypto.Util.Padding import pad, unpad +from typing import Tuple, List, Union class AESCipher(object): + + # Mainly for internal use. So we don't have to remake this object every encryption/decryption hasher = PasswordHasher() def __init__(self, key: str): - # self.bs = 32 - # self.key = hashlib.sha256(key.encode('utf-8')).digest() #turns the password into a 32char long key + """ + Constructor for an AESCipher object + :param key: The password to use as a key + """ + # argon2 outputs a single string with all parameters delimited by a '$' self.argon2 = self.hasher.hash(key).split('$') # argon2-cffi encodes the values in base64, so we decode it here to get our byte values @@ -20,17 +26,33 @@ def __init__(self, key: str): self.key = b64decode(self.argon2[5] + '=') # Should be 32 bytes long # encrypts plaintext and generates IV (initialization vector) - def encrypt(self, plaintext): + def encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes]: + """ + Returns the AES-GCM-encrypted ciphertext and MAC + :param plaintext: The plaintext to encrypt + :return: A Tuple containing [ciphertext, MAC] + """ cipher = AES.new(self.key, AES.MODE_GCM) return cipher.encrypt_and_digest(plaintext) # decrypts ciphertexts - def decrypt(self, ciphertext, mac_tag): + def decrypt(self, ciphertext: bytes, mac_tag: bytes) -> bytes: + """ + Returns the decrypted ciphertext + :param ciphertext: The ciphertext to decrypt + :param mac_tag: The MAC for the ciphertext + :return: The decrypted information + """ cipher = AES.new(self.key, AES.MODE_GCM) return cipher.decrypt_and_verify(ciphertext, mac_tag) # encrypts a file and returns a comment to be posted - def encrypt_file(self, file_path): + def encrypt_file(self, file_path: str) -> List[bytes]: + """ + Encrypts a file and returns the ciphertext and associated MAC + :param file_path: The path to the file to encrypt + :return: A list containing [ciphertext, MAC] + """ with open(file_path, 'rb') as fo: plaintext = fo.read() enc = self.encrypt(plaintext) @@ -39,9 +61,15 @@ def encrypt_file(self, file_path): # takes in a comment to be posted and decrypts it into a file - def decrypt_file(self, comment, file_path): - ciphertext = base64.b64decode(comment) + def decrypt_to_file(self, encrypt_items: List[bytes], file_path: str): + """ + Decrypts a file encrypted in AES-GCM and outputs the result to the given filepath + :parameter encrypt_items: A Tuple containing [ciphertext, MAC] + :parameter file_path: The file path to output the decrypted file to + """ + ciphertext = b64decode(encrypt_items[0]) + mac = b64decode(encrypt_items[1]) # ciphertext = comment.decode('ascii').encode('ISO-8859-1') - dec = self.decrypt(ciphertext) + dec = self.decrypt(ciphertext, mac) with open(file_path, 'wb') as fo: fo.write(dec) From a5e1635cb4281addc340c58182172cc79970fdf8 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:12:24 -0500 Subject: [PATCH 25/36] Subreddit is now set in the config file instead of redditglobals.py Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- README.md | 3 +-- example_praw.ini | 3 ++- redditglobals.py | 7 ++++--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 740fe08..a961eb1 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,8 @@ RedditStorage is an application that allows you to store on reddit subreddits vi ### Required Files You'll need a few things first: 1. A config file named `praw.ini` to be used with `configparser`. See an example here of what the format should look like: [example_praw.ini](/example_praw.ini) -2. Fill out `redditglobals.py` with the subreddit name and useragent you're using in the `praw.ini` file. +2. Fill out `redditglobals.py` with the label you're using in the `praw.ini` file. 1. Replace `reddit storage bot` with whatever label you set in between the square brackets in your `praw.ini` - 2. Replace `subredditname` with the name of the subreddit you're posting in (don't try and post to r/SUBREDDITNAME, because that works) ### Python installation: diff --git a/example_praw.ini b/example_praw.ini index b17cb36..33ec48e 100644 --- a/example_praw.ini +++ b/example_praw.ini @@ -3,4 +3,5 @@ client_id = p-jcoLKBynTLew client_secret = gko_LXELoV07ZBNUXrvWZfzE3aI user_agent = platform:app_id:versin_id (By u/reddit) username = my-reddit-username -password = my-reddit-password \ No newline at end of file +password = my-reddit-password +subreddit = my-subreddit \ No newline at end of file diff --git a/redditglobals.py b/redditglobals.py index 252056d..f3be7f4 100644 --- a/redditglobals.py +++ b/redditglobals.py @@ -1,9 +1,10 @@ import praw - +from configparser import ConfigParser global USERAGENT, SUBREDDIT, REDDIT - +config = ConfigParser() +config.read('praw.ini') USERAGENT = "reddit storage bot" -SUBREDDIT = "subredditname" +SUBREDDIT = config['reddit storage bot']['subreddit'] # MAXPOSTS = 100 REDDIT = praw.Reddit(USERAGENT) From d07533f870a0701ef80eac6f2568478f80e869d3 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:13:43 -0500 Subject: [PATCH 26/36] Adjusted to match new decryption stuff Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- RedditStorage.py | 4 ++-- reddit.py | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 817e95d..9a2ac91 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -442,7 +442,7 @@ def getItem(save_location, file_to_get, ENCRYPT_KEY): # loginMod(username, password, subreddit) cipher = AESCipher(ENCRYPT_KEY) - comment = get_decryption(file_to_get) + comment = get_ciphertext(file_to_get) """ if filename[-1] == ")": @@ -451,7 +451,7 @@ def getItem(save_location, file_to_get, ENCRYPT_KEY): filepath = filepath[:-n] """ - cipher.decrypt_file(comment, filepath) + cipher.decrypt_to_file(comment, filepath) postMessage1.SetLabel("Done") postMessage.SetLabel("Done") diff --git a/reddit.py b/reddit.py index c0e452d..53b8141 100644 --- a/reddit.py +++ b/reddit.py @@ -40,8 +40,8 @@ def post_encryption(filename, encrypt_items): current_comment.reply(encrypt_items[0][cur_index:]) -def get_decryption(filename): - decryption = '' +def get_ciphertext(filename): + ciphertext = '' subreddit = REDDIT.subreddit(SUBREDDIT) @@ -61,17 +61,18 @@ def get_decryption(filename): # level the comments subm[0].replace_more(limit=None, threshold=0) comments = subm[0].comments.list() - + mac: str = subm[0].selftext for comment in comments: - decryption = decryption + comment.body + ciphertext: str = ciphertext + comment.body - return decryption + return [ciphertext, mac] else: # More than 1 similar file found; todo: Need to show a dialog window so they can select which version to dl (or all) subm[0].comments.replace_more(limit=None, threshold=0) + mac: str = subm[0].selftext comments = subm[0].comments.list() for comment in comments: - decryption = decryption + comment.body + ciphertext: str = ciphertext + comment.body - return decryption + return [ciphertext, mac] From 67b9765a37c5dfc75d5a41aee94e1c97ebc43754 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Chenneth@users.noreply.github.com> Date: Sat, 8 Apr 2023 21:20:37 -0500 Subject: [PATCH 27/36] Changed typehints to Tuple Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- crypt.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crypt.py b/crypt.py index b33808f..d44e72e 100644 --- a/crypt.py +++ b/crypt.py @@ -47,7 +47,7 @@ def decrypt(self, ciphertext: bytes, mac_tag: bytes) -> bytes: return cipher.decrypt_and_verify(ciphertext, mac_tag) # encrypts a file and returns a comment to be posted - def encrypt_file(self, file_path: str) -> List[bytes]: + def encrypt_file(self, file_path: str) -> Tuple[bytes, bytes]: """ Encrypts a file and returns the ciphertext and associated MAC :param file_path: The path to the file to encrypt @@ -57,11 +57,11 @@ def encrypt_file(self, file_path: str) -> List[bytes]: plaintext = fo.read() enc = self.encrypt(plaintext) # comment = enc.decode('ISO-8859-1').encode('ascii') - return [b64encode(enc[0]), b64encode(enc[1])] + return b64encode(enc[0]), b64encode(enc[1]) # takes in a comment to be posted and decrypts it into a file - def decrypt_to_file(self, encrypt_items: List[bytes], file_path: str): + def decrypt_to_file(self, encrypt_items: Tuple[bytes, bytes], file_path: str): """ Decrypts a file encrypted in AES-GCM and outputs the result to the given filepath :parameter encrypt_items: A Tuple containing [ciphertext, MAC] From e43b44b139944949343d1b12434060734eeefbb5 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:41:04 -0500 Subject: [PATCH 28/36] Encryption and decryption now works; parameters are in post text --- RedditStorage.py | 55 +++++------------- crypt.py | 142 +++++++++++++++++++++++++++++++++++------------ reddit.py | 81 +++++++++++++++++---------- 3 files changed, 175 insertions(+), 103 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 9a2ac91..24b366b 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -1,6 +1,7 @@ #!/usr/bin/python # simple.py +import gc import praw from reddit import * @@ -422,12 +423,18 @@ def postItem(filename: str, KEYPASS: str): filepath = filename k = filename.rfind("/") filename = filename[k + 1:] - + del k + gc.collect() # loginMod(username, password, subreddit) cipher = AESCipher(KEYPASS) encrypt_items = cipher.encrypt_file(filepath) - post_encryption(filename, encrypt_items) - postMessage.SetLabel("Done") + b64ciphertext = b64encode(encrypt_items[0]) + b64mac = b64encode(encrypt_items[1]) + b64nonce = b64encode(encrypt_items[2]) + del encrypt_items + gc.collect() + post_encryption(filename, b64ciphertext, b64mac, cipher.salt, b64nonce, cipher.argon2params) + postMessage.SetLabel("Done") # Todo: these labels need to disappear after some time postMessage1.SetLabel("Done") @@ -442,48 +449,14 @@ def getItem(save_location, file_to_get, ENCRYPT_KEY): # loginMod(username, password, subreddit) cipher = AESCipher(ENCRYPT_KEY) - comment = get_ciphertext(file_to_get) - - """ - if filename[-1] == ")": - j = filename.rfind("(") - 1 - n = len(filename) - j - filepath = filepath[:-n] - """ - - cipher.decrypt_to_file(comment, filepath) + encrypt_items = get_ciphertext(file_to_get) + cipher.decrypt_to_file(encrypt_items, filepath) + del encrypt_items + gc.collect() postMessage1.SetLabel("Done") postMessage.SetLabel("Done") -# -# def loginMod(username, password, subreddit): -# trying = True -# while trying: -# try: -# _login(username, password) -# trying = False -# except praw.errors.InvalidUserPass: -# postMessage.SetLabel("Wrong Password") -# postMessage1.SetLabel("Wrong Password") -# while checkForMod(username, subreddit): -# postMessage.SetLabel("Not a Moderator of the Subreddit") -# postMessage1.SetLabel("Not a Moderator of the Subreddit") - - -# def _login(username, password): -# r.login(user=username,passwd=password) -# -# def checkForMod(user, subreddit): -# subr = reddit.get_subreddit(subreddit) -# mods = subr.get_moderators() -# -# for mod in mods: -# if mod == user.lower(): -# return True -# return False -# - def StartApp(): app = wx.App() MainWindow(None, title='subreddit') diff --git a/crypt.py b/crypt.py index d44e72e..a26747e 100644 --- a/crypt.py +++ b/crypt.py @@ -1,15 +1,13 @@ -from argon2 import PasswordHasher - +import argon2.low_level +from argon2 import PasswordHasher, Parameters, Type +from argon2.low_level import hash_secret from base64 import b64decode, b64encode from Crypto.Cipher import AES -from Crypto import Random as crand -from Crypto.Util.Padding import pad, unpad from typing import Tuple, List, Union class AESCipher(object): - # Mainly for internal use. So we don't have to remake this object every encryption/decryption hasher = PasswordHasher() @@ -19,57 +17,133 @@ def __init__(self, key: str): :param key: The password to use as a key """ # argon2 outputs a single string with all parameters delimited by a '$' - self.argon2 = self.hasher.hash(key).split('$') - + self.argon2 = self.hasher.hash(key) + self.argon2params, self.salt, self.hash = self.extract_parameters(self.argon2) # argon2-cffi encodes the values in base64, so we decode it here to get our byte values - self.salt = b64decode(self.argon2[5] + '==') # Should be 16 bytes long by default - self.key = b64decode(self.argon2[5] + '=') # Should be 32 bytes long + # And we need to add padding '=' because reasons b64 needs that number of chars + self.key: bytes = b64decode(self.hash + '=') # Should be 32 bytes long + self.secret = key + + # encrypts a file and returns a comment to be posted + def encrypt_file(self, file_path: str) -> Tuple[bytes, bytes, bytes]: + """ + Encrypts a file and returns the ciphertext and associated MAC + :param file_path: The path to the file to encrypt + :return: A list containing [ciphertext, MAC] + """ + with open(file_path, 'rb') as fo: + plaintext = fo.read() + enc = self._encrypt(plaintext) + # comment = enc.decode('ISO-8859-1').encode('ascii') + print('\nEncryption info:\nMAC: ', enc[1],'\nSalt: ',self.salt,'\nKey: ',self.hash,'\nSecret: ',self.secret) + return enc[0], enc[1], enc[2] + + # takes in a comment to be posted and decrypts it into a file + + _STR_TYPE_TO_TYPE = {"Type.ID": Type.ID, "Type.I": Type.I, "Type.D": Type.D} + + def decrypt_to_file(self, encrypt_items: Tuple[bytes, List[str]], file_path: str): + """ + Decrypts a file encrypted in AES-GCM and outputs the result to the given filepath + :parameter encrypt_items: A Tuple containing [ciphertext, argon2 parameters] + :parameter file_path: The file path to output the decrypted file to + """ + ciphertext = encrypt_items[0] + mac = encrypt_items[1][0] + salt = encrypt_items[1][1] + nonce = encrypt_items[1][9] + # ciphertext = comment.decode('ascii').encode('ISO-8859-1') + # Format is MAC$salt$time cost$memory cost$parallelism$hash length$salt length$argon2 type$argon2 version + dec = self._decrypt(ciphertext, b64decode(mac), b64decode(salt), b64decode(nonce), + Parameters(time_cost=int(encrypt_items[1][2]), + memory_cost=int(encrypt_items[1][3]), + parallelism=int(encrypt_items[1][4]), + hash_len=int(encrypt_items[1][5]), + salt_len=int(encrypt_items[1][6]), + type=self._STR_TYPE_TO_TYPE[encrypt_items[1][7]], + version=int(encrypt_items[1][8]) + ) + ) + with open(file_path, 'wb') as fo: + fo.write(dec) # encrypts plaintext and generates IV (initialization vector) - def encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes]: + def _encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes, bytes]: """ Returns the AES-GCM-encrypted ciphertext and MAC :param plaintext: The plaintext to encrypt :return: A Tuple containing [ciphertext, MAC] """ cipher = AES.new(self.key, AES.MODE_GCM) - return cipher.encrypt_and_digest(plaintext) + ciphertext_mac = cipher.encrypt_and_digest(plaintext) + return ciphertext_mac[0], ciphertext_mac[1], cipher.nonce # decrypts ciphertexts - def decrypt(self, ciphertext: bytes, mac_tag: bytes) -> bytes: + def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce:bytes, argon2_params: Parameters) -> bytes: """ Returns the decrypted ciphertext :param ciphertext: The ciphertext to decrypt :param mac_tag: The MAC for the ciphertext + :param salt: The salt used for the key + :param nonce: The nonce used by the AES algorithm :return: The decrypted information """ - cipher = AES.new(self.key, AES.MODE_GCM) + cipher = AES.new( + b64decode(argon2.low_level.hash_secret(self.secret.encode('utf-8'), salt, + argon2_params.time_cost, argon2_params.memory_cost, + argon2_params.parallelism, argon2_params.hash_len, + argon2_params.type, argon2_params.version + ).decode('utf-8').split('$')[5] + '=' + ), + AES.MODE_GCM, nonce=nonce) return cipher.decrypt_and_verify(ciphertext, mac_tag) - # encrypts a file and returns a comment to be posted - def encrypt_file(self, file_path: str) -> Tuple[bytes, bytes]: + _NAME_TO_TYPE = {"argon2id": Type.ID, "argon2i": Type.I, "argon2d": Type.D} + + @classmethod + def extract_parameters(cls, argon2item: str) -> List[Union[Parameters, str]]: """ - Encrypts a file and returns the ciphertext and associated MAC - :param file_path: The path to the file to encrypt - :return: A list containing [ciphertext, MAC] + Extracts argon2 parameters and returns the salt and hash + :param argon2item: The argon2 item returned from using argon2 hashing (i.e., argon2.PassswordHasher) + :return: A list containing [argon2 parameters, salt, hash] """ - with open(file_path, 'rb') as fo: - plaintext = fo.read() - enc = self.encrypt(plaintext) - # comment = enc.decode('ISO-8859-1').encode('ascii') - return b64encode(enc[0]), b64encode(enc[1]) + parts = argon2item.split("$") - # takes in a comment to be posted and decrypts it into a file + # Backwards compatibility for Argon v1.2 hashes + if len(parts) == 5: + parts.insert(2, "v=18") + + argon2_type = cls._NAME_TO_TYPE[parts[1]] + + kvs = { + k: int(v) + for k, v in ( + s.split("=") for s in [parts[2]] + parts[3].split(",") + ) + } - def decrypt_to_file(self, encrypt_items: Tuple[bytes, bytes], file_path: str): + return [Parameters( + type=argon2_type, + salt_len=cls._decoded_str_len(len(parts[4])), + hash_len=cls._decoded_str_len(len(parts[5])), + version=kvs["v"], + time_cost=kvs["t"], + memory_cost=kvs["m"], + parallelism=kvs["p"], + ), parts[4], parts[5]] + + @classmethod + def _decoded_str_len(cls, l: int) -> int: """ - Decrypts a file encrypted in AES-GCM and outputs the result to the given filepath - :parameter encrypt_items: A Tuple containing [ciphertext, MAC] - :parameter file_path: The file path to output the decrypted file to + Compute how long an encoded string of length *l* becomes. """ - ciphertext = b64decode(encrypt_items[0]) - mac = b64decode(encrypt_items[1]) - # ciphertext = comment.decode('ascii').encode('ISO-8859-1') - dec = self.decrypt(ciphertext, mac) - with open(file_path, 'wb') as fo: - fo.write(dec) + rem = l % 4 + + if rem == 3: + last_group_len = 2 + elif rem == 2: + last_group_len = 1 + else: + last_group_len = 0 + + return l // 4 * 3 + last_group_len diff --git a/reddit.py b/reddit.py index 53b8141..09fa419 100644 --- a/reddit.py +++ b/reddit.py @@ -1,55 +1,80 @@ +import gc import os.path - +from base64 import b64encode, b64decode import praw +from typing import Tuple, List from redditglobals import * - - -def post_encryption(filename, encrypt_items): +from argon2 import Parameters # For typehints + + +# Tuple containing [ciphertext, MAC, salt, time_cost, memory_cost, parallelism, hash_len, argon2type, argon2version] +def post_encryption(post_title, ciphertext: bytes, MAC: bytes, salt: str, nonce: bytes, argon2_params: Parameters): + """ + Posts encrypted ciphertext to a subreddit defined in redditglobals.py + :param post_title: The title of the post to make + :param ciphertext: Encrypted text to post, encoded in base64 + :param MAC: Associated MAC for the ciphertext, encoded in base64 + :param salt: Salt used to hash the password, encoded in base64 utf-8 + :param nonce: Nonce used by the AES algorithm + :param argon2_params: Argon2 parameters used to hash the password + """ subreddit = REDDIT.subreddit(SUBREDDIT) does_not_exist = True - filename = os.path.basename(filename) - file_submissions = subreddit.search(filename, SUBREDDIT) + post_title = os.path.basename(post_title) + file_submissions = subreddit.search(post_title, SUBREDDIT) # getting the submission of the file if it exists already count = 0 - filename_lower = filename.lower() + filename_lower = post_title.lower() for submission in file_submissions: if filename_lower in submission.title.lower(): # Looks for submissions with filename inside it count += 1 does_not_exist = False - # create submission. Post text will be the MAC + # create submission. Post text will be params necessary to recreate the hash in argon2 + # Format is MAC$salt$time cost$memory cost$parallelism$hash length$salt length$argon2 type$argon2 version$nonce + # Ex: examplemac$examplesalt$20$45$4$32$16$Type.ID$19 + post_text = f'{MAC.decode("utf-8")}${salt}==${argon2_params.time_cost}${argon2_params.memory_cost}$' \ + f'{argon2_params.parallelism}${argon2_params.hash_len}${argon2_params.salt_len}${argon2_params.type}${argon2_params.version}${nonce.decode("utf-8")}' if does_not_exist: - file_post = subreddit.submit(filename, selftext=encrypt_items[1]) - else: # if file exists, then add a number to the end of the filename - file_post = subreddit.submit(filename + " (" + str(count) + ")", selftext=encrypt_items[1]) - + file_post = subreddit.submit(post_title, selftext=post_text) + else: # if file exists, then add a number to the end of the filename + file_post = subreddit.submit(post_title + " (" + str(count) + ")", selftext=post_text) + del post_text + gc.collect() # going to be splitting the encryption since the comment limit is 10000 characters # this is the first-level comment - current_comment = file_post.reply(encrypt_items[0][:10000]) + current_comment = file_post.reply(ciphertext[:10000]) cur_index = 10000 - ciphertext_len = len(encrypt_items[0]) + ciphertext_len = len(ciphertext) # Tries to reply with max chars for comments (10,000) until there isn't enough in the buffer while ciphertext_len - cur_index >= 10000: # Keep replying with max chars until we can't - current_comment = current_comment.reply(encrypt_items[0][cur_index:cur_index+10000]) + current_comment = current_comment.reply(ciphertext[cur_index:cur_index + 10000]) cur_index += 10000 if ciphertext_len > cur_index: - current_comment.reply(encrypt_items[0][cur_index:]) + current_comment.reply(ciphertext[cur_index:]) + del ciphertext + gc.collect() -def get_ciphertext(filename): +def get_ciphertext(post_title) -> Tuple[bytes, List[str]]: + """ + Returns the ciphertext and MAC from given post title + :param post_title: Name of the post to find + :return: A tuple containing [ciphertext, argon2 parameters]. Only ciphertext is decoded from base64 + """ ciphertext = '' - + subreddit = REDDIT.subreddit(SUBREDDIT) - file_submissions = subreddit.search(filename) + file_submissions = subreddit.search(post_title) subm = [] submissions_found = 0 # find the corresponding post for the file - filename_lower = filename.lower() + filename_lower = post_title.lower() for submission in file_submissions: if filename_lower in submission.title.lower(): subm.append(submission) @@ -59,20 +84,20 @@ def get_ciphertext(filename): raise Exception("Couldn't find file") if submissions_found == 1: # Found only 1 file # level the comments - subm[0].replace_more(limit=None, threshold=0) + subm[0].comments.replace_more(limit=None, threshold=0) comments = subm[0].comments.list() - mac: str = subm[0].selftext + params: List[str] = subm[0].selftext.split('$') + for comment in comments: ciphertext: str = ciphertext + comment.body - return [ciphertext, mac] - else: # More than 1 similar file found; todo: Need to show a dialog window so they can select which version to dl (or all) + return b64decode(ciphertext), params + else: # More than 1 similar file found; + # todo: Need to show a dialog window so they can select which version to dl (or all) subm[0].comments.replace_more(limit=None, threshold=0) - mac: str = subm[0].selftext comments = subm[0].comments.list() - + params: List[str] = subm[0].selftext.split('$') for comment in comments: ciphertext: str = ciphertext + comment.body - return [ciphertext, mac] - + return b64decode(ciphertext), params From 3426a0289ceda2feb5846dcade36e3c03a9d2739 Mon Sep 17 00:00:00 2001 From: Kenneth Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Sun, 9 Apr 2023 16:45:00 -0500 Subject: [PATCH 29/36] Adjusted note about Reddit search Changed the bit about some filenames not appearing to how Reddit's search tool doesn't update indexes for a bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a961eb1..5badb02 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ python main.py ### Downloading files -1. Enter the name of the file to get. *Some filenames will not show up in Reddit's search tool (e.g., ChromeSetup.exe). You should check that you can find it using Reddit's search feature first before running this.* +1. Enter the name of the file to get. *It takes some time for Reddit's search index to update (about every 20 minutes). You should check that you can find it using Reddit's search feature first before running this.* 2. Enter the encryption key you used to encrypt the file when posting it. 3. Click `Save File As` and select a location and name to save the file as. Alternatively, enter the save location manually. 4. Press `Get`. From 22fe8efc5a0bfe702e0189044be672d57a97cd50 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Mon, 10 Apr 2023 17:06:57 -0500 Subject: [PATCH 30/36] Reformatting and improve DocString --- crypt.py | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/crypt.py b/crypt.py index a26747e..8ef5b66 100644 --- a/crypt.py +++ b/crypt.py @@ -1,10 +1,9 @@ -import argon2.low_level -from argon2 import PasswordHasher, Parameters, Type -from argon2.low_level import hash_secret -from base64 import b64decode, b64encode +from base64 import b64decode +from typing import Tuple, List, Union +import argon2.low_level from Crypto.Cipher import AES -from typing import Tuple, List, Union +from argon2 import PasswordHasher, Parameters, Type class AESCipher(object): @@ -35,7 +34,8 @@ def encrypt_file(self, file_path: str) -> Tuple[bytes, bytes, bytes]: plaintext = fo.read() enc = self._encrypt(plaintext) # comment = enc.decode('ISO-8859-1').encode('ascii') - print('\nEncryption info:\nMAC: ', enc[1],'\nSalt: ',self.salt,'\nKey: ',self.hash,'\nSecret: ',self.secret) + print('\nEncryption info:\nMAC: ', enc[1], '\nSalt: ', self.salt, '\nKey: ', self.hash, '\nSecret: ', + self.secret) return enc[0], enc[1], enc[2] # takes in a comment to be posted and decrypts it into a file @@ -79,7 +79,8 @@ def _encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes, bytes]: return ciphertext_mac[0], ciphertext_mac[1], cipher.nonce # decrypts ciphertexts - def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce:bytes, argon2_params: Parameters) -> bytes: + def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce: bytes, + argon2_params: Parameters) -> bytes: """ Returns the decrypted ciphertext :param ciphertext: The ciphertext to decrypt @@ -133,11 +134,13 @@ def extract_parameters(cls, argon2item: str) -> List[Union[Parameters, str]]: ), parts[4], parts[5]] @classmethod - def _decoded_str_len(cls, l: int) -> int: + def _decoded_str_len(cls, str_len: int) -> int: """ Compute how long an encoded string of length *l* becomes. + :param str_len Length of encoded string + :return Length of decoded string """ - rem = l % 4 + rem = str_len % 4 if rem == 3: last_group_len = 2 @@ -146,4 +149,4 @@ def _decoded_str_len(cls, l: int) -> int: else: last_group_len = 0 - return l // 4 * 3 + last_group_len + return str_len // 4 * 3 + last_group_len From a1b22ea6cd526d493be7b9e0b347d5fb221f728a Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 13:25:47 -0500 Subject: [PATCH 31/36] Now encrypts as it reads file instead of loading entire file first; some documentation Signed-off-by: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> --- crypt.py | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/crypt.py b/crypt.py index 8ef5b66..3b353f0 100644 --- a/crypt.py +++ b/crypt.py @@ -17,26 +17,37 @@ def __init__(self, key: str): """ # argon2 outputs a single string with all parameters delimited by a '$' self.argon2 = self.hasher.hash(key) + """The argon2 parameters, salt, and hash, as output by PasswordHasher""" + self.argon2params, self.salt, self.hash = self.extract_parameters(self.argon2) + # argon2-cffi encodes the values in base64, so we decode it here to get our byte values # And we need to add padding '=' because reasons b64 needs that number of chars self.key: bytes = b64decode(self.hash + '=') # Should be 32 bytes long + """The key for encrypting, in raw byte form; 32 bytes long""" self.secret = key + """The password hashed to generate the key""" # encrypts a file and returns a comment to be posted def encrypt_file(self, file_path: str) -> Tuple[bytes, bytes, bytes]: """ Encrypts a file and returns the ciphertext and associated MAC :param file_path: The path to the file to encrypt - :return: A list containing [ciphertext, MAC] + :return: A list containing [ciphertext, MAC, nonce] """ + cipher = AES.new(self.key, AES.MODE_GCM) + ciphertext = b'' with open(file_path, 'rb') as fo: - plaintext = fo.read() - enc = self._encrypt(plaintext) + while True: + plaintext = fo.read(20000) + if not plaintext: + break + ciphertext += cipher.encrypt(plaintext) + mac = cipher.digest() # comment = enc.decode('ISO-8859-1').encode('ascii') - print('\nEncryption info:\nMAC: ', enc[1], '\nSalt: ', self.salt, '\nKey: ', self.hash, '\nSecret: ', + print('\nEncryption info:\nMAC: ', mac, '\nSalt: ', self.salt, '\nKey: ', self.hash, '\nSecret: ', self.secret) - return enc[0], enc[1], enc[2] + return ciphertext, mac, cipher.nonce # takes in a comment to be posted and decrypts it into a file @@ -67,16 +78,16 @@ def decrypt_to_file(self, encrypt_items: Tuple[bytes, List[str]], file_path: str with open(file_path, 'wb') as fo: fo.write(dec) - # encrypts plaintext and generates IV (initialization vector) - def _encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes, bytes]: - """ - Returns the AES-GCM-encrypted ciphertext and MAC - :param plaintext: The plaintext to encrypt - :return: A Tuple containing [ciphertext, MAC] - """ - cipher = AES.new(self.key, AES.MODE_GCM) - ciphertext_mac = cipher.encrypt_and_digest(plaintext) - return ciphertext_mac[0], ciphertext_mac[1], cipher.nonce + # # encrypts plaintext and generates IV (initialization vector) + # def _encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes, bytes]: + # """ + # Returns the AES-GCM-encrypted ciphertext and MAC + # :param plaintext: The plaintext to encrypt + # :return: A Tuple containing [ciphertext, MAC] + # """ + # cipher = AES.new(self.key, AES.MODE_GCM) + # ciphertext_mac = cipher.encrypt_and_digest(plaintext) + # return ciphertext_mac[0], ciphertext_mac[1], cipher.nonce # decrypts ciphertexts def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce: bytes, From 923f4b725b888a2b8b555a992d059dc180119610 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 13:26:55 -0500 Subject: [PATCH 32/36] Added sleep to prevent rate limiting/banning --- reddit.py | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/reddit.py b/reddit.py index 09fa419..886d896 100644 --- a/reddit.py +++ b/reddit.py @@ -3,9 +3,10 @@ from base64 import b64encode, b64decode import praw from typing import Tuple, List - +from time import sleep +from secrets import SystemRandom from redditglobals import * -from argon2 import Parameters # For typehints +from argon2 import Parameters # For typehints # Tuple containing [ciphertext, MAC, salt, time_cost, memory_cost, parallelism, hash_len, argon2type, argon2version] @@ -46,13 +47,23 @@ def post_encryption(post_title, ciphertext: bytes, MAC: bytes, salt: str, nonce: # going to be splitting the encryption since the comment limit is 10000 characters # this is the first-level comment + print(f'\nNumber of comments to post: {len(ciphertext) / 10000 + 1}\n') + rand_num = SystemRandom() current_comment = file_post.reply(ciphertext[:10000]) cur_index = 10000 ciphertext_len = len(ciphertext) - + num_comments = 1 + next_sleep = rand_num.randrange(10, 22) + print(f'\nPosted {num_comments} comment\n') # Tries to reply with max chars for comments (10,000) until there isn't enough in the buffer while ciphertext_len - cur_index >= 10000: # Keep replying with max chars until we can't current_comment = current_comment.reply(ciphertext[cur_index:cur_index + 10000]) + num_comments += 1 + print(f'Posted {num_comments} comment\n') + if num_comments == next_sleep: + print('Sleeping') + next_sleep += rand_num.randrange(1, 10) # Stop commenting after 1-10 comments + sleep(float(rand_num.randrange(200, 450)) / 10.0) # 20.0-45.0 second sleep cur_index += 10000 if ciphertext_len > cur_index: current_comment.reply(ciphertext[cur_index:]) From c39920534456dee3bddef162574534f99d873f76 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 14:47:36 -0500 Subject: [PATCH 33/36] Some documentation --- RedditStorage.py | 270 +++++++++++++++++++++-------------------------- 1 file changed, 123 insertions(+), 147 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index 24b366b..a8c061f 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -1,20 +1,10 @@ #!/usr/bin/python -# simple.py -import gc +from typing import Union -import praw from reddit import * -import getpass from crypt import AESCipher -import hashlib -# import os -# from os.path import basename -import base64 - -from Crypto.Cipher import AES -from Crypto import Random from redditglobals import * import wx @@ -23,21 +13,14 @@ from threading import Thread, Lock, Event from time import sleep -# cleanup dist and build directory first (for new py2exe version) -# if os.path.exists("dist/prog"): -# shutil.rmtree("dist/prog") - -# if os.path.exists("dist/lib"): -# shutil.rmtree("dist/lib") - -# if os.path.exists("build"): -# shutil.rmtree("build") - wildcard = "All files (*.*)|*.*" # noinspection PyAttributeOutsideInit class RedditList(wx.Frame): + """ + A window showing the list of files posted to Reddit. Listens to the subredditListener pub. + """ def __init__(self): wx.Frame.__init__(self, None, wx.ID_ANY, "Files Stored", size=(300, 400)) @@ -46,6 +29,9 @@ def __init__(self): self._init_UI() def _init_UI(self): + """ + Initializes the UI of the window + """ self.fileList = wx.ListCtrl(self.panel, size=(-1, 300), style=wx.LC_REPORT | wx.EXPAND) self.fileList.InsertColumn(0, "File Name", width=500) @@ -68,68 +54,90 @@ def _init_UI(self): flags = wx.ALL | wx.EXPAND sizer.Add(self.fileList, 0, wx.ALL | wx.EXPAND, 5) - sizer.Add(self.gs, flag=wx.ALIGN_BOTTOM | wx.EXPAND | wx.ALL, border=5) + sizer.Add(self.gs, flag=wx.EXPAND | wx.ALL, border=5) self.panel.SetSizer(sizer) # Used by onClickGetRedditList - def subreddit_listener(self, subreddit_name, username, password): - reddit = praw.Reddit("reddit storage bot") - subreddit = reddit.subreddit(subreddit_name) + def subreddit_listener(self, subreddit_name: str): + """ + Subscribed to subredditListener event. Fetches posts on the subreddit + :param subreddit_name: + """ + print("Fetching posts...\n") + sub = REDDIT.subreddit(subreddit_name) global posts - posts = subreddit.get_new(limit=1000) - index = 0 + posts = sub.new(limit=1000) self.myRowDict = {} # dictionary used for retrival later for x in posts: print(str(x)) - self.fileList.InsertStringItem(index, x.title) + index = self.fileList.InsertItem(x.title) self.myRowDict[index] = x.title - index += 1 - print("done") + print("Done fetching list of posts\n") + # Closes window def onClose(self, event): self.Close() + # When user presses submit button in the new window def onSubmit(self, event): pub.sendMessage("fileListener", fileName=self.myRowDict[get_selected_items(self.fileList)[0]]) self.Close() -def get_selected_items(list_control): - # Gets the selected items for the list control. - # Selection is returned as a list of selected indices, - # low to high. +def get_selected_items(list_control: wx.ListCtrl): + """ + Gets the selected items for the list control. + Selection is returned as a list of selected indices, + low to high. + :param list_control: The list control to retrieve all selected items from + """ selection = [] - # start at -1 to get the first selected item + # start at index -1 to get the first selected item current = -1 while True: - next = GetNextSelected(list_control, current) - if next == -1: + selectedItem = GetNextSelected(list_control, current) + if selectedItem == -1: return selection - selection.append(next) - current = next + selection.append(selectedItem) + current = selectedItem -def GetNextSelected(list_control, current): - # Returns next selected item, or -1 when no more +def GetNextSelected(list_control: wx.ListCtrl, current_index: int): + """ + Retrieves the next selected item in the list control + :param list_control: The list control to retrieve an item from + :param current_index: Current index to retrieve on. -1 retrieves the first item + :return: The first selected item after current_index, or -1 if no item is found + """ - return list_control.GetNextItem(current, + return list_control.GetNextItem(current_index, wx.LIST_NEXT_ALL, wx.LIST_STATE_SELECTED) # noinspection PyPep8Naming,PyRedundantParentheses class PostPanel(wx.Panel): - - def __init__(self, parent): + """ + This is the panel where files can be posted. + The window switches to this panel when pressing the "Post" tab. + """ + + def __init__(self, parent: Union[wx.Window, None] = None): + """ + Initializes the 'Post' panel + :param parent: The parent process of the panel; optional + """ wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) - self.InitUI() # noinspection PyAttributeOutsideInit def InitUI(self): + """ + Initializes the panel elements + """ ID_POST_BUTTON = wx.NewIdRef(count=1) ID_BROWSE_FILE_BUTTON = wx.NewIdRef(count=1) @@ -138,27 +146,20 @@ def InitUI(self): fgs = wx.FlexGridSizer(5, 2, 9, 15) gs = wx.GridSizer(2, 2, 9, 10) - global username - # username = wx.StaticText(self, label="Username") - # password = wx.StaticText(self, label="Password") - # subreddit = wx.StaticText(self, label="Subreddit") KEY_PASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") post = wx.Button(self, ID_POST_BUTTON, "Post") browseFile = wx.Button(self, ID_BROWSE_FILE_BUTTON, "Browse File") + global postMessage + """Refers to the message to say posting is done.""" postMessage = wx.StaticText(self, label="") - # self.usernameField = wx.TextCtrl(self) - # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) - # self.subredditField = wx.TextCtrl(self) - self.keypassField = wx.TextCtrl(self) + self.passwordField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([ # (username), (self.usernameField, 1, wx.EXPAND), (password),(self.passwordField, 1, wx.EXPAND), - # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (KEY_PASS, 1, wx.EXPAND), (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), - (self.filepathField, 1, wx.EXPAND)]) + fgs.AddMany([(KEY_PASS, 1, wx.EXPAND), (self.passwordField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), + (self.filepathField, 1, wx.EXPAND)]) gs.AddMany([(post, 1, wx.EXPAND), (browseFile, 1, wx.EXPAND), (postMessage)]) @@ -166,20 +167,20 @@ def InitUI(self): hit_box.Add(fgs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) hit_box.Add(gs, proportion=1, flag=wx.ALL | wx.EXPAND, border=15) - # hit_box.Add(postMessage, proportion=1, flag=wx.ALL|wx.EXPAND, border=15) self.SetSizer(hit_box) post.Bind(wx.EVT_BUTTON, self.onClickPostItem, post) browseFile.Bind(wx.EVT_BUTTON, self.onClickBrowseFile, browseFile) def onClickBrowseFile(self, e): - # Create the dialog. In this case the current directory is forced as the starting - # directory for the dialog, and no default file name is forced. This can easily - # be changed in your program. This is an 'open' dialog, and allows multiple - # file selections as well. - # - # Finally, if the directory is changed in the process of getting files, this - # dialog is set up to change the current working directory to the path chosen. + """Create the dialog. In this case the current directory is forced as the starting + directory for the dialog, and no default file name is forced. This can easily + be changed in your program. This is an 'open' dialog, and allows multiple + file selections as well. + + Finally, if the directory is changed in the process of getting files, this + dialog is set up to change the current working directory to the path chosen. + """ dlg = wx.FileDialog( self, message="Choose a file", defaultDir=os.getcwd(), @@ -192,50 +193,45 @@ def onClickBrowseFile(self, e): # process the data. if dlg.ShowModal() == wx.ID_OK: # Use GetPaths() for multiple files - paths = dlg.GetPaths() + paths = dlg.GetPaths() # Todo: allow user to select and post multiple files self.filepathField.SetValue(paths[0]) - # self.log.WriteText('You selected %d files:' % len(paths)) - - # for path in paths: - # self.log.WriteText(' %s\n' % path) - - # Compare this with the debug above; did we change working dirs? - # self.log.WriteText("CWD: %s\n" % os.getcwd()) - - # Destroy the dialog. Don't do this until you are done with it! - # BAD things can happen otherwise! dlg.Destroy() def onClickPostItem(self, e): - # if (self.usernameField.IsEmpty()): - # postMessage.SetLabel("No Username Specified") - # elif (self.passwordField.IsEmpty()): - # postMessage.SetLabel("No Password Entered") - # elif (self.subredditField.IsEmpty()): - # postMessage.SetLabel("No Subreddit Specified") - if (self.keypassField.IsEmpty()): + """Executes when user clicks the 'Post' button""" + if (self.passwordField.IsEmpty()): postMessage.SetLabel("No Encryption Key Specified") elif (self.filepathField.IsEmpty()): postMessage.SetLabel("No Filepath Specified") else: postItem( # self.usernameField.GetValue(), self.passwordField.GetValue(), self.subredditField.GetValue(), - self.filepathField.GetValue(), self.keypassField.GetValue()) + self.filepathField.GetValue(), self.passwordField.GetValue()) # noinspection PyPep8Naming,PyRedundantParentheses class GetPanel(wx.Panel): - - def __init__(self, parent): + """ + This is the panel where files can be retrieved. + The window switches to this panel when pressing the "Get" tab. + """ + + def __init__(self, parent: Union[wx.Window, None] = None): + """ + Initializes the 'Get' panel + :param parent: The parent process of the panel + """ wx.Panel.__init__(self, parent=parent, id=wx.ID_ANY) pub.subscribe(self.fileListener, "fileListener") self.InitUI() def fileListener(self, fileName): + """Subscribed to 'fileListener' event""" self.fileToGetField.SetValue(fileName) # noinspection PyAttributeOutsideInit def InitUI(self): + """Initializes the UI for the panel""" ID_GET_BUTTON = wx.NewIdRef(count=1) ID_SAVE_FILE_BUTTON = wx.NewIdRef(count=1) ID_GET_REDDIT_LIST_BUTTON = wx.NewIdRef(count=1) @@ -245,10 +241,6 @@ def InitUI(self): fgs = wx.FlexGridSizer(6, 2, 9, 15) gs = wx.GridSizer(2, 2, 9, 10) - global username - # username = wx.StaticText(self, label="Username") - # password = wx.StaticText(self, label="Password") - # subreddit = wx.StaticText(self, label="Subreddit") file_to_get = wx.StaticText(self, label="File to get") KEYPASS = wx.StaticText(self, label="Encryption key") filename = wx.StaticText(self, label="Filepath") @@ -259,17 +251,12 @@ def InitUI(self): global postMessage1 postMessage1 = wx.StaticText(self, label="") - # self.usernameField = wx.TextCtrl(self) - # self.passwordField = wx.TextCtrl(self, style=wx.TE_PASSWORD) - # self.subredditField = wx.TextCtrl(self) self.fileToGetField = wx.TextCtrl(self) self.keypassField = wx.TextCtrl(self) self.filepathField = wx.TextCtrl(self) - fgs.AddMany([ - # (subreddit, 1, wx.EXPAND), (self.subredditField, 1, wx.EXPAND), - (file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), - (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) + fgs.AddMany([(file_to_get, 1, wx.EXPAND), (self.fileToGetField, 1, wx.EXPAND), (KEYPASS, 1, wx.EXPAND), + (self.keypassField, 1, wx.EXPAND), (filename, 1, wx.EXPAND), (self.filepathField, 1, wx.EXPAND)]) gs.AddMany([(get, 1, wx.EXPAND), (saveFile, 1, wx.EXPAND), (getRedditList, 1, wx.EXPAND), (postMessage1)]) @@ -286,8 +273,9 @@ def InitUI(self): # noinspection PyUnusedLocal def onClickSaveItem(self, e): - # self.log.WriteText("CWD: %s\n" % os.getcwd()) - + """Opens a file dialog window so users can choose where to save the file. + Called when user clicks the 'Save File As' button + """ # Create the dialog. In this case the current directory is forced as the starting # directory for the dialog, and no default file name is forced. This can easilly # be changed in your program. This is an 'save' dialog. @@ -309,36 +297,10 @@ def onClickSaveItem(self, e): if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() self.filepathField.SetValue(path) - # todo: what does this comment block imply? - # Normally, at this point you would save your data using the file and path - # data that the user provided to you, but since we didn't actually start - # with any data to work with, that would be difficult. - # - # The code to do so would be similar to this, assuming 'data' contains - # the data you want to save: - # - # fp = file(path, 'w') # Create file anew - # fp.write(data) - # fp.close() - # - # You might want to add some error checking - - # Note that the current working dir didn't change. This is good since - # that's the way we set it up. - # self.log.WriteText("CWD: %s\n" % os.getcwd()) - - # Destroy the dialog. Don't do this until you are done with it! - # BAD things can happen otherwise! dlg.Destroy() # noinspection PyUnusedLocal def onClickGetItem(self, e): - # if (self.usernameField.IsEmpty()): - # postMessage1.SetLabel("No Username Specified") - # elif (self.passwordField.IsEmpty()): - # postMessage1.SetLabel("No Password Entered") - # elif (self.subredditField.IsEmpty()): - # postMessage1.SetLabel("No Subreddit Specified") if (self.fileToGetField.IsEmpty()): postMessage1.SetLabel("No File Specified") elif (self.keypassField.IsEmpty()): @@ -349,15 +311,16 @@ def onClickGetItem(self, e): getItem( # self.subredditField.GetValue(), self.filepathField.GetValue(), self.fileToGetField.GetValue(), self.keypassField.GetValue()) - # noinspection PyUnusedLocal - def onClickGetRedditList(self, e): - if self.subredditField.IsEmpty(): - postMessage1.SetLabel("No Subreddit Specified") - else: - print(self.subredditField.GetValue()) - frame = RedditList() - pub.sendMessage("subredditListener", subredditName=self.subredditField.GetValue()) - frame.Show() + @staticmethod + def onClickGetRedditList(e): + """ + Function used when user clicks the 'Retrieve List of Stored Files' button + :param e: + :return: + """ + frame = RedditList() + pub.sendMessage("subredditListener", subreddit_name=SUBREDDIT) + frame.Show() class MainNotebook(wx.Notebook): @@ -400,7 +363,12 @@ def OnPageChanging(self, event): class MainWindow(wx.Frame): - def __init__(self, parent, title): + def __init__(self, parent, title: str): + """ + Creates a wxPython window + :param parent: The parent process for this window + :param title: The title of the window. Process will show up in things such as Task Manager or 'ps' with this name + """ super(MainWindow, self).__init__(parent, title=title, size=(500, 375)) panel = wx.Panel(self) @@ -419,14 +387,19 @@ def __init__(self, parent, title): # noinspection PyUnusedLocal,PyPep8Naming -def postItem(filename: str, KEYPASS: str): +def postItem(filename: str, PASSKEY: str): + """ + Encrypts and then posts a file to Reddit + :param filename: The title of the post to be created + :param PASSKEY: The password used to generate the encryption key + """ filepath = filename k = filename.rfind("/") filename = filename[k + 1:] del k gc.collect() # loginMod(username, password, subreddit) - cipher = AESCipher(KEYPASS) + cipher = AESCipher(PASSKEY) encrypt_items = cipher.encrypt_file(filepath) b64ciphertext = b64encode(encrypt_items[0]) b64mac = b64encode(encrypt_items[1]) @@ -434,21 +407,20 @@ def postItem(filename: str, KEYPASS: str): del encrypt_items gc.collect() post_encryption(filename, b64ciphertext, b64mac, cipher.salt, b64nonce, cipher.argon2params) - postMessage.SetLabel("Done") # Todo: these labels need to disappear after some time + postMessage.SetLabel("Done") # Todo: these labels need to disappear after some time postMessage1.SetLabel("Done") # noinspection PyUnusedLocal,PyPep8Naming -def getItem(save_location, file_to_get, ENCRYPT_KEY): +def getItem(save_location: str, file_to_get: str, PASSKEY: Union[str, bytes]): + """ + Gets an encrypted file from Reddit + :param save_location: The location to save the file at + :param file_to_get: The name of the file to get. This should be the same as the post title on Reddit + :param PASSKEY: The password used to generate the encryption key. + """ filepath = save_location - # k = filename.rfind("/") - # filename = filename[k+1:] - # filepath = filepath[:k+1] - # temp_fp = filepath - # filepath = filepath + file_to_get - - # loginMod(username, password, subreddit) - cipher = AESCipher(ENCRYPT_KEY) + cipher = AESCipher(PASSKEY) encrypt_items = get_ciphertext(file_to_get) cipher.decrypt_to_file(encrypt_items, filepath) del encrypt_items @@ -458,6 +430,10 @@ def getItem(save_location, file_to_get, ENCRYPT_KEY): def StartApp(): + """ + Starts the app UI. Does not execute in a separate thread. Code after this function will only execute after + the window is closed. + """ app = wx.App() MainWindow(None, title='subreddit') app.MainLoop() From d84cea9c33942ab383b56dc5faa66ac006b38ac0 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:06:13 -0500 Subject: [PATCH 34/36] Documentation done. --- RedditStorage.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/RedditStorage.py b/RedditStorage.py index a8c061f..a394161 100644 --- a/RedditStorage.py +++ b/RedditStorage.py @@ -226,7 +226,8 @@ def __init__(self, parent: Union[wx.Window, None] = None): self.InitUI() def fileListener(self, fileName): - """Subscribed to 'fileListener' event""" + """Sets the value in the 'Filepath' field. + Subscribed to 'fileListener' event""" self.fileToGetField.SetValue(fileName) # noinspection PyAttributeOutsideInit @@ -301,6 +302,10 @@ def onClickSaveItem(self, e): # noinspection PyUnusedLocal def onClickGetItem(self, e): + """ + Gets the file from the specified Reddit post and downloads to specified file path. + Activated when the 'Get' button is clicked + """ if (self.fileToGetField.IsEmpty()): postMessage1.SetLabel("No File Specified") elif (self.keypassField.IsEmpty()): @@ -314,9 +319,7 @@ def onClickGetItem(self, e): @staticmethod def onClickGetRedditList(e): """ - Function used when user clicks the 'Retrieve List of Stored Files' button - :param e: - :return: + Shows a list of files in a separate window posted to the subreddit. """ frame = RedditList() pub.sendMessage("subredditListener", subreddit_name=SUBREDDIT) @@ -325,18 +328,16 @@ def onClickGetRedditList(e): class MainNotebook(wx.Notebook): - def __init__(self, parent): - wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style= - wx.BK_DEFAULT - # wx.BK_TOP - # wx.BK_BOTTOM - # wx.BK_LEFT - # wx.BK_RIGHT - ) - + def __init__(self, parent: Union[wx.Window, None] = None): + """ + Constructs a notebook. + :param parent: The parent window of the notebook. + """ + wx.Notebook.__init__(self, parent, id=wx.ID_ANY, style=wx.BK_DEFAULT) self.InitUI() def InitUI(self): + """Initializes the UI for the notbook""" tab_one = PostPanel(self) self.AddPage(tab_one, "Post") @@ -347,17 +348,17 @@ def InitUI(self): self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGING, self.OnPageChanging) def OnPageChanged(self, event): + """Event for when tab is finished changing""" old = event.GetOldSelection() new = event.GetSelection() sel = self.GetSelection() - # print 'OnPageChanged, old:%d, new:%d, sel:%d\n' % (old, new, sel) event.Skip() def OnPageChanging(self, event): + """Event for when tab is about to change""" old = event.GetOldSelection() new = event.GetSelection() sel = self.GetSelection() - # print 'OnPageChanging, old:%d, new:%d, sel:%d\n' % (old, new, sel) event.Skip() @@ -365,7 +366,7 @@ class MainWindow(wx.Frame): def __init__(self, parent, title: str): """ - Creates a wxPython window + Creates a wxPython window. :param parent: The parent process for this window :param title: The title of the window. Process will show up in things such as Task Manager or 'ps' with this name """ From e237340b3c01df68fe2f1075447fafd7df608ab4 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:08:31 -0500 Subject: [PATCH 35/36] Added DocString to some vars --- crypt.py | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/crypt.py b/crypt.py index 3b353f0..ddb7118 100644 --- a/crypt.py +++ b/crypt.py @@ -7,8 +7,9 @@ class AESCipher(object): - # Mainly for internal use. So we don't have to remake this object every encryption/decryption + hasher = PasswordHasher() + """Mainly for internal use. So we don't have to remake this object every encryption/decryption""" def __init__(self, key: str): """ @@ -78,17 +79,6 @@ def decrypt_to_file(self, encrypt_items: Tuple[bytes, List[str]], file_path: str with open(file_path, 'wb') as fo: fo.write(dec) - # # encrypts plaintext and generates IV (initialization vector) - # def _encrypt(self, plaintext: Union[str, bytes]) -> Tuple[bytes, bytes, bytes]: - # """ - # Returns the AES-GCM-encrypted ciphertext and MAC - # :param plaintext: The plaintext to encrypt - # :return: A Tuple containing [ciphertext, MAC] - # """ - # cipher = AES.new(self.key, AES.MODE_GCM) - # ciphertext_mac = cipher.encrypt_and_digest(plaintext) - # return ciphertext_mac[0], ciphertext_mac[1], cipher.nonce - # decrypts ciphertexts def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce: bytes, argon2_params: Parameters) -> bytes: @@ -111,6 +101,7 @@ def _decrypt(self, ciphertext: bytes, mac_tag: bytes, salt: bytes, nonce: bytes, return cipher.decrypt_and_verify(ciphertext, mac_tag) _NAME_TO_TYPE = {"argon2id": Type.ID, "argon2i": Type.I, "argon2d": Type.D} + """Dictionary quick translation of an argon2 type to appropriate enum""" @classmethod def extract_parameters(cls, argon2item: str) -> List[Union[Parameters, str]]: From 69fb85d5eaef0053b2b8c9f87001257e6ff82087 Mon Sep 17 00:00:00 2001 From: Kenneth-W-Chen <74205780+Kenneth-W-Chen@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:10:14 -0500 Subject: [PATCH 36/36] Added DocString to some vars --- redditglobals.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/redditglobals.py b/redditglobals.py index f3be7f4..3d218cd 100644 --- a/redditglobals.py +++ b/redditglobals.py @@ -4,7 +4,9 @@ config = ConfigParser() config.read('praw.ini') USERAGENT = "reddit storage bot" +"""The useragent of the bot. See https://en.wikipedia.org/wiki/User_agent for more details""" SUBREDDIT = config['reddit storage bot']['subreddit'] -# MAXPOSTS = 100 +"""The name of the subreddit files are posted to.""" REDDIT = praw.Reddit(USERAGENT) +"""Praw instance for accessing Reddit."""