def attach_file(self, filename, name=None, content_type=None, comment=None): """attach_file Attach a file to this page Wrapper for the Confluence method attach_file() that includes the page ID of this object and is wrapped in utils.safe_service_connect(). Parameters ---------- filename : `str` Filename of the attachment name : `str`, optional Display name for this attachment [Default: None] content_type : `str`, optional MIME content type [Default: None] comment : `str`, optional Additional comment or description to be included [Default: None] """ if not self._check_perm("CREATEATTACHMENT", "create an attachment"): return utils.safe_service_connect( self.instance.attach_file, filename, name=name, content_type=content_type, page_id=self.page_id, comment=comment, )
def create(self, page_body, parent_id=None): """create Create a brand new Confluence page Summon from the depths of computing a new page. Parameters ---------- page_body : `str` The body of the new Confluence page. parent_id : `str`, optional The parent page to place this under. If none given, the new page will be created at the root of `self.space`. [Default: None] """ if not self._check_perm("EDITSPACE", "create a page"): return # Check if it exists before we try anything if self.exists: print("Can't create a page that already exists!") return utils.safe_service_connect( self.instance.create_page, self.space, self.title, page_body, parent_id=parent_id, representation="wiki", editor="v1", ) # Set the instance metadata (exists, page_id, etc.) self._set_metadata()
def smite(self): """smite Kill with extreme prejudice Remove the Confluence page and update the instance metadata to reflect the new state. """ if not self._check_perm("REMOVEPAGE", "remove a page"): return utils.safe_service_connect(self.instance.remove_page, self.page_id) self._set_metadata()
def _read_channels(self, name): """_read_channels Return the Channel ID for the names channel Parameters ---------- name : `str` The name of the channel Returns ------- `str` The desired Channel ID """ conversation_id = None try: # Call the conversations.list() method using the WebClient result = utils.safe_service_connect(self.client.conversations_list) for _ in result: if conversation_id is not None: break for channel in result["channels"]: if channel["name"] == name: conversation_id = channel["id"] break except slack_sdk.errors.SlackApiError as error: warnings.warn( f"An error occurred within SlackChannel._read_channels():\n{error}" ) # Return the conversation ID return conversation_id
def __init__(self, label=None, after=None, before=None, interactive=False): # Initialize basic stuff self.label_list = None self.message_list = [] # Initialize the Gmail connection self.service = setup_gmail(interactive=interactive) self.label_id = self._lableId_from_labelName(label) self.query = build_query(after_date=after, before_date=before) # Get the list of matching messages (API: users.messages.list) if self.label_id: try: results = utils.safe_service_connect( self.service.users() .messages() .list( userId="me", labelIds=[self.label_id], q=self.query, maxResults=500, ) .execute ) self.message_list = results.get("messages", []) except (HttpError, ConnectionError) as error: warnings.warn( f"An error occurred within GetMessages.__init__():\n{error}" )
def send(self): """send Send the GmailMessage _extended_summary_ Parameters ---------- n_tries : `int`, optional The number of retry attemps at sending this message [Default: 5] Returns ------- `dict` The sent message object """ # Take the message object, and 64-bit encode it properly for sending encoded_message = base64.urlsafe_b64encode(self.message.as_bytes()) # The sendable message is a dictionary containing the raw decoded thing sendable_message = {"raw": encoded_message.decode()} # If Gmail `Resource` was not returned earlier, try again if not self.service: self.service = setup_gmail() # Try to send the message (API: users.messages.send) try: return utils.safe_service_connect( self.service.users() .messages() .send(userId="me", body=sendable_message) .execute ) except (HttpError, ConnectionError) as error: warnings.warn(f"An error occurred within GmailMessage.send():\n{error}") return None
def add_label(self, label): """add_label Add a label to the Confluence page Sometimes it's helpful to have a label on a Confluence page for searching or sorting. This method adds such to a page. Parameters ---------- label : `str` The label to be added to the page """ if not self._check_perm("EDITSPACE", "add a label"): return utils.safe_service_connect(self.instance.set_page_label, self.page_id, label)
def _set_permdict(self): """_set_permdict Create a dictionary of permissions This method creates a dictionary of permissions for this user in this space. Each item in the dictionary is boolean based on the results of the method confluence.get_space_permissions(). Returns ------- `dict` The dictionary of permissions (boolean) """ perms = utils.safe_service_connect(self.instance.get_space_permissions, self.space) # Check to see if the authenticated user can view permissions if not perms: warnings.warn( f"User {self.uname} needs permission to view " f"permissions in space {self.space}. Contact " "your Confluence administrator.", utils.PermissionWarning, ) perm_dict = {} for perm in perms: # Set this permission as false... will update to True if needed perm_dict[perm["type"]] = False for space_perm in perm["spacePermissions"]: if space_perm["userName"] == self.uname: perm_dict[perm["type"]] = True return perm_dict
def _set_metadata(self): """_set_metadata Set the various instance metadata Especially after a page is created or deleted, this method updates the various instance attributes to keep current. """ self.exists = utils.safe_service_connect(self.instance.page_exists, self.space, self.title) # Page-Specific Information self.page_id = (None if not self.exists else utils.safe_service_connect( self.instance.get_page_id, self.space, self.title)) self.attachment_url = ( None if not self.exists else f"{self.instance.url}download/attachments/{self.page_id}/")
def send_message(self, message): """send_message Send a (text only) message to the channel _extended_summary_ Parameters ---------- message : `str` or `blocks[]` array The message to send to the Slack channel Returns ------- `Any` The response from Slack """ response = None try: # Call the conversations.list method using the WebClient response = utils.safe_service_connect( self.client.chat_postMessage, channel=self.channel_id, text=message # You could also use a blocks[] array to send richer content ) # Print result, which includes information about the message (like TS) # print(result) except slack_sdk.errors.SlackApiError as error: warnings.warn( f"An error occurred within SlackChannel.send_message():\n{error}" ) return response
def upload_file(self, file, title=None): """upload_file Upload a file to the channel _extended_summary_ Parameters ---------- file : `str` or `os.PathLike` The (path and) filename of the file to be uploaded. title : `str`, optional The title for the file posted [Default: None] Returns ------- `Any` The response from Slack """ response = None try: response = utils.safe_service_connect( self.client.files_upload, channels=self.channel_id, file=file, title=title, ) except slack_sdk.errors.SlackApiError as error: warnings.warn( f"An error occurred within SlackChannel.upload_file():\n{error}" ) return response
def add_comment(self, comment): """add_comment Add a comment to the Confluence page Sometimes it's helpful to include a comment at the bottom of the Confluence page. These will be signed by Nanni. This method adds such to a page. Parameters ---------- comment : `str` The comment to be left on the page. """ if not self._check_perm("COMMENT", "add a comment"): return utils.safe_service_connect(self.instance.add_comment, self.page_id, comment)
def render_message(self, message_id): """render_message Retrieve and render a message by ID# Gmail mnessages are stored in a JSON-like structure that must be parsed out to get the tasty nougat center. Parameters ---------- message_id : `str` The ['id'] field of an entry in self.message_list Returns ------- `dict` Dictionary containing the subject, sender, date, and body of the message. """ try: # Get the message, then start parsing (API: users.messages.get) results = utils.safe_service_connect( self.service.users().messages().get(userId="me", id=message_id).execute ) payload = results["payload"] headers = payload["headers"] # If exception, print message and return empty values except (HttpError, ConnectionError) as error: warnings.warn( f"An error occurred within GetMessages.render_message():\n{error}" ) payload = None # Return empty dictionary if unsuccessful in connecting if not payload: return dict(subject="", sender="", date="", body="") # Look for Subject and Sender Email in the headers for d in headers: if d["name"] == "Subject": subject = d["value"] if d["name"] == "From": sender = d["value"] if d["name"] == "Date": date = d["value"] # The Body of the message is in Encrypted format -- decode it. # Get the data and decode it with base 64 decoder. data = payload["body"]["data"] data = data.replace("-", "+").replace("_", "/") decoded_data = base64.b64decode(data) # `decoded_data` is in lxml format; parse with BeautifulSoup body = BeautifulSoup(decoded_data, "lxml").body() body = body[0].text # Return a dictionary with the plain-text components of this message return dict(subject=subject, sender=sender, date=date, body=body)
def delete_attachment(self, filename): """delete_attachment Delete an attachment from this page Wrapper for the Confluence method delete_attachment() that includes the page ID of this object and is wrapped in utils.safe_service_connect(). An attahment may be deleted using either the filename, or by passing `None` or "" to the filename and specifying the attachment_id. Parameters ---------- filename : `str` Filename of the attachment to delete """ if not self._check_perm("REMOVEATTACHMENT", "remove an attachment"): return utils.safe_service_connect(self.instance.delete_attachment, self.page_id, filename)
def update_msg_labels(self, message_id, add_labels=None, remove_labels=None): """update_msg_labels Update the labels for a message by ID# _extended_summary_ Parameters ---------- message_id : `str` The ['id'] field of an entry in self.message_list add_labels : `list`, optional The list of label IDs to add to this message [Default: None] remove_labels : `list`, optional The list of label IDs to remove from this message [Default: None] Returns ------- `Any` Uh, the Message object from Gmail... probably just return nothing? """ if not add_labels and not remove_labels: print("No labels to change.") return None # Convert Label Names to Label IDs add_label_ids, remove_label_ids = [], [] if add_labels: for label in add_labels: add_label_ids.append(self._lableId_from_labelName(label)) if remove_labels: for label in remove_labels: remove_label_ids.append(self._lableId_from_labelName(label)) # Build the label dictionary to send to Gmail body = {} if add_label_ids: body["addLabelIds"] = add_label_ids if remove_label_ids: body["removeLabelIds"] = remove_label_ids try: # Modify message lables (API: users.messages.modify) return utils.safe_service_connect( self.service.users() .messages() .modify(userId="me", id=message_id, body=body) .execute ) # If exception, print message except (HttpError, ConnectionError) as error: warnings.warn( f"An error occurred within GetMessages.update_msg_labels():\n{error}" ) # If unsuccessful in connecting, return None return None
def update_contents(self, body): """update_contents Update the contents of the Confluence page Update the page by replacing the existing content with new. The idea for this method is to be used in concert with `get_page_contents` to obtain a page, modify it, then replace it. Parameters ---------- body : `str` The new page contents to upload to Confluence. Returns ------- _type_ _description_ """ if not self._check_perm("EDITSPACE", "update a page"): return utils.safe_service_connect(self.instance.update_page, self.page_id, self.title, body)
def get_page_contents(self): """get_page_contents Retrieve the page contents in HTML-ish format Either for curiosity or for modification, get the page contents, which live in the `body.storage` portion of the `get_page_by_id` response. Returns ------- `str` The HTML-ish body of the confluence page. """ contents = utils.safe_service_connect(self.instance.get_page_by_id, self.page_id, expand="body.storage") # Extract the contents from the return object return contents["body"]["storage"]["value"]
def _lableId_from_labelName(self, name): """_lableId_from_labelName Get the Label ID from the Label Name _extended_summary_ Parameters ---------- name : `str` Label name Returns ------- `str` Label ID """ if not self.service: return None # Only do this once if not self.label_list: # Get the list of labels for the "me" account (API: users.labels.list) try: results = utils.safe_service_connect( self.service.users().labels().list(userId="me").execute ) self.label_list = results.get("labels", []) except (HttpError, ConnectionError) as error: warnings.warn( f"An error occurred within GetMessages._labelId_from_labelName():\n{error}" ) self.label_list = [] # If there are no labels, return None if not self.label_list: print("Whoops, no labels found.") return None label_id = None # Go through the labels, and return the ID matching the name for label in self.label_list: if label["name"] == name: label_id = label["id"] return label_id
def get_page_attachments(self, limit=200): """get_page_attachments _summary_ Return a list of page attachment IDs, up to `limit` in length. Parameters ---------- limit : `int`, optional The number of attachments to return [Default: 200] Returns ------- `list` List of Confluence attachment IDs """ return utils.safe_service_connect( self.instance.get_attachments_from_content, self.page_id, limit=limit)
------- `googleapiclient.discovery.Resource` The GMail API service object for consumption by other routines """ # Read in the credential token creds = None if os.path.exists(token_fn := utils.Paths.gmail_token): creds = Credentials.from_authorized_user_file(token_fn, SCOPES) # If there are no (valid) credentials available... if not creds or not creds.valid: # If just expired, refresh and move on if creds and creds.expired and creds.refresh_token: try: utils.safe_service_connect(creds.refresh, Request()) except (HttpError, ConnectionError) as error: warnings.warn(f"An error occurred within setup_gmail():\n{error}") # If running in `interactive`, lauch browser to log in elif interactive: flow = InstalledAppFlow.from_client_secrets_file( utils.Paths.gmail_creds, SCOPES ) creds = flow.run_local_server(port=0) # Otherwise, raise an exception and specify to run interactively else: raise ValueError( "\nNo Gmail credentials found. You may need to run:\n" "j5_install_conf\n"