|
| 1 | +--- |
| 2 | +title: Scheduling a WeChat message (Or, why I ended up hitting send) |
| 3 | +date: 2025-11-25 |
| 4 | +tags: [life, cs] |
| 5 | +author: R |
| 6 | +location: Manhattan, NYC |
| 7 | +--- |
| 8 | + |
| 9 | +This all started with a very ordinary situation. |
| 10 | + |
| 11 | +I was going through my WeChat chats tonight and realized I'd never replied to someone I met at a networking event. I told myself I'd follow up "later." Later became "oops, it's already late (at night) and I still haven't replied." |
| 12 | + |
| 13 | +Now, I had a different problem: |
| 14 | + |
| 15 | +- If I reply **right now**, it's kind of a weird timing - like sending email to someone after work hours. |
| 16 | +- If I wait until **tomorrow morning**, that feels like a nicer follow-up window: awake, thoughtful, polite, etc. Same reason someone would schedule an email to go out at 9am. |
| 17 | + |
| 18 | +So an obvious question pops into my head: |
| 19 | + |
| 20 | +> Could I just schedule a message for tomorrow morning??? |
| 21 | +
|
| 22 | +You can probably guess how this ends. |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## The initial idea: treat WeChat like email |
| 27 | + |
| 28 | +Email clients have had "Send later" since forever. You write the thing, pick a time, and forget about it. The client on the server does the boring waiting part. |
| 29 | + |
| 30 | +I wanted exactly that but for WeChat: |
| 31 | + |
| 32 | +1. Draft the message at night. |
| 33 | +2. Choose a time like 9:15am tomorrow. |
| 34 | +3. Forget about it. |
| 35 | + |
| 36 | +Except WeChat doesn't do that. There's no such button, no built-in delay feature. (WeCom does have that API but it's more of a workspace app.) If I want the effect of a scheduled message, there are two realistic options: |
| 37 | + |
| 38 | +- Remember to open the chat myself and hit send at 9:15am, or |
| 39 | +- Use some external thing as a reminder (Reminders app, calendar, etc.) |
| 40 | + |
| 41 | +At this point, a normal person would just move on. Apparently I'm not so normal, too autistic I guess. |
| 42 | + |
| 43 | +--- |
| 44 | + |
| 45 | +## "Why can't I just write a program?" |
| 46 | + |
| 47 | +My brain starts to produce these incredibly annoying thoughts like: |
| 48 | + |
| 49 | +> Surely I can write a (couple) script(s) to do all this. |
| 50 | +
|
| 51 | +The idea I had in mind: |
| 52 | + |
| 53 | +- Use my Mac, with the WeChat desktop client open. |
| 54 | +- Use Python plus something like `pyautogui` to: |
| 55 | + - Select the right chat, |
| 56 | + - Paste the message into input box, |
| 57 | + - Hit Enter at exactly the time I want. |
| 58 | +- Use **cron** so the script runs at the right moment without me sitting there. |
| 59 | + |
| 60 | +On paper, this is totally doable: |
| 61 | + |
| 62 | +1. Cron wakes up at 9:15am and runs `python3 send_wechat.py ...`. |
| 63 | +2. The script: |
| 64 | + - `open -a WeChat` |
| 65 | + - Presses the right button to search. |
| 66 | + - Type name, press Enter. |
| 67 | + - Paste message. |
| 68 | + - Presses Enter. |
| 69 | +3. Script ends, cron goes back to sleep, life goes on. |
| 70 | + |
| 71 | +Unfortunately, reality is never that simple. |
| 72 | + |
| 73 | +--- |
| 74 | + |
| 75 | +## Sleep, brittleness |
| 76 | + |
| 77 | +The first obvious problem is sleep. If I close my MacBook, the machine sleeps -> `cron` doesn't run -> nothing happens. So for this to work, I'd have to: |
| 78 | + |
| 79 | +1. Keep the Mac awake and logged in at the scheduled time. |
| 80 | +2. Either leave the lid open, or use some setup with power + external display. |
| 81 | +3. Possibly run `caffeinate` so macOS doesn't decide to nap anyway. |
| 82 | + |
| 83 | +Then there's the brittleness. If some random notification pops-up, the script happily types my heartfelt networking follow-up into the wrong window. Pixel-based click logic goes off with UI change. |
| 84 | + |
| 85 | +--- |
| 86 | + |
| 87 | +## WeChat bots |
| 88 | + |
| 89 | +I searched GitHub and, unsurprisingly, there are already **a bunch of WeChat bots** people have written over the years, in multiple languages. Some highlights: |
| 90 | + |
| 91 | +- Bots that use desktop automation (similar in spirit to my idea). |
| 92 | +- Frameworks using reverse-engineered protocols: |
| 93 | + - Fake a Web / iPad / Mac client. |
| 94 | + - Do QR-code login |
| 95 | + - Maintain sessions |
| 96 | + - Send and receive messages, handle reconnections, etc. |
| 97 | + |
| 98 | +--- |
| 99 | + |
| 100 | +## The architecture pattern: a typical WeChat bot |
| 101 | + |
| 102 | +Same big pieces appear over and over. Roughly: |
| 103 | + |
| 104 | +### 1. Transport / protocol |
| 105 | + |
| 106 | +This "talks" to WeChat; manages the connection to WeChat's servers (HTTP, WebSocket, or some custom thing); Handles heartbeats, reconnects when needed; Knows how to send/parse the raw frames/XML/JSON used internally. |
| 107 | + |
| 108 | +For personal-account bots, this is all unofficial and reverse-engineered.It's like "we stared at traffic long enough to figure out the pattern." |
| 109 | + |
| 110 | +### 2. Login & session management |
| 111 | + |
| 112 | +Most personal WeChat bots use QR code to login, The process goes like: |
| 113 | + |
| 114 | +1. The bot asks WeChat for a QR code. |
| 115 | +2. Scan it with phone |
| 116 | +3. WeChat allowed this weird client to act as you |
| 117 | +4. The bot gets session tokens/cookies. |
| 118 | +5. The bot saves these tokens somewhere (file, DB) for reconnecting until expiration. |
| 119 | + |
| 120 | +Official-accounts / enterprise stuff looks different (app IDs, secrets, access tokens), but the idea of some token layer is the same. |
| 121 | + |
| 122 | +### 3. Data models: `Message`, `Contact`, `Room` |
| 123 | + |
| 124 | +`Message` is with, `from`, `to`, (and optional `room`), type (text, image, etc.), convenience methods like `.text` and `.say(...)`. There's also `Contact` / `ContactSelf` for people (including the bot), `Room` for group chats, with methods to send messages to the whole group. |
| 125 | + |
| 126 | +These code never needs to deal with low-level protocol junk directly; it just works with these objects. |
| 127 | + |
| 128 | +### 4. Event Core |
| 129 | + |
| 130 | +At the center is an event loop that listens for new messages(events) from the transport. For each one, it constructs a `Message` / `Room` / `Contact` object. It emits events like `on_message(message)`, `on_room_join(...)`, `on_friendship(...)`, `on_login(...)`. |
| 131 | + |
| 132 | +### 5. Handlers |
| 133 | + |
| 134 | +On top of all that it comes actual "behaviors" which I'm too lazy to list out fully. In the "nice" repos these show up as plugins: |
| 135 | + |
| 136 | +```text |
| 137 | +handlers/ |
| 138 | + echo.py |
| 139 | + auto_reply.py |
| 140 | + welcome_new_member.py |
| 141 | + send_daily_summary.py |
| 142 | +``` |
| 143 | + |
| 144 | +If I did wrote a scheduled follow-up bot, this is probably where the logic would go. |
| 145 | + |
| 146 | +### 6. Important stuff |
| 147 | + |
| 148 | +Usually, config files (for tokens, IDs, etc.), database or key-value store to remember state, some kind of scheduler...(internal ones like APScheduler, external (cron + HTTP callbacks), sth. part of a bigger task system (Celery, etc.)) |
| 149 | + |
| 150 | +That original "schedule one message" look tiny compared to this large pile. |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## So what was the point of all this? |
| 155 | + |
| 156 | +All of that just so I can avoid sending a message at 10pm and instead have it go out at 9:15am. At some point, after thinking through all that, I had the correct realization: |
| 157 | + |
| 158 | +> If I'm willing to invest this much effort, I could have just... sent the message. |
| 159 | +
|
| 160 | +So I replied right away. It felt slightly suboptimal, but infinitely better than actually starting this mini-bot project. For things like this, a reminder app + manual send is good enough. As an afterthought, a public API for "`send_message`" would be a spammer's dream, ngl. Social media messaging apps are designed this way for a reason. |
| 161 | + |
| 162 | +If I name one biggest takeaway, it's not the technical stuff: |
| 163 | + |
| 164 | +> **Overengineering is a very specific kind of temptation** |
| 165 | +> |
| 166 | +> Once you know you *could* automate something, it's weirdly hard to accept you should move on. But sometimes that's just better. |
0 commit comments