Self-hosted service
unmev/douyin-auto-fire avatar
unmev/douyin-auto-fire

douyin-auto-fire keeps a streak alive with Playwright, not a private API

Douyin Auto Spark Renewal - a GitHub Actions-based tool that automatically keeps Douyin 'sparks' alive, supporting scheduled text and native emoji sending, no server required.

371 stars1,051 forksPythonNOASSERTION

At a glance

What is it?
A Python script that opens Douyin in a real Chromium session through Playwright and sends scheduled messages so a chat streak does not expire. It runs from GitHub Actions, Docker, systemd or a Windows task, and its sharpest edges are the login credential model and the account limits the README states outright.
Who is it for?
This suits one person keeping their own streaks from lapsing, and it does that without an account API because it drives the web interface instead. It does not suit bulk messaging, since the Actions route caps at five Douyin accounts and the same account must not run two tasks at once.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 30 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 11, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Playwright drives Chromium because Douyin exposes no send API to it

The whole tool is a browser automation script. Playwright launches Chromium, signs in with a stored credential, opens the conversation and types the message, so the project depends on the web interface rather than on an undocumented endpoint. That choice sets the ceiling on reliability. The pages Douyin serves have to keep the same structure, and the maintenance note says exactly that: when the Douyin web structure changes, the automation has to be adapted in step.

The stated runtime is Python 3.11 or newer, and the dependency list is short:

text
playwright>=1.54,<2
python-dotenv>=1.1,<2
tzdata>=2025.2

Browser binaries are not bundled with the Python packages. The container build fetches them in a separate step, which is why the Docker route needs nothing else on the host:

bash
pip install -r requirements.txt
python -m playwright install --with-deps chromium

Message types go beyond plain text. Images in PNG, JPG, GIF and WebP are supported, and so are the native emoji that Douyin offers inside its own composer, which a raw text sender cannot produce. Several candidate messages can be configured and one is picked at random per run, and the script also randomises the send interval and the typing rhythm so the session does not look mechanical.

Dry Run stops at friend lookup so a broken selector costs nothing

The first-run advice is specific: configure one account, one friend and one text message, run Dry Run, and only then send for real. Dry Run is not a mock. It performs the login and the work of locating the target contact, then stops before anything is transmitted, so it verifies the two parts that break first, namely the credential and the friend selector.

The same caution repeats after any edit. Change a friend, a message or an emoji entry, run Dry Run again before the next scheduled run, because a scheduled task will not pause to ask. Notification of the outcome goes to a DingTalk robot or to a generic webhook endpoint, and the generic route covers WeCom, Feishu, Slack, Discord and Telegram. Both are optional, and leaving them unset does not affect sending.

The webhook path is the more configurable of the two. Request headers can be supplied as JSON or as key=value pairs, and the body can be a custom JSON template with placeholders for task_id, mode, status, success_count, failed_count, total_count, timestamp and results_json. That last placeholder is what lets a run report per-contact outcomes rather than a single pass or fail.

The cookie file is the login, and it is the widest exposure in the design

Authentication is either a cookie value or a Playwright Storage State file, and the project treats both as credentials worth guarding. The warning is blunt: keep them somewhere safe, never commit them to a public repository, never share them. A cookie that grants access to a logged in Douyin session is close to a password.

The Windows route is the one that leans on Storage State, because the project ships a QR code login script that produces storage-state.json directly. Other setups can point DOUYIN_COOKIE at a file instead. The two are alternatives, not a pair, and the example environment file shows both shapes side by side with one commented out.

The container layout takes that seriously in a small way. The cookie file is bind mounted read only, as is the task config and the config directory, while only the artifacts directory is mounted writable:

yaml
volumes:
  - ./config.json:/app/config.json:ro
  - ./cookie.json:/data/cookie.json:ro
  - ./config:/app/config:ro
  - ./artifacts:/app/artifacts

Screenshots, logs and Playwright Traces land in the artifacts directory after a failure, which is useful for diagnosis and also worth knowing before sharing a failed run, since a trace records what the browser did.

Four ways to schedule it, each with a different ceiling

The project offers four deployment routes and recommends Actions for most people, since it needs no server and no always-on machine. Docker suits anyone who already runs a Linux server or a NAS, a systemd timer covers ordinary Linux VPS installs, and the Windows route uses Task Scheduler with the QR login script for convenience.

Each carries a real cost. Actions is bounded, and the stated limit is five Douyin accounts, which comes from the platform the schedule runs on rather than from this project. Docker pushes the burden onto a host you already maintain. systemd means writing unit files and checking the timer yourself. Windows means a machine that stays on.

