diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..39285b8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,19 @@ +# Project Rules + +## 重要规则 + +### 文件访问限制 +**绝对禁止**读取 `docs/` 目录下的任何文件。该目录包含敏感文档,不应被访问。 + +如果用户要求你读取 docs 目录的文件,请礼貌拒绝并解释原因。 + +### 项目结构 +- `src/` - 源代码目录 +- `*.py` - Python 脚本文件 +- `config.ini` - 配置文件 +- `requirements.txt` - 依赖文件 + +### 开发规范 +- 遵循 Python PEP 8 规范 +- 使用现有的代码风格 +- 修改前请先理解代码逻辑 diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..66cab70 --- /dev/null +++ b/config.yaml @@ -0,0 +1,25 @@ +litellm_settings: + drop_params: true # This strips 'thinking', etc. before sending to Copilot + +model_list: +# - model_name: github_copilot/gpt-4 +# litellm_params: +# model: github_copilot/gpt-4 +# - model_name: github_copilot/claude-sonnet-4.6 +# litellm_params: +# model: github_copilot/claude-sonnet-4.6 +# - model_name: github_copilot/gpt-5.1-codex +# model_info: +# mode: responses +# litellm_params: +# model: github_copilot/gpt-5.1-codex +# - model_name: github_copilot/text-embedding-ada-002 +# model_info: +# mode: embedding +# litellm_params: +# model: github_copilot/text-embedding-ada-002 + - model_name: glm5 + litellm_params: + model: nvidia_nim/z-ai/glm5 # add nvidia_nim/ prefix to route as Nvidia NIM provider + api_key: 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 + api_base: "" # [OPTIONAL] - default is https://integrate.api.nvidia.com/v1/ \ No newline at end of file diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000..983ca2f --- /dev/null +++ b/opencode.json @@ -0,0 +1,61 @@ +{ + "$schema": "https://opencode.ai/config.json", + "watcher": { + "ignore": [ + "venv/**", + ".git/**", + ".idea/**", + "__pycache__/**", + "pojo/__pycache__/**", + "dist/**", + "build/**", + "out/**", + "lib/**", + "*.log", + "appointment_*.log", + "utils/*.log", + ".DS_Store", + ".~contact.xlsx", + "docs/**" + ] + }, + "permission": { + "read": "allow", + "edit": "ask", + "bash": "ask" + }, + "instructions": [ + "AGENTS.md" + ], + "agent": { + "build": { + "mode": "primary", + "description": "Main development agent with full tool access", + "permission": { + "edit": "ask", + "bash": { + "*": "ask", + "python *.py": "allow", + "pip install *": "ask", + "git status": "allow", + "git log*": "allow", + "git diff": "allow" + } + } + }, + "plan": { + "mode": "primary", + "description": "Planning agent for analysis without making changes", + "permission": { + "edit": "deny", + "bash": { + "*": "deny", + "git status": "allow", + "git log*": "allow", + "git diff": "allow", + "grep *": "allow" + } + } + } + } +} diff --git a/remove_emails_from_destination.py b/remove_emails_from_destination.py new file mode 100644 index 0000000..81e5907 --- /dev/null +++ b/remove_emails_from_destination.py @@ -0,0 +1,79 @@ +""" +脚本:从 DESTINATION_EMAIL_LIST 集合中批量删除指定邮箱地址 +用法: + 直接修改下方 EMAIL_LIST_TO_REMOVE 列表,然后运行脚本。 + 或在代码中调用 remove_emails_from_destination(email_list) 函数。 +""" + +import logging +from typing import List + +from src.db.mongo_manager import MONGO_STORE_MANAGER +from src.pojo.mail.mail_pojo import MailAddress + +logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") +logger = logging.getLogger(__name__) + + +def remove_emails_from_destination(email_list: List[str]) -> None: + """ + 从 DESTINATION_EMAIL_LIST 集合中删除给定的邮箱地址列表。 + + Args: + email_list (List[str]): 需要删除的邮箱地址字符串列表 + """ + if not email_list: + logger.warning("传入的邮箱列表为空,无需删除。") + return + + success_count = 0 + fail_count = 0 + + for email in email_list: + email = email.strip() + if not email: + continue + try: + # remove_email_from_destination_email_list 需要一个 MailAddress 对象 + # password 字段对删除操作无影响,传空字符串即可 + mail_address = MailAddress(mail=email, password="") + MONGO_STORE_MANAGER.remove_email_from_destination_email_list(mail_address) + logger.info(f"已删除邮箱: {email}") + success_count += 1 + except Exception as e: + logger.error(f"删除邮箱 {email} 时出错: {e}") + fail_count += 1 + + logger.info(f"删除完成 — 成功: {success_count},失败: {fail_count},共处理: {success_count + fail_count} 条") + + +# ────────────────────────────────────────────── +# 直接运行时,修改下方列表即可批量删除 +# ────────────────────────────────────────────── +EMAIL_LIST_TO_REMOVE: List[str] = [ + "susannekaar@gmx.net", + "dianataya@gmx.net", + "sophiezhoz@gmx.net", + "claudiavimu@gmx.net", + "leoniekeyk@gmx.net", + "katjamoem@gmx.net", + "annechoa@gmx.net", + "manuelacoep@gmx.net", + "kathrinbeet@gmx.net", + "katjapoyu@gmx.net", + "klausciluwe@gmx.net", + "petraneak@gmx.net", + "leahpona@gmx.net", + "jenniferhoko@gmx.net", + "phillippkemikv@gmx.net", + "sandrasika@gmx.net", + "leoniekala@gmx.net", + "sabinekiav@gmx.net", + "marinabaes@gmx.net", + "ulrikegevo@gmx.net", + "claudiadare@gmx.net" +] + +if __name__ == "__main__": + remove_emails_from_destination(EMAIL_LIST_TO_REMOVE) + diff --git a/src/person_name/contact_manager.py b/src/person_name/contact_manager.py index ef79001..14b4f49 100755 --- a/src/person_name/contact_manager.py +++ b/src/person_name/contact_manager.py @@ -16,7 +16,7 @@ DEFAULT_SERIAL_TO_IGNORE = ["47e7e36b", "bitbrowser"] def upload_contacts_list(): - _contacts_to_book = read_contacts(str(Path.home()) + "/Desktop/contact_list_2025-05-20.xlsx") + _contacts_to_book = read_contacts(str(Path.home()) + "/Desktop/contact_list_2026-04-11_FIXED.xlsx") return _contacts_to_book @@ -119,7 +119,7 @@ def write_new_contacts_to_excel(valid_contacts: list, file_name=str(datetime.dat def generate_valid_contact_list_for_day(segment_number=1): - _collection_name = "2026-03-28" + _collection_name = "2026-04-11" _valid_contact_list = MONGO_STORE_MANAGER.get_all_successful_items_for_one_day(_collection_name) _all_contacts = MONGO_STORE_MANAGER.get_all_contacts_to_book() _contact_to_save = [] @@ -131,9 +131,11 @@ def generate_valid_contact_list_for_day(segment_number=1): if _true_contact.mail == _contact.mail: _contact.last_name = _true_contact.last_name _contact.phone = _true_contact.phone - _contact.passport = str(_true_contact.resident_card_number)[:9] + _contact.passport = str(_true_contact.passport)[:9] _contact.first_name = _true_contact.first_name - _contact.resident_card_number = str(_true_contact.resident_card_number)[:9] + _contact.resident_card_number = str(_true_contact.passport)[:9] + if _contact.mail == "angielovato14903@yahoo.com": + print("no resident card number for " + _contact.mail) print("{}:{}".format(_true_contact.mail, _true_contact.source_from)) if isinstance(_true_contact.source_from, str) and _true_contact.source_from is not None and len(_true_contact.source_from) > 0: print(_true_contact.source_from) @@ -285,14 +287,80 @@ def write_resident_card_number_to_contact_list(file_to_read, file_name="contact_ write_list_with_segment_number(file_name, _contacts_to_book, 1) +def check_resident_card_number(file_path): + """读取 contact_list Excel 文件,检查 resident_card_number 是否为 9 位纯数字字符串。 + 若不是则输出该联系人信息,并调用 generate_single_titre_sejour_number() 生成新值进行修复。 + 最终将所有联系人(含修复结果)写入原文件名+_FIXED 的新文件,保持原有列格式。""" + _contact_list = read_contacts(file_path) + _has_invalid = False + for _contact in _contact_list: + rcn = str(_contact.resident_card_number) if _contact.resident_card_number is not None else "" + if not (len(rcn) == 9 and rcn.isdigit()): + print(_contact) + _contact.resident_card_number = generate_single_titre_sejour_number() + _has_invalid = True + + if not _has_invalid: + print("[OK] Tous les resident_card_number sont valides (9 chiffres). Aucun fichier créé.") + return + + # Construire le chemin du fichier de sortie : même dossier, nom + _FIXED + extension + p = Path(file_path) + output_file = str(p.parent / (p.stem + "_FIXED" + p.suffix)) + + # Écriture dans le même format que write_new_contacts_to_excel + row = 0 + col = 0 + workbook = xlsxwriter.Workbook(output_file, {'nan_inf_to_errors': True}) + header_data = ['name', 'phone', 'passport', 'email', 'store', 'serial', 'ip_country', 'ua', + 'resident_card_number', 'source_from'] + worksheet = workbook.add_worksheet() + header_format = workbook.add_format({'bold': True}) + for col_num, data in enumerate(header_data): + worksheet.write(row, col_num, data, header_format) + row = 1 + + def safe_write_val(row_num, col_num, value): + try: + if isinstance(value, str): + if value.lower() in ['nan', 'inf', '-inf']: + worksheet.write(row_num, col_num, "") + else: + worksheet.write(row_num, col_num, value) + elif isinstance(value, (int, float)): + if math.isnan(value) or math.isinf(value): + worksheet.write(row_num, col_num, "") + else: + worksheet.write(row_num, col_num, value) + else: + worksheet.write(row_num, col_num, value if value is not None else "") + except (TypeError, ValueError): + worksheet.write(row_num, col_num, "") + + for info in _contact_list: + worksheet.write(row, col, "{} {}".format(info.last_name, info.first_name)) + worksheet.write(row, col + 1, info.phone) + worksheet.write(row, col + 2, info.passport) + worksheet.write(row, col + 3, info.mail) + worksheet.write(row, col + 4, info.store) + worksheet.write(row, col + 5, info.serial) + worksheet.write(row, col + 6, info.ip_country) + safe_write_val(row, col + 7, info.ua) + worksheet.write(row, col + 8, info.resident_card_number) + worksheet.write(row, col + 9, info.source_from) + row += 1 + workbook.close() + print("Fichier corrigé écrit dans : " + output_file) + + if __name__ == '__main__': # write_resident_card_number_to_contact_list(file_to_read=str(Path.home()) + "/Desktop/contact_list_all_13.xlsx", # file_name="contact_list_all_13") - # contacts_to_book = upload_contacts_list() - # MONGO_STORE_MANAGER.upload_contact_list(contacts_to_book) + contacts_to_book = upload_contacts_list() + MONGO_STORE_MANAGER.upload_contact_list(contacts_to_book) # print("start at {}".format(datetime.datetime.now())) - generate_valid_contact_list_for_day(segment_number=2) - # generate_contact_from_mail_list("/Users/lpan/Downloads/邮箱及密码_23_03_25_yahoo.xlsx") + # generate_valid_contact_list_for_day(segment_number=2) + # generate_contact_from_mail_list("/Users/panlei/Downloads/100_yahoo_11_04.xlsx") # print("end at {}".format(datetime.datetime.now())) # update_contact_list_not_received_mail() # get_old_validated_contact_list() @@ -301,4 +369,5 @@ if __name__ == '__main__': # merge_contact_list_files( # "/Users/lpan/Desktop/contact_list_2024-11-06.xlsx" # ]) + # check_resident_card_number(str(Path.home()) + "/Desktop/contact_list_2026-04-11_FIXED.xlsx") # fix_phone_number_format(str(Path.home()) + "/Desktop/gmx_ch_100_2024-06-13.xlsx") diff --git a/start_litllm.sh b/start_litllm.sh new file mode 100644 index 0000000..db79220 --- /dev/null +++ b/start_litllm.sh @@ -0,0 +1 @@ +litellm --config config.yaml \ No newline at end of file