From 0768e01d58efcc02f8aff785c4059a9da59a7af5 Mon Sep 17 00:00:00 2001 From: nocerae Date: Tue, 6 Apr 2021 15:52:31 +0100 Subject: [PATCH] Initial commit --- .idea/.gitignore | 3 + .idea/Emanuele.iml | 10 + .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + APIManager.py | 57 ++++ Database.py | 289 ++++++++++++++++++ EmailManager.py | 61 ++++ UserInterface.py | 219 +++++++++++++ main.py | 59 ++++ 10 files changed, 716 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/Emanuele.iml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 APIManager.py create mode 100644 Database.py create mode 100644 EmailManager.py create mode 100644 UserInterface.py create mode 100644 main.py diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/Emanuele.iml b/.idea/Emanuele.iml new file mode 100644 index 0000000..74d515a --- /dev/null +++ b/.idea/Emanuele.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d56657a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..c687523 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/APIManager.py b/APIManager.py new file mode 100644 index 0000000..d917606 --- /dev/null +++ b/APIManager.py @@ -0,0 +1,57 @@ +import requests # to get/download the data from the api +import json # parse the api data + +""" +Class APIManager + +Gestisce la comunicazione HTTP con le API che forniscono i dati per le newsletter +""" + +class APIManager: + + """ + + Metodo: getNews + + Invia una richiesta GET tramite la libreria requests alle API trovate online + + - Costruisce il dizionario con i parametri della richiesta (header + querystring) + - Invia la richiesta con metodo GET all'url delle API (trovato online su rapidapi.com) + - decodifica la risposta json in un Dizionario e restituisce i campi d'interesse (titolo, corpo, url) della news + + """ + + + def getNews(self, category): + + url = "https://contextualwebsearch-websearch-v1.p.rapidapi.com/api/search/NewsSearchAPI" # url of the api + + # I parametri che andiamo a fornire alle API per la nostra richiesta. + # Questi sono da trovare nella documentazione delle API + + querystring = { + "q": f"{category}", # query to follow, topic to get the about + "pageNumber": "1", # no of pages cna be change + "pageSize": "1", # size of the page should limit to paragraphs + "autoCorrect": "true", # spelling checker + "fromPublishedDate": "null", # date init + "toPublishedDate": "null" # date end + } + + headers = { + 'x-rapidapi-key': "20c7edee10msh9b13ee9a5b2a529p1d3da9jsnbed33a1d0b94", # sending header + 'x-rapidapi-host': "contextualwebsearch-websearch-v1.p.rapidapi.com" # hosting sender + } + + # creating requests + response = requests.request("GET", url, headers=headers, params=querystring) + + responseText = response.text # leggiamo la risposta in JSON che ci ha dato il server (si trova nel campo text) + data = json.loads(responseText) # fornattiamo il JSON in un dizionario Python + + # Estrai i dati dal dizionario data e ritorna i singoli campi + title = data['value'][0]['title'] + body = data['value'][0]['body'] + url = data['value'][0]['url'] + + return title, body, url \ No newline at end of file diff --git a/Database.py b/Database.py new file mode 100644 index 0000000..95ff8f7 --- /dev/null +++ b/Database.py @@ -0,0 +1,289 @@ +import pandas as pd +import os + +class DatabaseManager: + + """ + Metodo: checkDatabaseExists + + Controlla se nella cartella dello script esistono i file csv che contengono i dati del software relativi agli utenti e le iscrizioni. + Se questi non esistono: + - Crea una cartella ./data + - Crea due Dataframe da Pandas che conterranno i dati relativi a utenti e iscrizioni. + - Salva i dataframe in due file .csv distinti all'interno della cartella ./data + """ + + + def checkDatabaseExists(self): + # check if data folder exists in root folder, either creates it + if not os.path.exists("./data"): + print("Creating data folder...") + os.mkdir("data") + + # database files + files = ["users.csv", 'subscriptions.csv'] #Dichiara una lista di stringhe con i nomi dei fila che vuole utilizzare + for file in files: + # check if files exists, else create them + if not os.path.exists(f"./data/{file}"): + print(f"{file} Not Exists") + print(f"Creating {file}...") + + if 'users' in file: + #Crea il database con pandas e lo salva nel file users.csv + # init the dataframe + df = pd.DataFrame({ + "name": [], + "username": [], + "password": [], + }) + + # save the dataframe to users + df.to_csv("./data/users.csv", index=False) + + elif 'subscriptions' in file: + # init the dataframe + df = pd.DataFrame({ + "username": [], + "email": [], + "business": [], + "finance": [], + "computer": [], + "games": [], + "entertainment": [], + "music": [], + "currentAffairs": [], + "health": [], + "lifestyle": [], + "sports": [], + "culture": [], + "religion": [], + }) + # save the dataframe + df.to_csv("./data/subscriptions.csv", index=False) + + + ''' + Metodo: getUsersDF + + - Chiama il metodo checkDatabaseExists + - Legge e restituisce il dataframe con i dati relativi agli utenti + + ''' + def getUsersDF(self): + self.checkDatabaseExists() + df = pd.read_csv("./data/users.csv") + return df + + + + ''' + Metodo: getSubscriptionsDF + + - Chiama il metodo checkDatabaseExists + - Legge e restituisce il dataframe con i dati relativi alle iscrizioni + + ''' + def getSubscriptionsDF(self): + self.checkDatabaseExists() + df = pd.read_csv("./data/subscriptions.csv") + return df + + + ''' + Metodo: pintSubscriptions + + Stampa la tabella delle iscrizioni + + - Chiama il metodo getSubscriptionsDF + - Stampa il risultato + + ''' + + def printSubscriptions(self): + print(self.getSubscriptionsDF()) + + ''' + Metodo: removeUser + Param: username + + - Controlla se username è contenuto nella tabella users.csv + - Elimina il record relativo a username in entrambe le tabelle + - Salva le modifiche effettuate + + ''' + + + def removeUser(self, uname): + dfSub = self.getSubscriptionsDF() + dfUsers = self.getUsersDF() + # if user found + if dfSub['username'].str.contains(uname).any(): # if username exists in any record + print("Username found in database") + + # drop the row from users.csv and subscriptions.csv + + # Rimuove la riga dalla tabella dfSub che contiene 'uname' nel campo username. Con inplace = True modifico la tabella senza creare una copia + dfSub.drop(dfSub[dfSub['username'] == uname].index, inplace=True) + # Rimuove la riga dalla tabella dfUsers che contiene 'uname' nel campo username. Con inplace = True modifico la tabella senza creare una copia + dfUsers.drop(dfUsers[dfUsers['username'] == uname].index, inplace=True) + + # save the new csv + dfSub.to_csv("./data/subscriptions.csv", index=False) + dfUsers.to_csv("./data/users.csv", index=False) + + print(f"'{uname}' removed sucessfully") + else: # if user not found + print("username not found") + + ''' + Metodo: getSubscriptionsFor + Param: username + + - Ritorna il record del Dataframe che contiene il nome utente indicato e quindi tutte le sue iscrizioni + + ''' + + def getSubscriptionsFor(self,uname): + df = self.getSubscriptionsDF() + data = df[df['username'] == uname] + return data #Ritorna il record del Dataframe che contiene il nome utente indicato e quindi tutte le sue iscrizioni + + + ''' + Metodo: getSubscriptionsCategories + + + - Restituisce le colonne della tabella delle iscrizioni a partire dalla terza + + ''' + + def getSubscriptionsCategories(self): + return self.getSubscriptionsDF().columns[2:] + + ''' + Metodo: subscribeUserToCategory + + - Inserisce il carattere 'y' nella colonna indicata dal parametro category + - Salva il dataframe nell'apposito file. + + + ''' + + def subscribeUserToCategory(self,uname,category): + df = self.getSubscriptionsDF() #ottiene il Df delle iscrizioni + #Recupera il recordo con i dati dell'utente + df.loc[df.username == uname, category] = 'y'# inserisce 'y' alla colonna della categoria indicata nel record dell'utente indicato + print(df) + self.__saveSubscriptionsDF(df) # Chiama la funzione per salvare il file (to_csv) + print(f"{uname} successfully subscribed to {category}") + + ''' + Metodo: unsubscribeUserToCategory + + - Inserisce il carattere 'n' nella colonna indicata dal parametro category + - Salva il dataframe nell'apposito file. + + + ''' + + def unsubscribeUserToCategory(self, uname, category): + df = self.getSubscriptionsDF() + df.loc[df.username == uname, category] = 'n' # inserisce 'n' alla colonna della categoria indicata nel record dell'utente indicato + self.__saveSubscriptionsDF(df) + print("Unsubscribed successfully") + + def __saveSubscriptionsDF(self,df): + df.to_csv("./data/subscriptions.csv", index=False) + + def __saveUsersDF(self,df): + df.to_csv("./data/users.csv", index=False) + + ''' + Metodo: subscribeToAll + + - Inserisce il carattere 'n' in tutte le colonne della riga che si riferisce all'utente indicato + - Salva il dataframe nell'apposito file. + + + ''' + + def unsubscribeToAll(self, uname): + df = self.getSubscriptionsDF() #Ottiene il dataframe delle iscrizioni + row = df.loc[df.username == uname] #Prende il riferimento alla riga contenente i dati dell'utente + + for category in self.getSubscriptionsCategories(): #Per ogni colonna della tabella + row[category] = 'n' #Inserisce il carattere 'n' nelle colonna di quella specifica riga + + self.__saveSubscriptionsDF(df) #Salva + + + ''' + Metodo: userExists + Param: uname + + - Controlla se il nome utente inserito è contenuto in almeno un record della tabella + + ''' + + def userExists(self,uname): + df = self.getUsersDF() + return df['username'].str.contains(uname).any() #any() serve a capire se esiste almeno un elemento qualsiasi dove la condizione di contains() è verificata + + ''' + Metodo: uregUser + Param: uname , name, email, password + + - Controlla se il nome utente inserito è contenuto in almeno un record della tabella + - Crea I dizionari relativi alla tabella utente e a quella iscrizioni + - Imposta i valori iniziali + - Inserisce in append i due record con i dati dell'utente e le sue iscrizioni + - Salva i due file csv + + ''' + + def regUser(self, uname,name, email,password): + + if not self.userExists(uname): + + #Crea il dizionario che rappresenta il record con i dati dell'utente (andrà nella tabella DFUsers) + udata = { + "username": uname, + "name": name, + "password": password, + } + + #Crea il dizionario che rappresenta il record con i dati delle iscrizioni (andrà nella tabella DFSubscriptions) + sdata = { + "username": uname, + "email": email, + "business": 'n', + "finance": 'n', + "computer": 'n', + "games": 'n', + "entertainment": 'n', + "music": 'n', + "currentAffairs": 'n', + "health": 'n', + "lifestyle": 'n', + "sports": 'n', + "culture": 'n', + "religion": 'n', + } + + dfUsers = self.getUsersDF() # Prende un riferimento alla tabella DFUsers + dfSub = self.getSubscriptionsDF() # Prende un riferimento alla tabella DFSubscriptions + + #Inserisce con il metodo append (all'ultimo elemento) il record con i dati dell'utente nella tabella DFUsers + dfUsers = dfUsers.append(udata, ignore_index=True) + self.__saveUsersDF(dfUsers) #salva + + #Inserisce con il metodo append (all'ultimo elemento) il record con i dati delle iscrizioni nella tabella DFSub + dfSub = dfSub.append(sdata, ignore_index=True) + self.__saveSubscriptionsDF(dfSub) #salva + + # output if everything completes + print("You are successfully registed but not Subscribed to any list. Please login to your account to select any subscription.") + else: + print("Username already exists") + + diff --git a/EmailManager.py b/EmailManager.py new file mode 100644 index 0000000..8a58e47 --- /dev/null +++ b/EmailManager.py @@ -0,0 +1,61 @@ +import smtplib # to connect to gmail api +from email.message import EmailMessage # email formatting and reshaping +from APIManager import APIManager +from Database import DatabaseManager + + +class EmailManager: + + dm = DatabaseManager() + api = APIManager() + + # ---------------------- sends the email to a user + def sendEmailtoUser(self,email, subject, title, body, url): + EMAIL_ADDR = 'ema.univ@gmail.com' # email from + EMAIL_PASS = 'Emaun1v2k21' # password + + # enter the content, prettify + content = f''' + News Alert + ----------------------------------------------- + + Title: {title} + + {body} + + Reference url: {url} + ''' + + msg = EmailMessage() + msg['Subject'] = subject # adding title as email subject + msg['From'] = EMAIL_ADDR # from email + msg['To'] = email # senders email + msg.set_content(content) # enter the content + + with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp: + smtp.login(EMAIL_ADDR, EMAIL_PASS) # login to API + smtp.send_message(msg) # send the msg + print(f"Email sent to {email} for {subject}") # print msg acknowledgement + + # ---------------------- fectch emails and categories + def sendEmails(self): + print('Its Email time') + + print(''' + ---------------------------- + Sending Emails + ---------------------------- + ''') + # import pandas as pd + df = self.dm.getSubscriptionsDF() # get subscription csv + + cats = ['business', 'finance', 'computer', 'games', 'entertainment', 'music', 'currentAffairs', 'health', + 'lifestyle', 'sports', 'culture', 'religion'] + for cat in cats: + print(">>", cat) + title, body, url = self.api.getNews(cat) # get the news of ceertain category + + for email in df[df[cat] == 'y']['email']: # if cat has 'y' variable + self.sendEmailtoUser(email, title, title, body, url) # send the email + + diff --git a/UserInterface.py b/UserInterface.py new file mode 100644 index 0000000..61555e4 --- /dev/null +++ b/UserInterface.py @@ -0,0 +1,219 @@ +from Database import DatabaseManager + +''' +Classe utilizzata per simulare un interfaccia utente +''' +class UserInterface: + + dm = DatabaseManager() #Istanza del database manager utilizzata per le operazioni sui dati + + ''' + Metodo: showMainMenu + + Presenta al terminale un'interfaccia all'avvio del programma che permette di scegliere le azioni da eseguire + + ''' + + def showMainMenu(self): + menu = ''' + ------------------ MAIN MENU ---------------------- + Press following numbers to select + + 1- Admin (Manage Requests, Remove Users) + 2- Login + 3- Register + 4- Send Emails (Admin only) + 5- Exit + ---------------------------------------------------- + ''' + + print(menu) # print it menu + + ''' + Metodo: showAdminMenu + + Presenta al terminale un'interfaccia per il pannello di controllo dell'amministratore di sistema, una volta che + l'amministratore ha effettuato l'accesso + + Permette di leggere le iscrizioni di tutti gli utenti ed eliminare gli utenti iscritti + ''' + + def showAdminMenu(self): + + while True: + # print the menu + menu = ''' + ------------------- ADMIN MENU --------------------- + Press following numbers to select + + 1- See all users' subscriptions + 2- Remove User + 3- Exit + ---------------------------------------------------- + ''' + print(menu) + + _sel = int(input("> ")) # selt the input from menu + + # shows all the database + if _sel == 1: + self.dm.printSubscriptions() + # remove the specific user + elif _sel == 2: + uname = input("Enter username: ") # enter the username + self.dm.removeUser(uname) + + elif _sel == 3: + return + else: + print("Wrong selection") + + ''' + Metodo: showUserMenu + + Presenta al terminale un'interfaccia per il pannello di controllo dell'utente , una volta che ha effettuato l'accesso + + Permette di iscriversi o disiscriversi ai diversi topic di newsletter + ''' + + def showUserMenu(self, uname): + + categories = self.dm.getSubscriptionsCategories() + while True: + # prints the menu + menu = ''' + -------------------- USER MENU -------------------- + Press following numbers to select + + 1- See all subscriptions + 2- Subscribe to news feed + 3- Unsubscribe to news feed + 4- Unsubscribe to all News Feeds + 5- Exit + --------------------------------------------------- + + ''' + print(menu) + + _sel = int(input("> ")) # select the input + + # see all subscribed lists + if _sel == 1: + userData = self.dm.getSubscriptionsFor(uname).to_dict('records')[0] # see all list by that user + for k, v in userData.items(): + if v == 'y': + print(k, ":", "Subcribed") # print the keys and "subscribed" next to it + #df.to_csv("./data/subscriptions.csv", index=False) # save the change to file + + # sub to new category + elif _sel == 2: + + print("You can subscribe to any of these categories \n", categories) # get all cols and print + catsel = input("type category (full): ") # select for the category + + if not catsel in categories: # if not in cols + print("invalid selection") + else: # else + self.dm.subscribeUserToCategory(uname,catsel) + + # unsub to any cay + elif _sel == 3: + print("You can unsubscribe to any of these categories \n", categories) # show all cats + catsel = input("type category (full): ") # input the cat + + if not catsel in categories: # if invalid selection + print("invalid selection") + else: + self.dm.unsubscribeUserToCategory(uname,catsel) + + + elif _sel == 4: + self.dm.unsubscribeToAll(uname) + + elif _sel == 5: + + return + + else: + print("Wrong selection") + + ''' + Metodo: showRegMenu + + Presenta al terminale un'interfaccia per le registrazione dell'utente + + Controlla l'esistenza del nuovo username e registra i nuovi dati + + ''' + + def showRegMenu(self): + + + menu = ''' + -------------------- REGISTER MENU -------------------- + Press Enter your following details to Register yourself + ------------------------------------------------------- + ''' + + print(menu) # print the menu + + # inputs user's informations + name = input("Name: ") # + uname = input("Username (unique): ") + + if self.dm.userExists(uname): # check if same username exists or not + print("Username already exists, Please choose different") # if yes then raise error + return + + password = input("password: ") + email = input("email: ") + + self.dm.regUser(uname,name,email,password) + + ''' + Metodo: showLoginAdmin + + Presenta al terminale un'interfaccia per il login dell'amministratore + + Controlla la correttezza dei dati e restituisce True o False in base ad essa + + ''' + + def showLoginAdmin(self): + _user = input("username: ") + _pass = input("Password: ") + + if (_user == 'admin') and (_pass == 'admin'): + return True # true if user pass matchs + else: + return False + + + ''' + Metodo: showLoginUser + + Presenta al terminale un'interfaccia per il login dell'utente + + Controlla la correttezza dei dati e restituisce True o False in base ad essa + + ''' + + def showLoginUser(self): + + df = self.dm.getUsersDF() + + _user = input('username: ') + _pass = input('password: ') + + if self.dm.userExists(_user): # Se il nome utente che hai inserito esiste + userData = df[df['username'] == _user] #Legge i dati relativi al nome utente che è stato inserito {"nome,""username","Password"} + if userData['password'].str.contains(_pass).any() : #Se la password che hai inserito è corretta + name = userData['name'] # get the name by username from csv file + print(f"welcome {name.to_string().split()[1]}") #Questa operazione sulla variabile name serve a prendere solo la seconda parola della stringa presente (nel caso abbia due nomi) + return True, _user # return true with name + else: + return False, None + else: + return False, None + + diff --git a/main.py b/main.py new file mode 100644 index 0000000..9b0ee21 --- /dev/null +++ b/main.py @@ -0,0 +1,59 @@ +from Database import DatabaseManager +from EmailManager import EmailManager +from UserInterface import UserInterface +import pandas as pd +import sys + + +def main(): + + dm = DatabaseManager() # Si occupa delle operazioni sul DB + email = EmailManager() # Si occupa di inviare le email + ui = UserInterface() # Mostra i menù e chiede gli input + + + dm.checkDatabaseExists() # checks for the database folder and database files exists or not. Creates the files of not exists + ui.showMainMenu() # shows the main menu + + select = int(input('>> ')) # waits for the input for 5mnts. + + if select == 1: # Admin (Manage Requests, Remove Users) Apre il pannello Admin + + if ui.showLoginAdmin(): # if admin true + print("Welcome admin") + ui.showAdminMenu() # shows admin menu + else: + print("Wrong username or password please retry") # raise error + + + elif select == 2: # Effettua il login dell'utente + re = ui.showLoginUser() # check if user exists re = (Esito, nomeUtente) + if re[0]: # Se il login va a buon fine + ui.showUserMenu(re[1]) # user menu + else: + print("User not exists or wrong password") # raises the error + + elif select == 3: + ui.showRegMenu() # registration menu and control panel + + elif select == 4: # Invia le email + re = ui.showLoginAdmin() # login admin + if re: # Se il login + print("Welcome admin") + email.sendEmails() # start sending the emails + else: + print("Wrong Username or Password") + + elif select == 5: + print("Bye...") # exits the system + sys.exit() # exits the system + + +# Press the green button in the gutter to run the script. +if __name__ == '__main__': + while True: + main() + + + +