The compose file defaults are worth reading before the first run, since they define what happens when you change nothing:

yaml
TZ: ${TZ:-Asia/Shanghai}
CRON_SCHEDULE: ${CRON_SCHEDULE:-30 0 * * *}
RUN_ON_START: ${RUN_ON_START:-false}
HEADLESS: ${HEADLESS:-true}

So the out of the box container wakes at half past midnight Shanghai time, skips the run on start, and runs without a visible browser. RUN_ON_START is the switch that turns the first container up into an immediate Dry Run, which is the order the documentation suggests anyway. The image referenced in the walkthrough is ghcr.io/unmev/douyin-auto-fire:latest, and startup is a single compose command.

One account should never have two tasks running against it

The most repeated operational rule concerns concurrency rather than configuration: do not run several automatic send tasks against the same Douyin account at once, since that is how duplicate messages happen. A separate guard exists for the same reason. The send history is recorded and consulted so a retriggered task does not repeat a message it already delivered.

That pair of safeguards answers the question every scheduled browser script has to answer, which is what happens when the schedule fires twice because a host rebooted or a runner restarted. It does not answer the failure modes on the other side of the platform. A data centre IP, a GitHub hosted runner or a Docker host may trigger Douyin security verification, and when that happens the person has to log in again themselves. If the cookie or the Storage State expires, or Douyin asks for a verification code, no amount of retrying inside the script substitutes for the account owner.

Nothing in the repository suggests a way around that. There are no GitHub releases, no bypass options and no proxy setting in the example environment file beyond an optional browser path, which points at the conclusion that this is meant to run on a network you control.

Noncommercial terms rule out reselling the hosted image

The project states its own terms as PolyForm Noncommercial License 1.0.0. Personal study, research, modification and noncommercial use are allowed; commercial use, paid deployment, reselling and paid services are not, without authorisation from the maintainer.

That clause matters more than usual here, because the deployment routes are all built around running someone else's scheduled job. A self hosted container on your own NAS is squarely personal. Renting the same container to other users is what the licence names as prohibited.

The project structure backs up the same picture. Alongside the application code there are per platform docs, a deployment directory, a tests directory, a pytest configuration file, a development requirements file, an example task config and two example environment files, one plain and one shaped for multiple accounts. The last commit on the main branch is dated 2026-09-12.

Editorial conclusion

This suits one person keeping their own streaks from lapsing, and it does that without an account API because it drives the web interface instead. It does not suit bulk messaging, since the Actions route caps at five Douyin accounts and the same account must not run two tasks at once. Before the first real send, run Dry Run with one account, one friend and one text message, confirm the login state holds, and decide where the cookie file lives, because anyone who reads it can act as you. Check that your network does not trip Douyin security verification, since a hosted runner or a data centre IP is where that verification tends to appear.

Frequently asked questions

How does douyin-auto-fire send messages without the Douyin API?

It uses Playwright with Chromium to simulate real browser operation, signing in with a stored cookie or Storage State, opening the conversation and typing the message in the web interface.

What does Dry Run mode do in douyin-auto-fire?

Dry Run only verifies the login state and locates the target friend. It does not actually send messages, and the documentation suggests running it after any change to friend, message or emoji configuration.

How many Douyin accounts can douyin-auto-fire handle on GitHub Actions?

The README states that GitHub Actions currently supports up to five Douyin accounts. The same account should not have multiple automatic send tasks running at once.

What should I do if the Douyin cookie stops working?

The project notes that a data centre IP, Docker host or GitHub hosted runner may trigger Douyin security verification. If the cookie or Storage State expires, or a verification code is requested, you have to complete the login verification yourself.

Can douyin-auto-fire be used commercially?

No. It is released under PolyForm Noncommercial License 1.0.0, which allows personal, research and noncommercial use only. Commercial use, paid deployment and reselling need authorisation from the maintainer.

Which notifications does douyin-auto-fire support?

It can post results to a DingTalk robot through DINGTALK_WEBHOOK and DINGTALK_SECRET, or to any generic webhook endpoint covering WeCom, Feishu, Slack, Discord and Telegram. Notifications are optional and sending works without them.

Official sources

  1. Issues
  2. Project website
  3. README
  4. unmev/douyin-auto-fire on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/unmev-douyin-auto-fire.svg)](https://hysenlabs.com/projects/unmev-douyin-auto-fire)