From 81184a8acc32f0fc3f686bf6638c0fb6c99ec973 Mon Sep 17 00:00:00 2001 From: Berkay Date: Fri, 8 Aug 2025 17:15:19 +0300 Subject: [PATCH] updated Service Runner email Reader --- .../IsBank/__pycache__/config.cpython-312.pyc | Bin 0 -> 2002 bytes .../Reader/Banks/IsBank/app.py | 240 ++++++++++++++++++ .../Reader/Banks/IsBank/config.py | 37 +++ 3 files changed, 277 insertions(+) create mode 100644 ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/__pycache__/config.cpython-312.pyc create mode 100644 ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/app.py create mode 100644 ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/config.py diff --git a/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/__pycache__/config.cpython-312.pyc b/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/__pycache__/config.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..901da188a94a0eb60d023d509e3e9ba2d0a68487 GIT binary patch literal 2002 zcmaJ>%}*Og6rZ)%YkU0x6mS~{skAgDTgf&brYQ}IFu@`&#ExuGTQ|~b*`2}$dza2G zROZAXk$Q9v70xO2(ht!?{)DR6UW~R9?ly;1?WMP*t`sS!&dhpU6A?z%&u`}a-kW)| z?>E0hB0Rt&{qxtGbsqqKl8HOmj?qqI;{Yf?VJe^uJYWeZp1VNt9(f!_nW1YeUh{R= zJT*q({w9RH?9d$KI+ON?49N;XnNdL5vxf6R@0~y|)v*;u@pQBLAP4;lGlyNT)53Vk+rUDrNWH*EjSOm1bN@X6j=>uO~~tw2ptQ5DW(%T$x9u`X#_Nm;KZO=R;~ zLKbfnN~?SPwJvBRSH#g0_zcs#mca~sfv+AhKk3~aA~ z%j^?HNz-&wGF4qOL=lm$L!`M-kU)qsRn0_UZ2bV*7{QBssZG5KQ|l0INwtIl(Jn+O z1h;h~wT8v0{$>YDrb=YluSQna=V6z?kM1E07e7DJ@903q&{^pdw;g^Y4h^3HMp|B^i|1X zS6Z<*>+1*Vef2Q>eZ`7h-T$cWwb-kzkxPw*hq(v2=DTyvYoA#ox0~!xUAEZUtx@67 z^Ih_S6uJclcf z#;o}JR_qGd=2!L#in&KUC2?|YGTPr_=#}`{0xz==%i0k&W<+W1DJTah!$VphGhRF8 zq?0mF8C2+)LC*EkD2K3oIEAG-B8u-WIO;IU(HbZC?NGKVsTD_4v|sF~)8VAI^O>7j zckXsJ?H#$JPMV8!87aP9$8Z45uFveelmOguUsW35K#$jb9nbcj@jCXz(wDp^5Su?T zN1gpjK=9-R)@C<#!?f9L9hr7u2lqJ9ksa8U4C71Oq5@~LiYlAv0*-c6LllDI1TJC_ z7~3mX41<(5ieATP`z1ppF?)kOH<;`!4q=8djbR`B)i?NfVB}=s?}Nwz?2uf+ z1>!esQ&-R!@eLE8?MrHK$|W_Et&CL+SaY$Fli2uiY}^smcq=}75}!PdPddt&r1P2M z_>423X}!dM;_H9r;e34C3%CJlIUAB~1TX5xe@-d%u1_E6MPSZ(@UEdtm`85g7{Tz= b%P`FEAo!g1Gaozy7@iMB8U7!DfoA^?JYK5k literal 0 HcmV?d00001 diff --git a/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/app.py b/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/app.py new file mode 100644 index 0000000..aa9ebd2 --- /dev/null +++ b/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/app.py @@ -0,0 +1,240 @@ +import os + +from redis import Redis +from json import dumps, loads +from datetime import datetime +from typing import List, Dict, Any, Union +from email import policy +from email.message import EmailMessage +from email.headerregistry import UniqueDateHeader, UniqueAddressHeader, UniqueUnstructuredHeader +from email.parser import BytesParser +from imaplib import IMAP4_SSL +from config import EmailConfig, Config + + +email_config = EmailConfig() +config = Config() +redis_client = Redis( + host='10.10.2.15', + password='your_strong_password_here', + port=6379, + db=0 +) + +class Mails: + + def __init__(self, mail_id: bytes, mail_data: bytes): + self.id: bytes = mail_id + self.raw_data: bytes = mail_data + self.attachments: List[Dict[str, Union[str, bytes]]] = [] + self.message: EmailMessage = BytesParser(policy=policy.default).parsebytes(mail_data) + self.subject: UniqueUnstructuredHeader = self.message.get('Subject', '') or '' + self.from_: UniqueAddressHeader = self.message.get('From', '') or '' + self.to: UniqueAddressHeader = self.message.get('To', '') or '' + self.date: UniqueDateHeader = self.message.get('Date', '') or '' + self.body_text: str = self._get_body_text() + self._extract_attachments() + + + def to_dict(self) -> Dict[str, Any]: + return { + 'id': self.id.decode('utf-8'), + # 'raw_data': self.raw_data.decode('utf-8'), + 'attachments': [{ + 'filename': attachment['filename'], + 'content_type': attachment['content_type'], + 'charset': attachment['charset'], + 'data': attachment['data'].decode(attachment['charset'], errors='replace') + } for attachment in self.attachments], + # 'message': self.message.as_string(), + 'subject': str(self.subject), + 'from_': { + "display_name": self.from_.addresses[0].display_name, + "username": self.from_.addresses[0].username, + "domain": self.from_.addresses[0].domain, + "mail": f"{self.from_.addresses[0].username}@{self.from_.addresses[0].domain}" + }, + 'to': [ + { + "display_name": address.display_name, + "username": address.username, + "domain": address.domain, + "mail": f"{address.username}@{address.domain}" + } for address in self.to.addresses + ], + 'date': str(self.date.datetime), + 'body_text': str(self.body_text) + } + + def _get_body_text(self) -> str: + body = self.message.get_body(preferencelist=('plain',)) + if body is not None: + return body.get_content() or '' + if self.message.is_multipart(): + for part in self.message.walk(): + if part.get_content_type() == 'text/plain' and (part.get_content_disposition() or '') != 'attachment': + try: + return part.get_content() or '' + except Exception: + payload = part.get_payload(decode=True) or b'' + return payload.decode(part.get_content_charset() or 'utf-8', errors='replace') + else: + if self.message.get_content_type() == 'text/plain': + try: + return self.message.get_content() or '' + except Exception: + payload = self.message.get_payload(decode=True) or b'' + return payload.decode(self.message.get_content_charset() or 'utf-8', errors='replace') + return '' + + def _extract_attachments(self) -> None: + for part in self.message.walk(): + if part.get_content_disposition() == 'attachment': + filename = part.get_filename() + if not filename: + continue + data = part.get_payload(decode=True) or b'' + charset = part.get_charset() or 'utf-8' + self.attachments.append( + {'filename': filename, 'content_type': part.get_content_type(), 'data': data, 'charset': charset} + ) + + def save_attachments(self, folder: str) -> None: + os.makedirs(folder, exist_ok=True) + for att in self.attachments: + with open(os.path.join(folder, att['filename']), 'wb') as f: + f.write(att['data']) + + +class EmailReaderIsbankService: + + NO_ATTACHMENT_FOLDER = "NoAttachment" + COMPLETED_FOLDER = "Completed" + + def __init__(self, email_config: EmailConfig, config: Config): + self.email_config = email_config + self.config = config + self.mail = IMAP4_SSL(email_config.EMAIL_HOST, email_config.EMAIL_PORT) + self.mail.login(email_config.EMAIL_USERNAME, email_config.EMAIL_PASSWORD) + self.data: List[Mails] = [] + self.inc: int = 100 + self.start: int = 0 + self.end: int = self.inc + + self._connect_inbox() + self.mail_count = self._fetch_count() + self._fetch_all() + + + def _connect_inbox(self): + """INBOX'a bağlanır""" + status, _ = self.mail.select("INBOX") + if status != 'OK': + raise Exception("INBOX'a bağlanılamadı") + + def _fetch_count(self): + status, uids = self.mail.uid('SORT', '(REVERSE DATE)', 'UTF-8', 'ALL', 'FROM', f'"{self.config.MAILBOX}"') + if status != 'OK': + raise Exception("Mail sayısı alınamadı") + return len(uids[0].split()) + + def _fetch_all(self): + """Tüm mailleri çeker ve self.data'ya Mails objesi olarak ekler""" + status, uids = self.mail.uid('SORT', '(REVERSE DATE)', 'UTF-8', 'ALL', 'FROM', f'"{self.config.MAILBOX}"') + if status != 'OK': + raise Exception("Mail arama başarısız") + for uid in uids[0].split(): + status, msg_data = self.mail.uid('fetch', uid, '(RFC822)') + if status == 'OK' and msg_data[0] is not None: + self.data.append(Mails(uid, msg_data[0][1])) + + def mark_no_attachment(self, uid: bytes): + """Mesajı arşive taşır""" + self.mail.uid('COPY', uid, self.NO_ATTACHMENT_FOLDER) + self.delete(uid) + + def mark_completed(self, uid: bytes): + """Mesajı arşive taşır""" + self.mail.uid('COPY', uid, self.COMPLETED_FOLDER) + # self.delete(uid) + + def delete(self, uid: bytes): + """Mesajı siler""" + self.mail.uid('STORE', uid, '+FLAGS', r'(\Deleted)') + + def commit(self): + """Bekleyen silme/taşıma işlemlerini uygular""" + self.mail.expunge() + + def logout(self): + self.mail.logout() + + @property + def count(self): + return len(self.data) + +service = EmailReaderIsbankService(email_config, config) +mails = service.data +count = 0 +redis_prefix = "Bank:Services:Task" + +my_service = "mail" +for mail in mails: + if not getattr(mail, 'id', None): + continue + mail_id = mail.id.decode('utf-8') + if mail.attachments: + not_seen = redis_client.sadd(f'{redis_prefix}:Seen', mail_id) + index_of_set_data_get = redis_client.get(f'{redis_prefix}:Index') + if not_seen: + mail_to_dict = mail.to_dict() + mail_without_attachments = mail_to_dict.copy() + mail_without_attachments.pop('attachments', None) + write_object = { + 'id': mail_id, + 'data': { + "mail": mail_without_attachments, + "parser": mail_to_dict.get('attachments', []), + "ibanFinder": None, + "commentFinder": None + }, + 'created_at': datetime.now().isoformat(), + 'completed': True, + 'status': 'red', + 'service': my_service, + # 'bank': 'isbank', + 'is_completed': False + } + redis_write_ = redis_client.rpush(f'{redis_prefix}:Data', dumps(write_object)) + if redis_write_: + if index_of_set_data_get: + index_of_set_data_get = loads(index_of_set_data_get) + index_of_set_data_get[str(mail_id)] = redis_write_ - 1 + else: + index_of_set_data_get = {str(mail_id): redis_write_ - 1} + index_of_set_data_set = redis_client.set(f'{redis_prefix}:Index', dumps(index_of_set_data_get)) + count += 1 + else: + redis_client.spop(f'{redis_prefix}:Seen', mail_id) + else: + get_index = redis_client.get(f'{redis_prefix}:Index') + if not get_index: + continue + get_index = loads(get_index) + if get_index.get(str(mail_id), None): + object_from_redis = redis_client.lindex(f'{redis_prefix}:Data', int(get_index[str(mail_id)])) + if object_from_redis: + object_from_redis = loads(object_from_redis) + is_completed = object_from_redis.get('is_completed', False) + id_ = object_from_redis.get('data', {}).get('id', None) + if not mail_id == id_: + raise Exception("Mail id not match with id from redis") + if is_completed: + service.mark_completed(mail_id) + else: + service.mark_no_attachment(mail_id) + +service.commit() +service.logout() + +print("Total Mails: ", f"{count}/{service.count}") diff --git a/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/config.py b/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/config.py new file mode 100644 index 0000000..51308c0 --- /dev/null +++ b/ServicesRunnner/AccountRecordServices/Reader/Banks/IsBank/config.py @@ -0,0 +1,37 @@ +import os + +class Config: + + MAILBOX: str = os.getenv("MAILBOX", "bilgilendirme@ileti.isbank.com.tr") + EMAIL_HOST: str = os.getenv("EMAIL_HOST", "10.10.2.34") + EMAIL_LOGIN_USER: str = os.getenv("EMAIL_READER_ADDRESS", "isbank@mehmetkaratay.com.tr") + EMAIL_LOGIN_PASSWORD: str = os.getenv("EMAIL_LOGIN_PASSWORD", "system") + AUTHORIZE_IBAN: str = os.getenv("AUTHORIZE_IBAN", "4245-0093333") + EMAIL_PORT: int = int(os.getenv("EMAIL_PORT", 993)) + + +class EmailConfig: + + EMAIL_HOST: str = Config.EMAIL_HOST + EMAIL_USERNAME: str = Config.EMAIL_LOGIN_USER + EMAIL_PASSWORD: str = Config.EMAIL_LOGIN_PASSWORD + EMAIL_PORT: int = Config.EMAIL_PORT + + @classmethod + def as_dict(cls): + return dict( + host=EmailConfig.EMAIL_HOST, + port=EmailConfig.EMAIL_PORT, + username=EmailConfig.EMAIL_USERNAME, + password=EmailConfig.EMAIL_PASSWORD + ) + + +# INFO_MAIL: str = os.getenv("INFO_MAIL", "mehmet.karatay@hotmail.com") +# EMAIL_SEND: bool = bool(os.getenv("EMAIL_SEND", False)) +# EMAIL_SEND_PORT: int = int(os.getenv("EMAIL_SEND_PORT", 587)) +# EMAIL_SLEEP: int = int(os.getenv("EMAIL_SLEEP", 60)) +# SERVICE_TIMING: int = int(os.getenv("SERVICE_TIMING", 900)) +# EMAIL_LOGIN_USER: str = os.getenv("EMAIL_LOGIN_USER", "karatay@mehmetkaratay.com.tr") +# MAIN_MAIL: str = os.getenv("MAIN_MAIL", "karatay.berkay@gmail.com") +# EMAIL_SEND: bool = Config.EMAIL_SEND