{"id":40178682,"url":"https://github.com/zephyrtronium/robot","last_synced_at":"2026-01-19T18:03:29.747Z","repository":{"id":57547509,"uuid":"141977028","full_name":"zephyrtronium/robot","owner":"zephyrtronium","description":"Robot is a Twitch bot that learns from people and responds to them with things that it has learned.","archived":false,"fork":false,"pushed_at":"2025-12-18T01:34:59.000Z","size":665,"stargazers_count":38,"open_issues_count":15,"forks_count":7,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-12-21T11:53:25.715Z","etag":null,"topics":["irc-bot","markov-chain","twitch-bot","twitch-tv"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/zephyrtronium.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"COPYING","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2018-07-23T07:21:53.000Z","updated_at":"2025-12-18T01:35:03.000Z","dependencies_parsed_at":"2024-02-19T20:25:12.143Z","dependency_job_id":"af331bff-6d68-461f-96f0-a4757bcf3829","html_url":"https://github.com/zephyrtronium/robot","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/zephyrtronium/robot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephyrtronium%2Frobot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephyrtronium%2Frobot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephyrtronium%2Frobot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephyrtronium%2Frobot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zephyrtronium","download_url":"https://codeload.github.com/zephyrtronium/robot/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zephyrtronium%2Frobot/sbom","scorecard":{"id":1237198,"data":{"date":"2025-08-11","repo":{"name":"github.com/zephyrtronium/robot","commit":"573aad277325892448d47185202d4200ac4bc277"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.7,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/go.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":7,"reason":"8 commit(s) and 1 issue activity found in the last 90 days -- score normalized to 7","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: COPYING:0","Info: FSF or OSI recognized license: GNU General Public License v3.0: COPYING:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/zephyrtronium/robot/go.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/go.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/zephyrtronium/robot/go.yml/master?enable=pin","Warn: containerImage not pinned by hash: Dockerfile:5","Warn: containerImage not pinned by hash: Dockerfile:21: pin your Docker image by updating alpine to alpine@sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 containerImage dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":6,"reason":"4 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GO-2025-3487 / GHSA-hcg3-q754-cr77","Warn: Project is vulnerable to: GO-2025-3503 / GHSA-qxp5-gwg8-xv66","Warn: Project is vulnerable to: GO-2025-3595 / GHSA-vvgc-356p-c3xw","Warn: Project is vulnerable to: GO-2025-3488 / GHSA-6v2p-p543-phr9"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-09-07T23:25:13.198Z","repository_id":57547509,"created_at":"2025-09-07T23:25:13.198Z","updated_at":"2025-09-07T23:25:13.198Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28578952,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-19T17:42:58.221Z","status":"ssl_error","status_checked_at":"2026-01-19T17:40:54.158Z","response_time":67,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["irc-bot","markov-chain","twitch-bot","twitch-tv"],"created_at":"2026-01-19T18:03:29.002Z","updated_at":"2026-01-19T18:03:29.738Z","avatar_url":"https://github.com/zephyrtronium.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Robot\n\nRobot is a bot for Twitch.TV IRC that learns from people and responds to them with things that it has learned.\n\n\n## Tools for broadcasters and mods\n\nRobot has a number of feaatures for managing activity level and knowledge.\nGenerally speaking, the bot is designed to treat chat moderation actions as moderating its knowledge as well.\nThis includes:\n\n- When an individual message is deleted, Robot forgets anything it learned from it.\n- When a chatter is banned or timed out, Robot forgets anything it learned from that chatter in the last fifteen minutes.\n- When one of Robot's messages is deleted, Robot forgets every message that was used to generate it.\n- When Robot is timed out, it forgets every message used to generate everything it sent in the last fifteen minutes.\n  (Please do not ban Robot. The bot owner will likely be shadowbanned as well, and the bot won't rejoin after being unbanned.)\n- Robot doesn't learn from chat while the stream is offline.\n\nIn addition to the above, Robot provides explicit moderation [commands](#commands).\n\n- `forget pattern` tells robot to forget every message in the last fifteen minutes containing the supplied pattern.\n  E.g., if the bot's username is \"Robot\", then saying `@Robot forget anime is trash` makes the bot remove all messages in the last fifteen minutes that contain \"anime is trash\".\n- As a special case, `forget everything` causes Robot to remove all messages in the last fifteen minutes, regardless of content.\n\n\n## What data does Robot store?\n\nRobot learns from most messages in the Twitch chats she's in while the stream is online.\nIn order to ensure moderators are able to do their job, she stores additional metadata on those messages as well.\nHere is the complete list of information types that go into Robot's databases:\n\n- Message metadata.\n  This includes the message ID, channel, timestamp, and an obscured representation of the message sender (described below).\n  Robot uses this for moderation purposes, e.g. to delete what she's learned from people who are banned.\n- Markov chain tuples.\n  This is the majority of Robot's data, a list of prefixes and the words that can follow them.\n  Each tuple is associated with an entry from the message metadata.\n- A list of users who have opted out of message collection.\n  This applies globally; there is no way to tell where the user asked for this.\n- Messages Robot has produced with the message IDs used to produce them and some additional info for analytics.\n  No data collected from users is here, except insofar as the messages are produced from things people have said.\n\nIn the message metadata, the message sender is stored using a cryptographic hash of the sender's user ID, the channel it was sent to, and the fifteen-minute time period in which it was sent.\nRoughly speaking, if Robot has been learning from Bocchi, message metadata together with Markov chain tuples *can* answer questions like these:\n\n- What are all the messages Robot has learned from Bocchi in the last thirty minutes?\n- Did Bocchi talk in KessokuBand's chat on 21 Feb 2024 between 0900 and 1000 while KessokuBand's stream was online? (Robot never learns from offline streams.)\n- Was Bocchi the person who sent this particular message that Robot learned in KessokuBand's chat?\n\nOn the other hand, it is infeasible or at least very expensive for Robot's data to answer questions like these:\n\n- Who are all the people Robot has learned from in the last thirty minutes?\n- What times has Bocchi been active in KessokuBand's chat?\n- Who sent this particular message that Robot learned in KessokuBand's chat?\n- What channels has Bocchi been active in?\n\nIf you want Robot not to record your messages for any reason, simply use the `give me privacy` [command](#commands).\nYou'll still be able to ask Robot for messages and use other commands.\nIf you'd like the bot to learn from you again after going private, use the `learn from me again` command.\n\n\n## How Robot works\n\nRobot uses the mathematical concept of Markov chains, extended in some interesting ways, to learn from chat and apply its knowledge.\nHere's an example.\n\nLet's say Robot sees this chat message: `Bocchi the Rock!`.\nThe first thing it will do is run some preliminary checks to make sure it's ok to learn from the message,\ne.g. no links, sender hasn't opted out, \u0026c.\n\nThis particular message is fine.\nRobot's next step is to break it up into a list of *tokens* – basically words or stretches of non-letter characters followed by spaces.\nThe tokens here are `\u003cbeginning of message\u003e`, `Bocchi `, `the `, `Rock`, `!`, `\u003cend of message\u003e`.\nThe \"beginning of message\" and \"end of message\" are invisible tokens that are always there, at least conceptually.\n\nFor each token, Robot now learns that all of the ones before it, as a group, can be followed by the one after.\nThe *prefix* is made lowercase for this to help improve variety later.\nThat is to say:\n\n- `Bocchi ` can comme at the start of the message.\n- `the ` can come after `bocchi ` at the start.\n- `Rock` can come after `the ` after `bocchi ` at the start.\n- `!` can come after `rock` after `the ` after `bocchi ` at the start.\n- After all of the above, the message can end.\n\nLearning the message is finished.\nBut robots don't like learning things they'll never use.\n\nWhen it's time for Robot to think of something to say, the bot does a *random walk* on everything it's learned.\nStarting with the invisible beginning-of-message token, the bot picks out everything it has learned can follow and picks one option at random.\n\nLet's say it picks the word `You `.\nRobot records that the random walk went to `You `, then looks for everything that can follow `\u003cbeginning of message\u003e` `you ` (converted to lowercase, as during learning).\nIt might pick `SHOULD ` next; record it and look from `\u003cbeginning of message\u003e` `you ` `should `, and maybe choose `HAVE `; then `waited `.\n\nTo make generated messages more interesting, Robot can also shorten the length of the context it's using to search when there are few options.\nLet's say after `waited ` it starts applying this technique.\nInstead of looking for `\u003cbeginning of message\u003e` `you ` `should ` `have ` `waited `, it drops the beginning token and tries again for messages that contained \"you should have waited\" anywhere, rather than only at the start.\nThis might still not help much, so it does it again, and we'll say once more, so that now it's looking for ~~`\u003cbeginning of message\u003e`~~ ~~`you `~~ ~~`should `~~ `have ` `waited `.\n\nNow it finds `so ` as the next token.\nAlong with adding it to the random walk, it restores one of the tokens it dropped from the random walk, in case that will match something else it's learned.\nSo the next search happens with `should ` `have ` `waited ` `so `.\nNext it picks `long`, followed by `! ` and `\u003cend of message\u003e`.\nSo, the generated message is `You SHOULD HAVE waited so long!`.\n\n\n## Commands\n\nRobot understands commands to be messages which start or end with the bot's username, ignoring case, possibly preceded by an `@` character and possibly followed by punctuation when at the start.\nFor example, if the bot's username is \"Robot\", then it will recognize these as commands:\n\n- `@Robot bocchi`\n- `bocchi @rObOt`\n- `robot bocchi`\n- `Robot: bocchi`\n\nThese are *not* recognized as commands:\n\n- `bocchi Robot?`\n- `bocchi @Robot kita`\n- `¡Robot bocchi!`\n\n### Commands for everyone\n\n- `give me privacy` has Robot stop learning from your messages.\n- `learn from me again` undoes `give me privacy`.\n- `what information do you collect on me?` provides a link to the [section on privacy](#what-data-does-robot-store) on this page.\n- `will you marry me?` asks Robot to be your waifu, husbando, or whatever other label for a domestic partner is appropriate. Robot is choosy and capricious.\n- `how much do you like me?` asks Robot to compute your affection score.\n- `OwO` genyewates an especiawwy uwu message.\n- `how are you?` AAAAAAAAA A AAAAAAA AAA AAAA AAAAAAAA AA AA AAAAA.\n- `roar` makes the bot go rawr ;3\n- `where is your source code?` provides a link to this page.\n- `who are you?` gives a short self-description.\n- `generate bocchi` or `say bocchi` tells Robot to generate a message using `bocchi` as the prompt. (Nothing happens if the bot doesn't know anything to say from there.)\n\n### Commands for moderators\n\n- `echo bocchi` causes Robot to say `bocchi`, or whatever other message you give.\n- `talk about ranked competitive marriage` gives a short description of Robot's marriage system.\n- `forget bocchi` causes Robot to forget everything she's learned from messages containing `bocchi` in the last fifteen minutes. As a special case, `forget everything` tells her to forget all messages in the last fifteen minutes.\n- `be quiet for 8 hours` has Robot stop learning and speaking for eight hours; other durations like `an hour`, `1h30m`, `until tomorrow` work as well. Some commands relating to moderation and privacy will still cause her to talk. There is a twelve hour limit on quiet time.\n\n\n## Effects\n\nRobot sometimes applies special effects to copypasta and randomly generated messages.\nEffects can be configured per channel.\nThe possible effects are:\n\n- `OwO` twansfowms a message using the OwO command.\n- `o` roplocos vowols woth o.\n- `AAAAA` AAAAA AAA AAAAAAA A. (This effect tends to trigger Twitch's spam detection, preventing the message from sending. It is typically disabled for random messages.)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephyrtronium%2Frobot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzephyrtronium%2Frobot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzephyrtronium%2Frobot/lists"}