RUEN
Start a project

Engineering case

A seller on a platform with no API

The bot runs a seller storefront and delivers automatically: it takes the order, collects the buyer data, buys a subscription from a supplier with no interface and reports every outcome.

order states
9
ways to rebuild a session
3
offline licence grace
6 h

The bot runs a seller’s storefront on a trading platform: it takes an order, asks the buyer for details, buys a subscription from a supplier and reports the result. Neither the platform nor the supplier offers a machine interface — everything goes the same way an ordinary visitor does, through anti-bot protection.

An order is matched by a random marker in the description, not by the title

The panel creates the listing itself and appends a short random marker to the end of the description. On a new order the bot fetches the description and looks for it.

Matching used to work on words in the title — “month”, “30 days”. Sellers edit titles: they add emoji, reword for the season, and the matching falls apart silently. The marker lives in the description rather than the title for the same reason: in a title it takes up space in the listing and the seller trims it out — the code even strips it from there on update.

A purchase counts as successful when it appears in the supplier’s history, not by a status code

Before buying, the bot counts how many purchases are recorded for this recipient. After a “successful” answer it waits and counts again. No increase means no purchase.

The supplier has neither an interface nor any contract about status codes: the page returns success even when the purchase failed — for instance on insufficient funds. The answer cannot be trusted, so the consequence is checked rather than the promise.

The challenge is solved externally, but the connection fingerprint must match

An external service passes the anti-bot challenge and returns a pass together with a client description. The bot builds a connection with exactly that description and matches the fingerprint to the same browser.

The pass is bound to the client’s fingerprint. An ordinary request library has a recognisable fingerprint, and the protection rejects a request carrying a valid pass outright — so solving the challenge without matching the fingerprint is pointless. The browser profile is deliberately not the newest: the code records that newer fingerprints are rejected more often.

Our own proxy sits between the external solver and the site

The chain: the external service’s workers → a proxy on our server → the provider’s channel → the site. The bot itself goes through the same channel directly.

The solver must see the site from the same address as the bot, or the pass it obtains will not work. But the provider’s channel admits only allow-listed addresses, while the solver’s workers come from anywhere. Our own proxy is the only way to reconcile the two.

A session is rebuilt three different ways, and choosing the way is its own decision

There are three modes: reuse the current one, rebuild the connection without solving the challenge again, and solve it again.

After long chains the site starts refusing although the pass is still valid — it is the connection itself that went stale. Solving the challenge again in that case means paying for a solve where rebuilding the connection would do.

Signing in with the supplier goes through a code sent to the seller’s chat

The full path: pass the protection, open the shop, request a code-based sign-in, send the code as an image to the seller and poll the status until they confirm from their phone.

The supplier has neither an interface nor a login and password — the only way in is confirmation from a phone. This cannot be fully automated in principle, so the human is built into the flow deliberately, at one point and with one clear action.

Order state is a machine on disk, not in memory

Nine states, two indexes, a file on disk, every mutation under a lock. After a restart unfinished orders are brought back and sorted by age: those awaiting input expire on one deadline, those stuck mid-purchase on another.

A purchase takes minutes — the challenge, the requests, the retries — and a restart right then is inevitable. State held only in memory means a paid order lost without a trace.

Partial success is its own outcome, neither “error” nor “success”

If some of several subscriptions were delivered and some were not, the order is marked failed but not discarded: the buyer receives a line-by-line list of what was delivered and what was not.

Marking it successful deceives the buyer. Marking it failed and refunding means giving away the subscriptions already bought — they are irreversible. So there is a third outcome, and the decision stays with the seller.

A manual refund cancels delivery — but not every delivery

A status change to refunded takes the order out of the queue. The exception is orders already completed or being purchased right now.

Without it the seller refunded by hand and the bot delivered anyway a minute later — a double loss. But an order in the middle of a purchase cannot be cancelled: the money at the supplier is already spent.

An expired sign-in is a pause with a button, not a failed order

If the supplier session expires, the order goes into waiting: the buyer gets a neutral “technical pause”, the seller an alert with a “retry” button.

An expired session is neither a supplier refusal nor a buyer’s mistake but a thirty-second task for the seller. An automatic refund would burn the sale over something one tap fixes. Error details are never shown to the buyer — a neutral text goes out, the technical part goes into the alert.

The client receives bytecode, not sources

The build compiles the project, packs only an allow-listed set of files and recompiles on the target server.

The bot contains working ways around protection and logic that spends real money: shipping sources means it spreads instantly. Compiling on the target machine is needed because bytecode is tied to the interpreter version.

Python · curl_cffi · Telegram Bot API · Docker