**Motivations:** - Consume canonical imap_common from lecoffre_ng_test; align Gitea token resolution. **Root causes:** - IMAP logic duplicated under ia_dev only; token path ignored LeCoffre central tree. **Correctives:** - mail_common imports imap_common via LECOFFRE_REPO_ROOT or sibling lecoffre_ng_test; lib.sh token fallback order updated. **Evolutions:** - GIT_ISSUES_SCRIPTS_AGENTS.md documents new secret paths and centralize script. **Page affectées:** - git-issues/mail_common.py, git-issues/lib.sh, git-issues/mail-to-issue.py, projects/ia_dev/docs/GIT_ISSUES_SCRIPTS_AGENTS.md
112 lines
3.7 KiB
Python
112 lines
3.7 KiB
Python
# Shared config and helpers for git-issues mail scripts (Gitea + re-exported IMAP/SMTP from LeCoffre).
|
|
# IMAP/SMTP implementation: lecoffre_ng_test/automation/imap-bridge/imap_common.py
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
from pathlib import Path
|
|
from urllib.error import HTTPError, URLError
|
|
from urllib.request import Request, urlopen
|
|
|
|
|
|
def _imap_bridge_python_dir() -> Path:
|
|
if os.environ.get("LECOFFRE_REPO_ROOT"):
|
|
p = Path(os.environ["LECOFFRE_REPO_ROOT"]).expanduser().resolve() / "automation" / "imap-bridge"
|
|
if (p / "imap_common.py").is_file():
|
|
return p
|
|
here = Path(__file__).resolve().parent
|
|
ia_dev_root = here.parent
|
|
sibling = ia_dev_root.parent / "lecoffre_ng_test" / "automation" / "imap-bridge"
|
|
if (sibling / "imap_common.py").is_file():
|
|
return sibling
|
|
raise ImportError(
|
|
"imap_common not found: set LECOFFRE_REPO_ROOT to the LeCoffre monorepo root "
|
|
"or clone lecoffre_ng_test beside ia_dev (../lecoffre_ng_test/automation/imap-bridge/imap_common.py)."
|
|
)
|
|
|
|
|
|
_pkg = _imap_bridge_python_dir()
|
|
if str(_pkg) not in sys.path:
|
|
sys.path.insert(0, str(_pkg))
|
|
|
|
from imap_common import (
|
|
MAIL_SINCE_DATE_DEFAULT,
|
|
imap_search_criterion_all,
|
|
imap_search_criterion_unseen,
|
|
imap_since_date,
|
|
imap_ssl_context,
|
|
load_imap_config,
|
|
load_smtp_config,
|
|
)
|
|
|
|
|
|
def repo_root() -> Path:
|
|
env_root = os.environ.get("REPO_ROOT")
|
|
if env_root:
|
|
return Path(env_root).resolve()
|
|
issues_dir = os.environ.get("GIT_ISSUES_DIR")
|
|
if issues_dir:
|
|
return Path(issues_dir).resolve().parent
|
|
return Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def _lecoffre_automation_token_path() -> Path | None:
|
|
lr = os.environ.get("LECOFFRE_REPO_ROOT", "").strip()
|
|
if lr:
|
|
p = Path(lr).expanduser().resolve() / ".secrets" / "automation" / "git-issues" / "token"
|
|
return p if p.is_file() else None
|
|
sib = repo_root().parent / "lecoffre_ng_test" / ".secrets" / "automation" / "git-issues" / "token"
|
|
return sib if sib.is_file() else None
|
|
|
|
|
|
def load_gitea_config() -> dict[str, str]:
|
|
root = repo_root()
|
|
token = os.environ.get("GITEA_TOKEN")
|
|
if not token:
|
|
lt = _lecoffre_automation_token_path()
|
|
if lt is not None:
|
|
token = lt.read_text(encoding="utf-8").strip()
|
|
if not token:
|
|
for sub in ("git-issues", "gitea-issues"):
|
|
token_path = root / ".secrets" / sub / "token"
|
|
if token_path.is_file():
|
|
token = token_path.read_text(encoding="utf-8").strip()
|
|
break
|
|
return {
|
|
"api_url": os.environ.get("GITEA_API_URL", "https://git.4nkweb.com/api/v1").rstrip("/"),
|
|
"owner": os.environ.get("GITEA_REPO_OWNER", "4nk"),
|
|
"repo": os.environ.get("GITEA_REPO_NAME", "lecoffre_ng"),
|
|
"token": token or "",
|
|
}
|
|
|
|
|
|
def sanitize_title(raw: str, max_len: int = 200) -> str:
|
|
one_line = re.sub(r"\s+", " ", raw).strip()
|
|
return one_line[:max_len] if one_line else "(no subject)"
|
|
|
|
|
|
def create_gitea_issue(title: str, body: str) -> dict | None:
|
|
gitea = load_gitea_config()
|
|
if not gitea["token"]:
|
|
return None
|
|
url = f"{gitea['api_url']}/repos/{gitea['owner']}/{gitea['repo']}/issues"
|
|
payload = json.dumps({"title": title, "body": body}).encode("utf-8")
|
|
req = Request(
|
|
url,
|
|
data=payload,
|
|
method="POST",
|
|
headers={
|
|
"Accept": "application/json",
|
|
"Content-Type": "application/json",
|
|
"Authorization": f"token {gitea['token']}",
|
|
},
|
|
)
|
|
try:
|
|
with urlopen(req, timeout=30) as resp:
|
|
return json.loads(resp.read().decode("utf-8"))
|
|
except (HTTPError, URLError):
|
|
return None
|