{"_id":"@bitsocial/r9k-challenge","_rev":"2-d25609b90a00471ef0540accc3501e89","name":"@bitsocial/r9k-challenge","dist-tags":{"latest":"0.1.1"},"versions":{"0.1.0":{"name":"@bitsocial/r9k-challenge","version":"0.1.0","keywords":["bitsocial","bitsocial-protocol","anti-spam","challenge","r9k","robot9001","robot9000"],"author":{"name":"Bitsocial Forge"},"license":"GPL-3.0-or-later","_id":"@bitsocial/r9k-challenge@0.1.0","maintainers":[{"name":"tomcasaburi","email":"tom@plebbitlabs.com"},{"name":"rinse12","email":"rinse12@tutanota.com"}],"homepage":"https://github.com/bitsocialnet/r9k-challenge#readme","bugs":{"url":"https://github.com/bitsocialnet/r9k-challenge/issues"},"dist":{"shasum":"63c3157e44d594acea97d461ed101512efcacf75","tarball":"https://registry.npmjs.org/@bitsocial/r9k-challenge/-/r9k-challenge-0.1.0.tgz","fileCount":9,"integrity":"sha512-5FdJ5w/ImyzZMFHQEgI2nC37TwjA85FTW4R8V+Hq0zZ7lcgXiavyfNYiCVqvyEH7WTc8eEHEoznfIAVunc3jKA==","signatures":[{"sig":"MEQCIBNqIOX2tVcfHOL//pt2CiMmqsoEYpuqQXev/X1KkyolAiBS7Yk2PNVbsZX94zvm+Nm+FTikioJy3jabspXkgndTtg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":103562},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"gitHead":"a3e553da9082f336b57afb0e295152b6984cd9ed","scripts":{"test":"vitest run","build":"yarn clean && node esbuild.config.js && tsc -p tsconfig.json --emitDeclarationOnly","clean":"rm -rf dist","format":"prettier --write .","release":"HUSKY=0 release-it --config .release-it.json","type-check":"tsc -p tsconfig.json --noEmit","format:check":"prettier --check .","test:coverage":"vitest run --coverage.enabled","coverage:badge":"node scripts/write-coverage-badge.mjs"},"_npmUser":{"name":"tomcasaburi","email":"tom@plebbitlabs.com"},"repository":{"url":"git+https://github.com/bitsocialnet/r9k-challenge.git","type":"git"},"_npmVersion":"11.8.0","description":"Robot9001-style originality challenge for Bitsocial communities","directories":{},"resolutions":{"ws@npm:^8.5.0":"npm:8.20.1","ws@npm:^8.18.3":"npm:8.20.1","uuid@npm:13.0.0":"npm:13.0.1","axios@npm:^1.7.2":"npm:1.15.2","uuid@npm:^11.0.0":"npm:11.1.1","undici@npm:6.22.0":"npm:6.25.0","basic-ftp@npm:^5.0.2":"npm:5.3.1","ip-address@npm:^10.0.1":"npm:10.1.1","protobufjs@npm:^6.10.2":"npm:7.5.8","@libp2p/kad-dht@npm:^16.2.0":"npm:16.2.6","@protobufjs/utf8@npm:^1.1.0":"npm:1.1.1","@libp2p/gossipsub@npm:15.0.21":"npm:15.0.23","@conventional-changelog/git-client@npm:^1.0.0":"npm:2.7.0"},"_nodeVersion":"22.12.0","dependencies":{"zod":"4.3.5"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"packageManager":"yarn@4.13.0","devDependencies":{"vitest":"4.0.16","esbuild":"0.27.2","prettier":"3.8.1","release-it":"19.2.3","typescript":"6.0.2","@types/node":"24.10.1","better-sqlite3":"12.9.0","@pkcprotocol/pkc-js":"0.0.36","@vitest/coverage-v8":"4.0.16","@types/better-sqlite3":"7.6.13","@release-it/conventional-changelog":"10.0.1"},"_npmOperationalInternal":{"tmp":"tmp/r9k-challenge_0.1.0_1779523155961_0.8621946227686286","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@bitsocial/r9k-challenge","version":"0.1.1","description":"Robot9000-style originality challenge for Bitsocial communities","keywords":["bitsocial","bitsocial-protocol","anti-spam","anti-repost","challenge","r9k","robot9000"],"author":{"name":"Bitsocial Forge"},"license":"GPL-3.0-or-later","type":"module","engines":{"node":">=22"},"main":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"repository":{"type":"git","url":"git+https://github.com/bitsocialnet/r9k-challenge.git"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"build":"yarn clean && node esbuild.config.js && tsc -p tsconfig.json --emitDeclarationOnly","clean":"rm -rf dist","test":"vitest run","test:coverage":"vitest run --coverage.enabled","coverage:badge":"node scripts/write-coverage-badge.mjs","type-check":"tsc -p tsconfig.json --noEmit","format":"prettier --write .","format:check":"prettier --check .","release":"HUSKY=0 release-it --config .release-it.json"},"dependencies":{"zod":"4.3.5"},"devDependencies":{"@pkcprotocol/pkc-js":"0.0.36","@release-it/conventional-changelog":"10.0.1","@types/better-sqlite3":"7.6.13","@types/node":"24.10.1","@vitest/coverage-v8":"4.0.16","better-sqlite3":"12.9.0","esbuild":"0.27.2","prettier":"3.8.1","release-it":"19.2.3","typescript":"6.0.2","vitest":"4.0.16"},"resolutions":{"@conventional-changelog/git-client@npm:^1.0.0":"npm:2.7.0","@libp2p/gossipsub@npm:15.0.21":"npm:15.0.23","@libp2p/kad-dht@npm:^16.2.0":"npm:16.2.6","@protobufjs/utf8@npm:^1.1.0":"npm:1.1.1","axios@npm:^1.7.2":"npm:1.15.2","basic-ftp@npm:^5.0.2":"npm:5.3.1","ip-address@npm:^10.0.1":"npm:10.1.1","protobufjs@npm:^6.10.2":"npm:7.5.8","undici@npm:6.22.0":"npm:6.25.0","uuid@npm:^11.0.0":"npm:11.1.1","uuid@npm:13.0.0":"npm:13.0.1","ws@npm:^8.5.0":"npm:8.20.1","ws@npm:^8.18.3":"npm:8.20.1"},"packageManager":"yarn@4.13.0","gitHead":"b5c3dcd73d8a914c35e7d0ebe7bedff1935b3eec","_id":"@bitsocial/r9k-challenge@0.1.1","bugs":{"url":"https://github.com/bitsocialnet/r9k-challenge/issues"},"homepage":"https://github.com/bitsocialnet/r9k-challenge#readme","_nodeVersion":"24.15.0","_npmVersion":"11.15.0","dist":{"integrity":"sha512-1EN4nmwF9emINQDT/R7861K7pDcWNk3DHAHH3n03FPO0qYMyjK+Y2lEga9+tRkAhZlSeXLuVeW/POSUWXFwbdQ==","shasum":"95b1be3166cfb33dc227a824a398b765ea737ae5","tarball":"https://registry.npmjs.org/@bitsocial/r9k-challenge/-/r9k-challenge-0.1.1.tgz","fileCount":9,"unpackedSize":103724,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@bitsocial%2fr9k-challenge@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIFHzb/fUJTAIhrP6F9vX37MFAotdK/dg1YidzW3WDIYvAiEAk6VGfKVFMzpJRRLRwvFGoHBEAExgO+kpNhTOMqtVMnk="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:8c59de88-86fd-44bc-a494-70f9ddd5683b"}},"directories":{},"maintainers":[{"name":"tomcasaburi","email":"tom@plebbitlabs.com"},{"name":"rinse12","email":"rinse12@tutanota.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/r9k-challenge_0.1.1_1779525301565_0.34706552252708467"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-23T07:59:15.858Z","modified":"2026-05-23T08:35:01.987Z","0.1.0":"2026-05-23T07:59:16.113Z","0.1.1":"2026-05-23T08:35:01.709Z"},"bugs":{"url":"https://github.com/bitsocialnet/r9k-challenge/issues"},"author":{"name":"Bitsocial Forge"},"license":"GPL-3.0-or-later","homepage":"https://github.com/bitsocialnet/r9k-challenge#readme","keywords":["bitsocial","bitsocial-protocol","anti-spam","anti-repost","challenge","r9k","robot9000"],"repository":{"type":"git","url":"git+https://github.com/bitsocialnet/r9k-challenge.git"},"description":"Robot9000-style originality challenge for Bitsocial communities","maintainers":[{"name":"tomcasaburi","email":"tom@plebbitlabs.com"},{"name":"rinse12","email":"rinse12@tutanota.com"}],"readme":"[![Coverage](https://img.shields.io/endpoint?url=https://bitsocialnet.github.io/r9k-challenge/badges/coverage.json)](https://github.com/bitsocialnet/r9k-challenge/blob/master/scripts/write-coverage-badge.mjs)\n\n# @bitsocial/r9k-challenge\n\nRobot9000-style originality challenge for Bitsocial communities.\n\nThis package runs on a Bitsocial community owner node as a deterministic Bitsocial challenge. It is not specific to 5chan; any Bitsocial community can use it when the operator wants Robot9000-style anti-repost behavior. It does not use AI. It scans the community owner's local comments database for exact text reposts after Robot9000 normalization and applies escalating temporary bans for failed attempts.\n\nRobot9000 is based on the anti-repost idea originally described by Randall Munroe in [ROBOT9000 and #xkcd-signal: Attacking Noise in Chat](https://blog.xkcd.com/2008/01/14/robot9000-and-xkcd-signal-attacking-noise-in-chat/).\n\n## Installation\n\nRun this on the Bitsocial node that owns the community:\n\n```bash\nbitsocial challenge install @bitsocial/r9k-challenge\n```\n\nThis challenge is listed in the Bitsocial app directory under anti-spam:\n\nhttps://bitsocial.net/apps?category=anti-spam\n\n## Configuration\n\nAdd the challenge to the community's `settings.challenges`:\n\n```js\n[\n  { name: \"@bitsocial/spam-blocker-challenge\" },\n  { name: \"@bitsocial/r9k-challenge\" },\n];\n```\n\nDefault options implement Robot9000-style behavior:\n\n| Option                              | Default                                 | Behavior                                                            |\n| ----------------------------------- | --------------------------------------- | ------------------------------------------------------------------- |\n| `statePath`                         | `~/.bitsocial-r9k-challenge-state.json` | Private JSON state for temporary bans and accepted-hash race guards |\n| `minimumOriginalContentLength`      | `16`                                    | Requires this many normalized text characters                       |\n| `transgressionDecayIntervalSeconds` | `86400`                                 | Forgives one transgression per day                                  |\n| `penaltyBaseSeconds`                | `2`                                     | Temporary ban duration is `2^n` seconds                             |\n| `maxPenaltySeconds`                 | empty                                   | No cap by default                                                   |\n| `blockUnicode`                      | `true`                                  | Rejects non-ASCII text                                              |\n| `stripBacklinks`                    | `true`                                  | Ignores numeric backlinks like `>>123`                              |\n| `requireText`                       | `true`                                  | Rejects posts without title/body text                               |\n| `error`                             | `Rejected by Robot9000.`                | Error prefix shown to users                                         |\n\nPosts require enough original content to avoid low-effort duplicates. This package uses `16` normalized characters as its default and keeps it configurable per board.\n\n## Behavior\n\n- Exact normalized text reposts are rejected by scanning the owner node's `comments` SQLite table during `getChallenge()`.\n- Numeric backlinks like `>>1` do not count toward originality.\n- Images, media links, and URLs are not included in the originality hash.\n- Unicode is rejected by default.\n- Posts need text; image-only posts fail.\n- A failed originality attempt temporarily bans the author for `2^n` seconds, where `n` is the current transgression count.\n- The transgression count decays by one every `transgressionDecayIntervalSeconds`.\n- State stores SHA-256 hashes for newly accepted text plus temporary-ban counters, not raw post text. Existing-board originality comes from the local database scan.\n\n## Ban Semantics\n\nBitsocial exposes `author.community.banExpiresAt` for moderator bans, but a challenge failure happens before the offending comment is accepted and therefore has no accepted `commentCid` to moderate. This package enforces the same timed-ban behavior inside the challenge state and rejects the author's later challenge requests until `banExpiresAt`.\n\nIf Bitsocial later exposes a safe challenge-side author-ban API for rejected publications, this package can map the same penalty state to native `banExpiresAt`.\n\n## Development\n\n```bash\ncorepack yarn install\ncorepack yarn type-check\ncorepack yarn test\ncorepack yarn test:coverage\ncorepack yarn build\n```\n\n## Test Coverage\n\nThe test suite covers Robot9000 normalization, minimum text checks, Unicode blocking, image/link exclusion, escalating and decaying temporary bans, content edits, missing database fail-closed behavior, and duplicate detection against both the challenge state file and a real in-memory SQLite `comments`/`commentUpdates` database.\n\nThe coverage badge reports line coverage generated with `yarn test:coverage`. On pushes to `master`, CI writes a Shields-compatible endpoint payload and publishes it to GitHub Pages.\n\nThese tests exercise the challenge package directly. They do not start a full Bitsocial node or publish over the network.\n\n## Publishing\n\nCreate the GitHub release and changelog with release-it:\n\n```bash\ncorepack yarn release 0.1.0\n```\n\nThe command expects the current branch to have an upstream and needs a GitHub token that can create releases. It writes `CHANGELOG.md`, creates a `v0.1.0` tag, and opens the GitHub release.\n\nThe first npm publish must create the package before trusted publishing can be configured:\n\n```bash\nnpm publish --access public\n```\n\nAfter the package exists, configure npm trusted publishing:\n\n- Publisher: GitHub Actions\n- Organization: `bitsocialnet`\n- Repository: `r9k-challenge`\n- Workflow filename: `publish.yml`\n- Environment: leave blank\n\nEquivalent npm CLI command:\n\n```bash\nnpm trust github @bitsocial/r9k-challenge --repo bitsocialnet/r9k-challenge --file publish.yml\n```\n\nFuture releases publish automatically when `package.json` version changes on `master`. The publish workflow skips versions that already exist on npm.\n","readmeFilename":"README.md"}