File size: 5,794 Bytes
6925123
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
#!/usr/bin/env python3
"""patch_missing_defs.py — insert missing `_bstate_log` into app.py.

Fixes (verified from live build log):
  NameError: name '_bstate_log' is not defined  (app.py:5574)
  -> every POST /webhooks returned 500 Internal Server Error, so the main
  bot NEVER replied and webhook tests (webhook.test) failed.

Strategy (same proven pattern as patch_card_strings.py):
  - Fixes the LOCAL on-disk file first (running app uses the fix NOW).
  - If anything changed, commits the fixed file back to THIS Space's repo
    (SELF_REPO_ID env) -> repo converges after ONE rebuild, no loop.
  - Also installs a PEP 562 module-level __getattr__ that auto-provides any
    other missing `_bstate*` helper (fail-open) instead of NameError.
  - Idempotent & fail-open: never blocks the app from starting.
"""
import os
import sys
import tempfile
import pathlib

FILES = ["app.py"]

_FIX_BLOCK = '''def _bstate_log(state):
    """Log a bot-state event dict. Fail-open: never raises.

    Called at the top of handle_webhook; if missing, the app crashes with
    NameError and every webhook returns 500. Mirrors the child-template log
    semantics: append to in-memory _logs + print a [WEBHOOK] line so the
    main.py journal bridge persists it to the registry dataset.
    """
    try:
        if not isinstance(state, dict):
            state = {"data": str(state)}
        # Redact anything sensitive before persisting
        _red = {}
        for _k, _v in state.items():
            if re.search(r"token|secret|key|hash|password", str(_k), re.I):
                _red[str(_k)] = "***"
            else:
                _red[str(_k)] = _v
        _entry = {
            "event": str(state.get("event", "bstate"))[:40],
            "sender_id": str(state.get("sender_id", state.get("sender", "system")) or "system"),
            "sender_name": str(state.get("sender_name", "") or ""),
            "chat_id": str(state.get("chat_id", state.get("chat", "SYSTEM")) or "SYSTEM"),
            "chat_type": str(state.get("chat_type", "") or ""),
            "text": json.dumps(_red, ensure_ascii=False)[:500],
            "is_zgr": bool(state.get("is_zgr", False)),
            "time": time.strftime("%Y-%m-%d %H:%M:%S"),
        }
        try:
            _logs.append(_entry)
            if len(_logs) > 200:
                del _logs[:100]
        except Exception:
            pass
        print("[WEBHOOK] event=" + str(_entry["event"]) + " sender=" + str(_entry["sender_id"]) +
              " chat=" + str(_entry["chat_id"]) + " is_zgr=" + str(_entry["is_zgr"]) +
              " text=" + str(_entry["text"])[:100], flush=True)
    except Exception:
        pass


_BSTATE_STATE = {}


def _bstate_get(key, default=None):
    """Read one key from the in-memory bot-state dict (fail-open)."""
    try:
        return _BSTATE_STATE.get(key, default)
    except Exception:
        return default


def _bstate_set(key, value):
    """Store one key/value in the in-memory bot-state dict (fail-open)."""
    try:
        _BSTATE_STATE[key] = value
    except Exception:
        pass


def __getattr__(name):
    """PEP 562: auto-provide any OTHER missing `_bstate_*` helper as a no-op
    logger so one forgotten call site can never 500 the webhook again.
    All other undefined names still raise AttributeError -> real bugs stay
    visible.
    """
    if name.startswith("_bstate"):
        return _bstate_log
    raise AttributeError(name)


'''


def main():
    rc = 0
    try:
        repo_id = os.getenv("SELF_REPO_ID", "").strip()
        token = os.getenv("HF_TOKEN", os.getenv("HF_API_TOKEN", "")).strip()
        changed = False
        for fname in FILES:
            if not os.path.exists(fname):
                continue
            with open(fname, encoding="utf-8") as f:
                content = f.read()
            if "def _bstate_log(" in content:
                print("[patch-defs] already fixed:", fname, flush=True)
                continue
            anchor = "def handle_webhook("
            idx = content.find(anchor)
            if idx < 0:
                anchor = "async def handle_webhook("
                idx = content.find(anchor)
            if idx < 0:
                print("[patch-defs] WARNING: handle_webhook anchor not found in", fname, flush=True)
                continue
            content = content[:idx] + _FIX_BLOCK + content[idx:]
            with open(fname, "w", encoding="utf-8") as f:
                f.write(content)
            print("[patch-defs] fixed locally:", fname, flush=True)
            changed = True
            if repo_id and token:
                try:
                    from huggingface_hub import HfApi
                    api = HfApi(token=token)
                    with tempfile.TemporaryDirectory() as tmpdir:
                        tmp = pathlib.Path(tmpdir)
                        (tmp / fname).write_text(content, encoding="utf-8")
                        api.upload_folder(
                            repo_id=repo_id,
                            repo_type="space",
                            folder_path=str(tmp),
                            token=token,
                            commit_message="autofix: define missing _bstate_log in app.py",
                        )
                    print("[patch-defs] committed", fname, "->", repo_id, flush=True)
                except Exception as e:
                    print("[patch-defs] commit failed (local fix still applied):", str(e)[:200], flush=True)
        if not changed:
            print("[patch-defs] nothing to do — exiting clean", flush=True)
    except Exception as e:
        print("[patch-defs] FATAL (ignored, fail-open):", str(e)[:200], flush=True)
        rc = 0
    return rc


if __name__ == "__main__":
    sys.exit(main())