{"id":16577893,"url":"https://github.com/compufox/glacier","last_synced_at":"2025-08-09T03:39:22.929Z","repository":{"id":71050464,"uuid":"231030301","full_name":"compufox/glacier","owner":"compufox","description":"lightweight mastodon bot wrapper","archived":false,"fork":false,"pushed_at":"2024-01-16T21:52:44.000Z","size":59,"stargazers_count":9,"open_issues_count":3,"forks_count":3,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-04T23:35:16.274Z","etag":null,"topics":["bot-framework","lisp","mastodon","mastodon-bot"],"latest_commit_sha":null,"homepage":null,"language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/compufox.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2019-12-31T05:18:39.000Z","updated_at":"2024-09-26T16:19:03.000Z","dependencies_parsed_at":"2024-01-24T04:09:52.751Z","dependency_job_id":"dac41d6f-5ba3-4982-a841-3ac97e0cc9b7","html_url":"https://github.com/compufox/glacier","commit_stats":{"total_commits":69,"total_committers":5,"mean_commits":13.8,"dds":"0.10144927536231885","last_synced_commit":"2175efedfaaf9d425940df0c45556e1d99417364"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/compufox/glacier","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compufox%2Fglacier","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compufox%2Fglacier/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compufox%2Fglacier/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compufox%2Fglacier/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/compufox","download_url":"https://codeload.github.com/compufox/glacier/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/compufox%2Fglacier/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264502387,"owners_count":23618587,"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","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-framework","lisp","mastodon","mastodon-bot"],"created_at":"2024-10-11T22:12:33.995Z","updated_at":"2025-08-09T03:39:22.884Z","avatar_url":"https://github.com/compufox.png","language":"Common Lisp","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ![glacier logo](https://i.imgur.com/tHNtPgc.png)\n### _ava fox_\n\nlightweight wrapper for making mastodon bots\n\n## Installation\n\n`(ql:quickload :glacier)`\n\nor clone this repo into your local system definitions or `~/common-lisp` folder\n\n## Usage\n\ncreate a bot object, passing it (at minimum) a path to your config file\n\nthen pass that bot object to `(glacier:run-bot)`\n\nif you pass nothing else but the bot object,\nthe framework will drop into a loop monitoring for updates\nand running the proper handlers from your bot\n(assuming you specified what handlers to run when creating your bot object)\n\nyou can pass code along with your bot to `(glacier:run-bot)` and it will\nrun that code along with monitoring for streaming updates\n\n**NOTE**: if you pass code that doesn't block forever, the bot will exit without doing anything\n\n```lisp\n;; bad example\n(glacier:run-bot (my-bot)\n  (format t \"my bot is running!\")) ;; this doesn't block, so we print this and exit\n```\n\nsee below for good examples\n\nmost objects that get passed around are [tooter objects](https://shinmera.github.io/tooter), so it probably wouldnt hurt to get acquainted with that library.\n\n## Examples\n\nthe following runs a bot (using the instance and access token specified in the config)\nthat posts \"trans rights are human rights\" every 30 minutes\n\nplease see the example config for option names\n\n```lisp\n(glacier:run-bot ((make-bot :instance \"mastodon.social\" :access-token \"n0tArealT0KEn\"))\n  (glacier:after-every (30 :minutes)\n    (glacier:post \"trans rights are human rights\" :visibility :public)))\n```\n\nthe following runs a bot that responds to a mention with a cordial hello\n\n```lisp\n(defun maybe-respond (notification)\n  \"respond to a status from a mention NOTIFICATION\"\n  (when (glacier:mention-p notification)\n    (glacier:reply (tooter:status notification) \"hi! :3\")))\n\n(glacier:run-bot ((make-bot :config-file \"/path/to/bot.config\" :on-notification #'maybe-respond)))\n```\n\nthe following runs a bot that will respond to posts with `!hello` in \nthem with status personalized with their displayname\n\n```lisp\n(defun cordial-reply (status)\n  (glacier:reply status (format nil \"hi, ~a! :3\"\n                        (tooter:display-name (tooter:account status)))))\n\n(glacier:add-command \"hello\" #'cordial-reply)\n\n(glacier:run-bot ((make-bot :config-file \"/path/to/bot.config\")))\n```\n\n## API\n\n`make-bot (\u0026key config-file instance access-token (strip-html t) strip-username (timeline \"user\") on-update on-delete on-notification :cw-mappings)`\n\nmakes a bot and returns it. \n\nINSTANCE, ACCESS-TOKEN, STRIP-HTML, STRIP-USERNAME, TIMELINE are all options that are typically in a config file\npassing these values in allows the developer to skip specifying a config file and can pull values in from other places\ne.g., command line arguments\n\nCONFIG-FILE is a string or a path that denotes a glacier config file\n\nINSTANCE is a mastodon instance domain name, with or without http scheme\n\nACCESS-TOKEN is an access token for a mastodon account on INSTANCE\n\nSTRIP-HTML if non-nil incoming posts will have their html stripped from them. defaults to T\n\nSTRIP-USERNAME if non-nil strips the bot's username from incoming posts. defaults to NIL\n\nTIMELINE string denoting which timeline should be used for the streaming websocket. can be one of 'user', 'public', 'direct'. defaults to 'user'\n\nON-UPDATE a function that accepts a single mastodon status. gets ran for every new post that streams in from TIMELINE\n\nON-DELETE a function that accepts a single status id. gets ran for every deleted status that streams in from TIMELINE\n\nON-NOTIFICATION a function that accepts a single mastodon notification. gets ran for every notification that streams in from TIMELINE\n\nCW-MAPPINGS a pathname or list of files that contain mappings specially formatted for the bot to automatically provide content warnings for generated posts. please see content-warning.map.example for examples of the formatting\n\n---\n\n`run-bot ((bot \u0026key delete-command (with-websocket t)) \u0026body body)`\n\nruns BOT, setting up websocket handlers and starting the streaming connection before executing BODY\n\nif DELETE-COMMAND is non-nil, automatically adds a delete command\nif WITH-WEBSOCKET is non-nil (default), automatically starts up a websocket listener for realtime updates\n\nNOTE: DELETE-COMMAND is ignored if WITH-WEBSOCKET is nil\n\nif BODY is not provided drops into a loop where we sleep until the user quits us, or our connection closes. this functionality does not happen if WITH-WEBSOCKET is nil.\n\n---\n\n`post (text \u0026key (visibility :unlisted) cw media sensitive\n                 poll-options poll-multiple-choice-p poll-timeout poll-hide-totals-p)`\n\na thin wrapper around TOOTER:MAKE-STATUS \n\nwill automatically generate a content warning if cw-mappings was provided when the bot was created\n\n\nNote: POLL-TIMEOUT is the number of seconds until the poll ends\n\n\n`(post \"hi~\" :visibility :public)`\n\n\n---\n\n`reply (status text \u0026key include-mentions media)`\n\nreplys to STATUS with TEXT\n\nif include-mentions is non-nil then the reply will contain **all** mentions from the original status\n\nNOTE: reply will **always** include an @ to the person it's replying to\n\n---\n\n`mention-p (notification)`\n\nreturns T if NOTIFICATION is a mention\n\n---\n\n`fave-p (notification)`\n\nreturns T if NOTIFICATION is a favourite\n\n---\n\n`boost-p (notification)`\n\nreturns T if NOTIFICATION is a boost\n\n---\n\n`poll-ended-p (notification)`\n\nreturns T if NOTIFICATION is from a poll ending\n\n---\n\n`follow-request-p (notification)`\n\nreturns T if NOTIFICATION is a follow-request\n\n---\n\n`follow-p (notification)`\n\nreturns T if NOTIFICATION is a follow\n\n---\n\n`bot-post-p (status)`\n\nreturns T if STATUS was posted by the bot\n\n---\n\n`no-bot-p (account-or-mention)`\n\nreturns T if ACCOUNT (or account from MENTION) has #NoBot in their profile's bio\n\n---\n\n`delete-parent (status)`\n\ndeletes the parent to STATUS if it was made by the bot\n\n---\n\n`after ((amount duration \u0026key async) \u0026body body)`\n\nruns BODY after AMOUNT of DURATION time has passed\n\nif ASYNC is non-nil runs asynchronously\n\n```lisp\n(after (3 :minutes) (print \"hello\"))\n\n;; (after 3 minutes)\n;;=\u003e \"hello\"\n```\n\n---\n\n`after-every ((amount duration \u0026key async run-immediately) \u0026body body)`\n\nsame as AFTER except repeats after every duration\n\nif RUN-IMMEDIATELY is non-nil, executes BODY once before waiting for the next iteration\n\n---\n\n`on ((day \u0026key at async) \u0026body body`\n\nruns BODY on DAY, optionally AT a time\n\nDAY is a keyword with the day of the week (e.g., :sunday, :monday, etc)\n\nAT is a string denoting a time (e.g., \"13:20\", \"4:20PM\", \"23:00\")\n\nif ASYNC is non-nil code is executed asynchronously\n\nif AT is nil, code is ran at midnight on DAY\n\n---\n\n`time-to-seconds \u0026rest rest`\n\ntakes all values passed as REST, converts the time into seconds, and returns the total\n\nREST should be arguments compatible with PARSE-TIME\n\n\nexample usage: (time-to-seconds 1 :day 2 :hours 30 :minutes)\n\n---\n\n`add-command (cmd function \u0026key privileged add-prefix)`\n\nadds a command with CMD being the text to trigger the command and FUNCTION being the function that runs\n\nFUNCTION needs to accept one parameter: a tooter:status object\n\nif PRIVILEGED is non-nil, the bot needs to be following the account the mention comes from\nfor the command to be triggered\n\nif ADD-PREFIX is non-nil, adds \\*COMMAND-PREFIX\\* to the front of CMD\n\n---\n\n`*command-prefix*`\n\nthe string that prefixes commands. \n\ndefaults to \"!\"\n\n---\n\n`terminate-connection`\n\nterminates the websocket connection that feeds the bot streaming updates\n\neffectively terminates the bot\n\n---\n\n## License\n\nBSD 3-Clause\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompufox%2Fglacier","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcompufox%2Fglacier","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcompufox%2Fglacier/lists"}