diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e3460..7e727b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 1.4.4 (2023-05-26) +* fix: OTRS sending with no attachment forwarded + ## 1.4.3 (2023-05-05) * fix: OTRS sending removes the attachment we have been split from (avoid duplicity) diff --git a/convey/__init__.py b/convey/__init__.py index 3ba6d81..e2ef327 100644 --- a/convey/__init__.py +++ b/convey/__init__.py @@ -1,4 +1,4 @@ from .decorators import PickMethod, PickInput __all__ = ["PickMethod", "PickInput"] -__version__ = "1.4.3" +__version__ = "1.4.4" diff --git a/convey/mail_sender.py b/convey/mail_sender.py index ef08263..83b9269 100644 --- a/convey/mail_sender.py +++ b/convey/mail_sender.py @@ -22,6 +22,7 @@ OTRS_VERSION = 6 + class MailSender(ABC): def __init__(self, parser): self.parser: Parser = parser @@ -137,34 +138,15 @@ def _post_multipart(self, url, fields, cookies, attachments): # get FormID to pair attachments logger.debug("Receiving FormID") upload_form = get(url, - params={"ChallengeToken": self.parser.otrs_token, - "Action": "AgentTicketForward", - "TicketID": str(self.parser.otrs_id)}, - cookies=cookies - ) + params={"ChallengeToken": self.parser.otrs_token, + "Action": "AgentTicketForward", + "TicketID": str(self.parser.otrs_id)}, + cookies=cookies + ) m = re.search(r'FormID" value="((\d|\.)*)"', upload_form.text) if m: form_id = m[1] - - # Remove the attachment we have been split from (avoid duplicity) - if self.parser.source_file: - try: - bs = BeautifulSoup(upload_form.text, features="html.parser") - tr = bs.find('td', {'class': 'Filename'}, lambda tag: tag.string == self.parser.source_file.name).parent - data_file_id = tr.find('a', {'class': 'AttachmentDelete'})['data-file-id'] - logger.debug(f"Removing FileID={data_file_id} ({self.parser.source_file.name}) from attachments") - post(url, - params={"Action": "AjaxAttachment", - "Subaction": "Delete", - "FormID": form_id, - "ChallengeToken": self.parser.otrs_token, - "FileID": data_file_id - }, - cookies=cookies - ) - except TypeError: - logger.info(f"Unable to remove ({self.parser.source_file.name}) from attachments") - + self._clean_attachments(upload_form, url, form_id, cookies) for a in attachments: logger.debug("Uploading %s", a.name) post(url, @@ -181,13 +163,39 @@ def _post_multipart(self, url, fields, cookies, attachments): else: raise RuntimeError("Cannot get the FormID, unable to upload the attachments.") - # If we have a single file, we could upload it with single request that way: - # `files={"FileUpload": (a.name, a.data)}`` + # If we had a single file, we could upload it with a single request that way: + # `files={"FileUpload": (a.name, a.data)}` logger.debug("Submitting mail") - r = post(url, params=fields, cookies=cookies) + r = post(url, data=fields, cookies=cookies) return r.text + def _clean_attachments(self, upload_form, url, form_id, cookies): + """ Remove all the attachments, received in the first ticket article so that they are not forwareded. """ + REMOVE_ALL_ATTACHMENTS = True + def bs(): return BeautifulSoup(upload_form.text, features="html.parser") + + if REMOVE_ALL_ATTACHMENTS: + for td in reversed(bs().find_all('a', {'class': 'AttachmentDelete'})): # why reversed? When deleted, others data-file-id shift down. + self._remove_attachment(td['data-file-id'], form_id, url, cookies) + elif self.parser.source_file: # remove at least the attachment we have been split from (avoid duplicity) + td = bs().find('td', {'class': 'Filename'}, lambda tag: tag.string == self.parser.source_file.name) + if td: # such attachment exists + tr = td.parent + data_file_id = tr.find('a', {'class': 'AttachmentDelete'})['data-file-id'] + self._remove_attachment(data_file_id, form_id, url, cookies) + + def _remove_attachment(self, data_file_id, form_id, url, cookies): + logger.debug(f"Removing FileID={data_file_id} from the ticket article attachments") # XXX + vv = post(url, + params={"Action": "AjaxAttachment", + "Subaction": "Delete", + "FormID": form_id, + "ChallengeToken": self.parser.otrs_token, + "FileID": data_file_id + }, + cookies=cookies) + @staticmethod def _check_record(record, lineno): valid = ('CONTACTS' in record)