def create_mail(sender: str, to: str, subject: str, msg_html: str, msg_plain: str) -> dict: """Create an email message. I got this to work thanks to https://stackoverflow.com/questions/37201250/sending-email-via-gmail-python Args: sender (str): Mail address of sender to (str): Destination mail address subject (str): Mail subject msg_html (str): Html content for the mail msg_plain (str): String version of the mail Returns: Message body in mime format """ logger.info('creating mail') msg = MIMEMultipart('alternative') msg['Subject'] = subject msg['From'] = sender msg['To'] = to msg.attach(MIMEText(msg_plain, 'plain')) msg.attach(MIMEText(msg_html, 'html')) raw = base64.urlsafe_b64encode(msg.as_bytes()) raw = raw.decode() body = {'raw': raw} return body
def create_file(source_file_path: str, file_name: str = None, parent_folder_id: str = None, service=None) -> dict: """Upload a file from the local machine into a new file on the drive Args: source_file_path (str): Path to the file to upload file_name (str): If None, the name of the file will be used parent_folder_id (str): Id of a folder to put the copy into. if none is specified, the copy will be at the root of the drive. service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: dict containing the id and name of the created file """ logger.info('creating file') source_file_path = Path(source_file_path).expanduser() if file_name is None: file_name = source_file_path.name request_body = {'name': file_name, 'mimeType': '*/*'} media_body = MediaFileUpload( str(source_file_path), mimetype='*/*', resumable=False, ) if parent_folder_id is not None: request_body["parents"] = [parent_folder_id] return service.files().create( body=request_body, media_body=media_body, ).execute()
def get_messages(query: str, service=None) -> list: """List messages matching the specified query Args: query (str): a gmail-message-search-query. Documentation link: https://support.google.com/mail/answer/7190?hl=en service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: List of Messages that match the criteria of the query. Note that the returned list contains Message IDs, you must use get with the appropriate ID to get the details of a Message. """ logger.info('getting mails') response = service.users().messages().list(userId='me', q=query).execute() messages = [] if 'messages' in response: messages.extend(response['messages']) while 'nextPageToken' in response: page_token = response['nextPageToken'] response = service.users().messages().list( userId='me', q=query, pageToken=page_token).execute() messages.extend(response['messages']) return messages
def get_files(query: str, service=None) -> list: """Query google drive for files matching `query` If no accessible file matches the query, returns an empty list. Args: query (str): a drive-file-search-query. Documentation link: https://developers.google.com/drive/api/v3/search-parameters service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: list of dict, file information records. Contains the name & id of the first file in the drive matching the query and accessible within the oauth permissions of the script """ logger.info('getting files') logger.debug(f'query = {query}') page_token = None files = [] # We have to cycle on all the pages of the drive, while True: file_candidates = service.files().list( q=query, fields="nextPageToken, files(id, name)", pageToken=page_token).execute() files += file_candidates.get('files', []) page_token = file_candidates.get('nextPageToken', None) if page_token is None: break return files
def send_file(mail_address: str, mail_subject: str, file_id: str, service=None, sender: str = '*****@*****.**') -> dict: """Send a mail with a link to a google doc Args: mail_address (str): Destination mail address mail_subject (str): Mail subject file_id (str): Id of the file to send service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` sender (str): Mail address of the sender Returns: dict, information about the message used to send the file, including it's id """ logger.info('sending file') message = create_mail( sender, mail_address, mail_subject, f"<a href=https://docs.google.com/document/d/{file_id}>" f"Project description</a>", f"https://docs.google.com/document/d/{file_id}") return send('me', message, service=service)
def default_service(): """Lazy getter for the default drive-api-service to use Returns: The official python wrapper around the drive api """ logger.info("instantiating google drive service") return build('drive', 'v3', http=get_creds().authorize(Http()))
def delete_file(file_id: str, service=None): """copy a file in the user's drive Args: file_id (str): service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: an empty string if successful """ logger.info("deleting file") return service.files().delete(fileId=file_id).execute()
def get_labels(service=None) -> list: """Fetches all existing labels in the user's inbox Args: service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: list of dict, label information records. The id and name for each label existing in the user's inbox. """ logger.info('fetching labels') return service.users().labels().list(userId='me').execute().get( 'labels', [])
def download_file(file_id: str, service=None): """Download a file and return it in a variable Args: file_id (str): Id of the file to download service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: the content of the file """ logger.info('downloading file') return service.files().get_media(fileId=file_id).execute()
def send(user_id: str, mime_msg: dict, service=None) -> dict: logger.info('sending mail') """Send an email message. Args: user_id (str): User's email address. The special value "me" can be used to indicate the authenticated user. mime_msg (mime message): Message to be sent service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: dict containing information about the message sent, including it's id """ result = service.users().messages().send(userId=user_id, body=mime_msg).execute() return result
def create_folder(folder_name: str, service=None) -> dict: """Create a new folder in the user's drive Args: folder_name (str): name of the folder to create service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: dict, id and name of the folder """ logger.info('creating folder') file_metadata = { 'name': folder_name, 'mimeType': 'application/vnd.google-apps.folder' } return service.files().create(body=file_metadata, fields='id, name').execute()
def get_creds(config: Config = default): """Check that the SSO token is valid. If not, asks for a new one. Asking for a new token is done by opening the sing-in-with-google-tab in the browser. The google account linked to in client_id.json is pre-selected. The `client_id.json` corresponds to what you can download from https://console.developers.google.com/apis/credentials. I took this function almost as is from the official google gmail api doc: https://developers.google.com/gmail/api/quickstart/python. Note: `oauth2client.tools` bugs if `sys.argv` are specified. This function fixes that. Args: config: a config element as defined in the `config.py` package Returns: credentials that the api can work with """ config_path = config.credential_path scopes = config.scopes logger.info('loading token') logger.debug(f'config_path: {config_path}') config_path = Path(config_path).expanduser() store = file.Storage(config_path / 'token.json') creds = store.get() if not creds or creds.invalid: # Ask the user to give the correct permissions. logger.info('loading credentials') flow = client.flow_from_clientsecrets(config_path / 'client_id.json', scopes) arguments = sys.argv sys.argv = sys.argv[0:1] # This line is why we need to remove the arguments from sys.argv # If you find a better way to get it to work, i'm buying it creds = tools.run_flow(flow, store) sys.argv = arguments return creds
def move_to_trash(message_id: str, service=None): """Mark a message with a label, as read and archive it Args: message_id (str): Id of the message to trash service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: the api request's result """ logger.info('moving mail to trash') return service.users().messages().modify(id=message_id, userId='me', body={ "addLabelIds": [ 'TRASH', ] }).execute()
def archive_message(message_id: str, extra_labels: str = None, service=None): """Mark a message with a label, as read and archive it Args: message_id (str): Id of the message to archive extra_labels (str): Labels to add to the message in the form "label_1,label2" service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: the api request's result """ logger.info('archiving mail') body = {"removeLabelIds": ['UNREAD', 'INBOX']} if extra_labels is not None: body["addLabelIds"] = [extra_labels] return service.users().messages().modify(id=message_id, userId='me', body=body).execute()
def create_label(label_name: str, service=None) -> dict: """Create a label with the specified name in the user's inbox Args: label_name(str): Name of the label to create service (optional, gmail-api-service): the service to use. Default: the result of `default_service()` Returns: dict containing the id and name of the created label """ logger.info('creating label') label = service.users().labels().create(userId='me', body={ 'messageListVisibility': 'show', 'name': label_name, 'labelListVisibility': 'labelShow' }).execute() return label
def update_file(source_file_path: str, file_id: str, file_name: str = None, parent_folder_id: str = None, service=None) -> dict: """Upload a file from the local machine into an existing file on the drive Args: source_file_path (str): Path to the file to upload file_id (str): Id of the file to update file_name (str): If None, the name of the file will be used parent_folder_id (str): If None, sets the file's folder to root. If you want to keep an existing folder, you will have to include it's id. service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: dict containing the id and name of the created file """ logger.info('updating file') source_file_path = Path(source_file_path).expanduser() if file_name is None: file_name = source_file_path.name request_body = {'name': file_name, 'mimeType': '*/*'} media_body = MediaFileUpload( str(source_file_path), mimetype='*/*', resumable=False, ) if parent_folder_id is not None: request_body["parents"] = [parent_folder_id] return service.files().update( fileId=file_id, body=request_body, media_body=media_body, ).execute()
def copy_file(source_file_id: str, new_file_name: str, parent_folder_id: str = None, service=None) -> dict: """Duplicate a file inside the user's drive Args: source_file_id (str): Id of the file to copy new_file_name (str): Name to give to the copy parent_folder_id (str): Id of a folder to put the copy into. if none is specified, the copy will be at the root of the drive. service (optional, drive-api-service): the service to use. Default: the result of `default_service()` Returns: dict containing the id and name of the created file """ logger.info('copying file') request_body = { "name": new_file_name, } if parent_folder_id is not None: request_body["parents"] = [parent_folder_id] return service.files().copy(fileId=source_file_id, body=request_body).execute()
def default_service(): """Lazy getter for the default gmail-api-service to use """ logger.info("instantiating gmail service") return build('gmail', 'v1', http=get_creds().authorize(Http()))