# Changelog of BORG Telegram channel scraper (`acid-base/borg-telegram-scraper`) Actor

- **URL**: https://apify.com/acid-base/borg-telegram-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/acid-base/borg-telegram-scraper.md

## Changelog

### Unreleased

Not deployed. The live actor remains `1.0.16`; this entry describes what a
deploy of this branch would change for a caller.

#### Changed — billing

- A target whose page leaves `t.me` entirely is no longer sold as a row. It used
  to emit one `completeness: "empty"` channel row and report `GREEN`; it now
  emits no row and is recorded in `OUTPUT.outages`. Measured live: `a` and `zz`
  both go 1 row GREEN -> 0 rows RED. A handle that is merely nonexistent but
  well formed is NOT affected — Telegram redirects it to a real profile page,
  which stays billable on both sides.
- A response truncated before any message rendered used to emit one row; it now
  emits none.
- The same channel requested twice in different letter case used to be scraped
  and billed twice; it is now one observation. Measured live: `addemoji` +
  `ADDEMOJI` at limit 40 goes 8 rows (2 channel + 6 message) -> 4 (1 channel + 3
  message). This only costs you anything when the first target does not already
  exhaust `messageLimit`.
- A mode or filter that matches nothing (`dateRange` excluding everything, a
  media-only mode over text-only posts) used to emit one channel row stamped
  `completeness: "complete"`; it now emits none.

Three kinds of input yield fewer rows, and none of them loses a message you
asked for:

- **the same channel requested more than once** — case-variant duplicates are now
  one observation, so you are billed once for data you were previously billed
  twice for (`addemoji` + `ADDEMOJI` at limit 40: 8 rows -> 4);
- **inputs that were never a channel request** — an action link like
  `t.me/boost/durov` or `t.me/joinchat/<hash>` used to be read as the channel
  `@boost` / `@joinchat` and billed against it, and is now refused.
- **a mode or filter that matches nothing** — a `dateRange` excluding every post,
  or a media-only mode over text-only posts, used to emit one channel row stamped
  `completeness: "complete"`; it now emits none, because that row asserted a
  complete observation of something it had not observed.

Otherwise, measured across 58 populated live channels (independent audit,
2026-07-31), a genuine channel request
delivers what it did before.

#### Changed — contract

- **A partial export now reports `AMBER`, not `GREEN`.** A delivery whose
  DOCUMENT did not arrive whole used to ship rows stamped
  `completeness: "partial"` under a `GREEN` run status. The affected targets are
  listed in the new `OUTPUT.partialTargets`.
  This is deliberately narrow: a complete document whose posts include stickers
  or polls the extractor does not surface stays `GREEN`, because the customer
  received every post. Verified on live channels with exactly that shape
  (`@science`, `@biology` and `@durov_russia` all had such posts on 2026-07-31).
- **A channel that owns a second username now delivers.** `t.me/s/finance`
  serves the channel whose posts are authored as `@money`; the identity bind
  used to read that as another channel's posts and refuse the run. Measured
  across live channels, several were affected — the first sweep found 3 of 48 and
  a wider independent one 13 of 58 (22%), i.e. 78+ real message rows.
- **`OUTPUT.status` is derived rather than the constant `GREEN`.** See README
  for the full table. `GREEN` means nothing went wrong; it does not mean the
  channel's history was exhausted, and a run that stops at `messageLimit` — the
  ordinary case — is `GREEN`.
- **A run with no usable target now FAILS instead of succeeding.** An empty,
  blank, or absent target used to become the literal `@telegram` and bill 20
  rows of a channel the caller never requested. The actor now writes
  `OUTPUT {status: 'RED', refused: true, reason}` and the run fails. Affects API
  callers posting `{}` or an empty `telegramChannelOrProfile`, and saved Tasks
  whose target field was cleared; the Console UI is unaffected because the input
  schema prefills a target.
- Message rows are attributed and permalinked from the channel the page itself
  asserts (`data-post`), not from the requested handle, so another channel's
  bytes can never be billed under the requested one.
- `t.me` reserved paths (`/c/<id>`, `/joinchat/<hash>`, `/addstickers/…` and
  similar) are refused at the input boundary instead of being read as a channel
  handle named `c` or `joinchat`.
- `telegram.me`, `www.t.me`, `www.telegram.me` and `http://t.me` targets are
  accepted and canonicalised to `https://t.me/<handle>`; they previously failed
  the run. The request boundary is unchanged — only `https://t.me` is ever
  requested.

### 1.0.0

- Generated from CIS document `borg-telegram-historical-archive`.
- Deployment-ready Apify scaffold emitted by BORG Productizer.
