{"id":15758142,"url":"https://github.com/rossjrw/tars","last_synced_at":"2025-10-15T10:31:11.317Z","repository":{"id":36603193,"uuid":"172991969","full_name":"rossjrw/tars","owner":"rossjrw","description":"IRC chat and search bot for the SCP Wiki","archived":true,"fork":false,"pushed_at":"2021-12-02T18:22:51.000Z","size":1822,"stargazers_count":1,"open_issues_count":100,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-02-01T00:31:55.580Z","etag":null,"topics":["bot","irc","irc-bot","scp"],"latest_commit_sha":null,"homepage":"https://rossjrw.com/tars","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rossjrw.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-02-27T21:19:05.000Z","updated_at":"2023-06-02T13:49:34.000Z","dependencies_parsed_at":"2022-08-30T15:30:14.475Z","dependency_job_id":null,"html_url":"https://github.com/rossjrw/tars","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/rossjrw/tars","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossjrw%2Ftars","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossjrw%2Ftars/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossjrw%2Ftars/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossjrw%2Ftars/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rossjrw","download_url":"https://codeload.github.com/rossjrw/tars/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rossjrw%2Ftars/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279073306,"owners_count":26097375,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-15T02:00:07.814Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["bot","irc","irc-bot","scp"],"created_at":"2024-10-04T09:42:50.508Z","updated_at":"2025-10-15T10:31:11.014Z","avatar_url":"https://github.com/rossjrw.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# TARS\n\nIRC chatbot for the [SCP Wiki](https://scpwiki.com), facilitating wiki\nsearch and internet outreach automation.\n\nThis README contains instructions for command line usage and implementation\ndetails. End users looking for command instructions should look at the\ndocumentation: https://rossjrw.com/tars\n\n## Current state\n\nTARS operated on the SCP IRC network (currently\n[SkipIRC](http://05command.wikidot.com/skipirc:home)) since March 2019. In\nthat time it's seen two IRC networks, ~1000 users, ~1,000,000 messages, and\nserved ~75,000 requests.\n\nTARS has run on:\n\n* A Chromebook\n* A Raspberry Pi 3 Model A+ with a cute little screen\n* An AWS EC2 t2.micro instance\n* An AWS EC2 t3a.nano instance\n\nAs of December 2021, the main instance of TARS is no longer operational,\ncoinciding with the SCP Wiki's move away from IRC.\n\n## Installation\n\nTARS uses [Poetry](https://github.com/python-poetry/poetry) for environment\nmanagement.\n\n```shell\ngit clone https://github.com/rossjrw/tars\ncd tars\npoetry install\n```\n\nAll modules are from PyPI except:\n\n- pyaib, which is forked here: https://github.com/rossjrw/pyaib\n- re2, which is forked here: https://github.com/andreasvc/pyre2\n\nre2 will not be installed automatically as it has a specific install process\ndetailed in its README. TARS will operate fine without re2 but will be\nvulnerable to regular expression attacks.\n\nIn order to install the `cryptography` dependency of pyaib, you may be required\nto run:\n`sudo apt install build-essential libssl-dev libffi-dev python-dev`\n\nTARS requires at least Python 3.8.\n\n## Usage\n\n```shell\npoetry run python3 -m tars [config file] --deferral [deferral config]\n```\n\nThe config files I use are in `config/`.\n\nTARS requires a set of API keys to function correctly. These should be stored\nin `config/keys.secret.toml`. More details can be found in `helpers/api.py`.\n\nTARS will use the nick provided in the config file and NickServ password as\ndefined by the key `irc_password` in `keys.secret.toml`.\n\n## Building documentation\n\n```shell\npoetry run python3 -m tars [config file] --deferral [deferral config] --docs\n```\n\nInformation from the main config and the deferral config are used in the\ndocumentation, so they should be provided for the most accurate output.\n\n## Testing\n\n```shell\npoetry run pytest\n```\n\nTesting is... uh... a little spotty. Tests pass but the suite is not exactly\ncomprehensive.\n\n## Adding commands\n\nEach major command should be in its own file. Subcommands can be in the same\nfile as the major command's file.\n\nCreate a new file in `commands/` named the same as your major command.\n\nWithin this file, create a new class named for the major command that extends\n`helpers.basecommand.Command`, with the following properties (all of which are\noptional):\n\n* `command_name`: The canonical name of this command, used for documentation;\n  if not provided or `None`, this command will not appear in documentation.\n* `arguments`: A list of dicts, each of which represents an argument for this\n  command. These are passed directly to the argparse [`add_argument`\n  constructor](https://docs.python.org/3/library/argparse.html#the-add-argument-method)\n  and the keys correspond to its kwargs. However, the following extra keys are\n  accepted:\n    * `flags`: A list of flags for this argument (will be used as the first\n      positional parameter of `add_argument`).\n    * `mode`: If `\"hidden\"`, this argument will not appear in documentation.\n    * `permission`: The permission level required to run this command (\n      currently boolean, with `true` indicating only a Controller can run it).\n    * `type`: The same as `type` from argparse, but can also be the following\n      values:\n        * `tars.helpers.basecommand.regex_type`: Checks that the arguments\n          correctly compile to a regex, and exposes the argument values as\n          compiled regex objects.\n        * `tars.helpers.basecommand.matches_regex(rgx, reason)`: Checks that\n          the provided string matches regex `rgx` (can be a Pattern or a\n          string); if it does not, rejects the argument with the given reason.\n          The reason should complete the sentence \"Argument rejected because\n          the value...\"\n        * `tars.helpers.basecommand.longstr`: Same as `str`, but the argument\n          values are concatenated with spaces into a single string and exposed\n          as one value. Simulates passing e.g. a sentence as argument value\n          without needing to use quotes. Must be used with a `nargs` value that\n          would normally expose a list, but will actually expose a single value\n          as if the\n          `nargs` were `None`.\n* `permission`: The permission level required to run this command (currently\n  boolean, with `true` indicating only a Controller can run it).\n* `arguments_prepend`: A string that will be prepended to arguments passed to\n  this command. Useful for setting defaults for subcommands.\n* `aliases`: A list of string aliases for this command. A command with no\n  aliases cannot be called. A subcommand with no defined aliases will use the\n  same aliases as its parent command which probably isn't very useful.\n\nThe class' docstring is used as documentation for the command, although only\nthe first line will appear on the command line.\n\nThe command must have an instance method called `execute`, which is called when\nthe command is run, that takes the following arguments (which can be named\nanything):\n\n* `self`: The command object. Check for argument presence\n  with `'argname' in self`. To get the value of the argument, access `self`\n  like a dict:\n  `self['argname']`.\n* `irc_c`: [pyaib context](https://github.com/facebook/pyaib/wiki/Plugin-Writing#context-object)\n* `msg`: [pyaib message](https://github.com/facebook/pyaib/wiki/Plugin-Writing#message-object)\n* `cmd`: A parsed message-like object, similar to `msg` but with more\n  properties that are pertinent to this command specifically:\n    * `ping`: Whether the bot was pinged by this message.\n    * `command`: The command name as typed by the user (may differ from the\n      canonical `command_name`).\n    * `prefix`: The prefix used to call the command e.g. `..`.\n\nTo create a subcommand, create a new class that extends the parent command,\nwith its own docstring and `command_name`. I recommend using\n`arguments_prepend` to add command-line flags and then implement the\ncorresponding functionality in the parent command, but if you must add\nan `execute` method to the subcommand, be sure to call the parent's `execute`\nmethod as appropriate.\n\nEdit `commands/__init__.py` and add any commands you created to the `COMMANDS`\ndict along with any aliases as a sub-dict. Your command must have at least one\nalias, or it won't be able to be called.\n\nOther considerations:\n\n* Arguments may not pass an `action` to the argparse parser.\n* Creating boolean arguments is a little different to normal argparse usage.\n  Normally, you would set `default=False` and `action='store_true'`, omitting a\n  type. Here, just set `type=bool`; this is a special case and the action and\n  default value will be handled automatically. `nargs` must be 0 or omitted.\n* A boolean arg is always present (and an `'arg' in self` check will always\n  return `true`) regardless of whether it was actually specified. If it was not\n  specified, its value is `false`.\n* Most `nargs` values will result in a list being created, except for\n  `nargs=None`, which expects a single value and returns it directly.\n* The default value for `nargs` of `*` and `+` is an empty list, even if no\n  value was actually provided.\n* If an argument with `nargs` of `\"*\"` or `\"?\"` is not\n  present, `'argname' in self` will return `false`; if it _is_ present but no\n  values were provided,\n  `'argname' in self` will return `true`, even though either way the value is\n  identical (`[]` for `\"*\"` and `XXX TODO` for `\"?\"`).\n\n## Other bits\n\nA few other important pieces of information:\n\n* `msg`\n  - [pyaib's message object](https://github.com/facebook/pyaib/wiki/Plugin-Writing#message-object)\n* `from helpers.database import DB` then `DB.xxx()` - where xxx represents a\n  function in helpers/database.py\n* `from helpers.config import CONFIG` then `CONFIG.xxx` to access property xxx\n  of the configuration file\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frossjrw%2Ftars","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frossjrw%2Ftars","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frossjrw%2Ftars/lists"}