{"id":25525267,"url":"https://github.com/suyash/bots","last_synced_at":"2026-03-09T18:42:33.215Z","repository":{"id":57554442,"uuid":"128979428","full_name":"suyash/bots","owner":"suyash","description":"A small library to write scalable slack and web chat apps.","archived":false,"fork":false,"pushed_at":"2018-04-16T19:26:59.000Z","size":242,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-25T01:43:44.678Z","etag":null,"topics":["bot"],"latest_commit_sha":null,"homepage":"https://suy.io/bots","language":"Go","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/suyash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-10T18:38:04.000Z","updated_at":"2018-04-19T14:07:59.000Z","dependencies_parsed_at":"2022-09-26T18:51:21.757Z","dependency_job_id":null,"html_url":"https://github.com/suyash/bots","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suyash%2Fbots","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suyash%2Fbots/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suyash%2Fbots/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/suyash%2Fbots/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/suyash","download_url":"https://codeload.github.com/suyash/bots/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248339287,"owners_count":21087213,"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"],"created_at":"2025-02-19T20:19:01.402Z","updated_at":"2026-03-09T18:42:28.157Z","avatar_url":"https://github.com/suyash.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# bots\n\n[![GoDoc](https://godoc.org/suy.io/bots?status.svg)](https://godoc.org/suy.io/bots) [![Build Status](https://travis-ci.org/suyash/bots.svg?branch=master)](https://travis-ci.org/suyash/bots)\n\nA little library to write chatbots in go.\n\n## web\n\n```\ngo get suy.io/bots/web\n```\n\n\u003e more examples in [examples/web/bots](examples/web/bots). Small Deployment example in [examples/web/app](examples/web/app). Also deployed online on https://app-ruhxhowvkv.now.sh.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"suy.io/bots/web\"\n)\n\nfunc main() {\n\tc, err := web.NewController()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\thttp.HandleFunc(\"/\", index)\n\thttp.HandleFunc(\"/chat\", c.ConnectionHandler())\n\n\tgo handleMessages(c)\n\n\tlog.Fatal(http.ListenAndServe(\":8080\", nil))\n}\n\nfunc handleMessages(c *web.Controller) {\n\tfor msg := range c.DirectMessages() {\n\t\tmsg.Reply(web.TextMessage(msg.Text))\n\t}\n}\n\nfunc index(res http.ResponseWriter, req *http.Request) {\n\tfmt.Fprintf(res, \"%s\", `\n\u003c!DOCTYPE html\u003e\n\u003chtml lang=\"en\"\u003e\n\u003chead\u003e\n\t...\n\u003c/head\u003e\n\u003cbody\u003e\n\t\u003ctemplate id=\"user-message\"\u003e\n\t\t...\n\t\u003c/template\u003e\n\n\t\u003ctemplate id=\"bot-message\"\u003e\n\t\t...\n\t\u003c/template\u003e\n\n\t\u003cdiv id=\"chat\"\u003e\n\t\t\u003cdiv id=\"messages\"\u003e\u003c/div\u003e\n\t\t\u003cform id=\"form\" action=\"/\" method=\"post\"\u003e\n\t\t\t\u003cinput type=\"text\" name=\"message\" id=\"message\" placeholder=\"enter\" required=\"required\"\u003e\n\t\t\t\u003cbutton type=\"submit\"\u003eSend\u003c/button\u003e\n\t\t\u003c/form\u003e\n\t\u003c/div\u003e\n\n\t\u003cscript src=\"/lib/chat.js\"\u003e\u003c/script\u003e\n\u003c/body\u003e\n\u003c/html\u003e`,\n\t)\n}\n```\n\n`lib/chat.js`\n\n```js\nimport Chat from \"@suy/bots-web-client\";\n\nwindow.addEventListener(\"DOMContentLoaded\", loaded);\nasync function loaded() {\n    const chat = new Chat(`ws://${window.location.host}/chat`);\n    await chat.open();\n\n    const form = document.querySelector(\"#form\");\n    const message = document.querySelector(\"#message\");\n    form.addEventListener(\"submit\", (e) =\u003e {\n        e.preventDefault();\n\tchat.say({ text: message.value });\n\t...\n    });\n\n    for await (const msg of chat.messages()) {\n        log(\"received\", msg);\n\t...\n    }\n\n    log(\"closed\")\n}\n```\n\n### Client\n\nThere is a very simple browser client implemented at [web/browser](web/browser) that provides incoming messages over an async iterator (for-await in the above example).\n\n### Conversations\n\n\u003e [full example](examples/web/bots/conversations.go)\n\n```go\npassword := web.NewConversation()\n\npassword.On(\"start\", func(msg *web.Message, controls *web.Controls) {\n\tmsg.Text = \"Please specify a length\"\n\tcontrols.Bot().Say(msg)\n\tcontrols.To(\"length\")\n})\n\npassword.On(\"length\", func(msg *web.Message, controls *web.Controls) {\n\t_, err := strconv.ParseInt(msg.Text, 10, 64)\n\tif err != nil {\n\t\t// NOTE: we send a message and stay in this state, instead of transitioning\n\t\t// anywhere. This is how \"repeat\" works. This will repeat indefinitely until\n\t\t// a valid value is obtained, or can set a state variable to repeat a fixed\n\t\t// number of times before calling 'controls.End()'.\n\t\tcontrols.Bot().Say(\u0026web.Message{Text: \"Invalid Value, Please try again\"})\n\t\treturn\n\t}\n\n\tcontrols.Set(\"length\", msg.Text)\n\n\tmsg.Text = \"Do you want numbers\"\n\tcontrols.Bot().Say(msg)\n\n\tcontrols.To(\"numbers\")\n})\n\npassword.On(\"numbers\", func(msg *web.Message, controls *web.Controls) {\n\tif lt := strings.ToLower(msg.Text); lt == \"no\" || lt == \"nope\" {\n\t\tcontrols.Bot().Say(\u0026web.Message{Text: \"Not Using Numbers\"})\n\t\tcontrols.Set(\"numbers\", \"false\")\n\t} else {\n\t\tcontrols.Bot().Say(\u0026web.Message{Text: \"Using Numbers\"})\n\t\tcontrols.Set(\"numbers\", \"true\")\n\t}\n\n\tcontrols.Bot().Say(\u0026web.Message{Text: \"Do you want special characters\"})\n\tcontrols.To(\"characters\")\n})\n\npassword.On(\"characters\", func(msg *web.Message, controls *web.Controls) {\n\tcharacters := true\n\n\t...\n\n\tcontrols.Bot().Say(\u0026web.Message{Text: \"Your Password is '\" + ans + \"'\"})\n\tcontrols.End()\n})\n```\n\n### Storage\n\nThere are 3 main storage interfaces,\n\n- [ControllerStore](https://godoc.org/suy.io/bots/web#ControllerStore)\n\n  A controller store essentially stores the current bots by ID. [Example Redis Implementation](https://godoc.org/suy.io/bots/web/contrib/redis#RedisControllerStore)\n\n- [ItemStore](https://godoc.org/suy.io/bots/web#ItemStore)\n\n  An ItemStore stores items (threads, messages) for a single bot. [Example Redis Implementation](https://godoc.org/suy.io/bots/web/contrib/redis#RedisItemStore)\n\n- [ConversationStore](https://godoc.org/suy.io/bots/web#RedisConversationStore)\n\n  A ConversationStore stores and manages conversation state. [Example Redis Implementation](https://godoc.org/suy.io/bots/web/contrib/redis#RedisItemStore)\n\n### BotID Creation\n\n[BotIDCreator](https://godoc.org/suy.io/bots/web#BotIDCreator) is a function that can be passed to [WithBotIDCreator](https://godoc.org/suy.io/bots/web#WithBotIDCreator) when initializing controller to generate bot IDs. The default one generates a timestamp to get an id, so every connection is unique. The actual request is passed along in the function so any request parameters can be used to detect identity. A very simple example using cookies can be seen at https://github.com/suyash/bots/blob/master/examples/web/bots/redis.go#L31-L43\n\n### Issues\n\n- [ ] support for sending more than text from client\n- [ ] shell client.\n- [ ] MemoryItemStore can be made O(nlogn).\n- [ ] HTTP + SSE?\n\n## Slack\n\n```\ngo get suy.io/bots/slack\n```\n\n\u003e more examples in [examples/slack](examples/slack)\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\n\t\"suy.io/bots/slack\"\n\t\"suy.io/bots/slack/api/chat\"\n)\n\nfunc main() {\n\tc, err := slack.NewController()\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tb, err := c.CreateBot(\"BOT_TOKEN\") // https://my.slack.com/services/new/bot\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tif err := b.Start(); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfor msg := range c.DirectMessages() {\n\t\t_, err := msg.Reply(chat.TextMessage(msg.Text)) // echoes the same message back\n\t\tif err != nil {\n\t\t\tlog.Fatal(\"error:\", err)\n\t\t}\n\t}\n}\n```\n\n### Conversations\n\nThe following example is a password generation conversation. ([full example](examples/slack/conversations/main.go))\n\n```go\npassword := slack.NewConversation()\n\npassword.On(\"start\", func(msg *chat.Message, controls *slack.Controls) {\n\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Please specify a length\"))\n\tcontrols.To(\"length\")\n})\n\npassword.On(\"length\", func(msg *chat.Message, controls *slack.Controls) {\n\t_, err := strconv.ParseInt(msg.Text, 10, 64)\n\tif err != nil {\n\t\t// NOTE: we send a message and stay in this state, instead of transitioning\n\t\t// anywhere. This is how \"repeat\" works. This will repeat indefinitely until\n\t\t// a valid value is obtained, or can set a state variable to repeat a fixed\n\t\t// number of times before calling 'controls.End()'.\n\t\tcontrols.Bot().Reply(chat.RTMMessage(msg), \u0026chat.Message{Text: \"Invalid Value, Please try again\"})\n\t\treturn\n\t}\n\n\tcontrols.Set(\"length\", msg.Text)\n\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Do you want numbers\"))\n\tcontrols.To(\"numbers\")\n})\n\npassword.On(\"numbers\", func(msg *chat.Message, controls *slack.Controls) {\n\tif lt := strings.ToLower(msg.Text); lt == \"no\" || lt == \"nope\" {\n\t\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Not Using Numbers\"))\n\t\tcontrols.Set(\"numbers\", \"false\")\n\t} else {\n\t\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Using Numbers\"))\n\t\tcontrols.Set(\"numbers\", \"true\")\n\t}\n\n\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Do you want special characters\"))\n\tcontrols.To(\"characters\")\n})\n\npassword.On(\"characters\", func(msg *chat.Message, controls *slack.Controls) {\n\t...\n\n\tcontrols.Bot().Reply(chat.RTMMessage(msg), chat.TextMessage(\"Your Password is '\"+ans+\"'\"))\n\tcontrols.End()\n})\n```\n\n### Connector\n\nA connector is a websocket connection pool defined at https://godoc.org/suy.io/bots/slack#Connector. The connector package provides a type that can manage connections. By default all connections are also a part of the same service, but if required, can be abstracted out and the two services can talk using any transport mechanism. Sample HTTP implementations are by [httpserver](https://godoc.org/suy.io/bots/slack/connector/contrib/httpserver) and [httpclient](https://godoc.org/suy.io/bots/slack/contrib/connector/httpclient) respectively. There is also [an example](examples/slack/compose).\n\n### Storage\n\nThere are 3 main storage interfaces\n\n- [BotStore](https://godoc.org/suy.io/bots/slack#BotStore)\n\n  This essentially stores `oauth.AccessResponse`s of all bots that have been authenticated with the service. A custom implementation can be provided by passing it inside `WithBotStore` function when initializing a controller. An example [redis implementation](https://godoc.org/suy.io/bots/slack/contrib/redis#RedisBotStore).\n\n- [ConversationStore](https://godoc.org/suy.io/bots/slack#ConversationStore)\n\n  This stores and manages conversation data and state. A custom implementation can be provided at initialization by using `WithConversationStore` when initializing a controller. An example [redis implementation](https://godoc.org/suy.io/bots/slack/contrib/redis#RedisConversationStore).\n\n### Issues\n\n- [ ] `*websocket.Conn` does not `Close()` and throws an error.\n\n- [ ] ffjson not generating fflib import for interactions\n\n- [ ] does not work on appengine, [because of using http.DefaultClient](https://github.com/suyash/bots/blob/master/slack/api/request.go#L63). Figure out a way to switch out the client.\n\n## build\n\nThe project uses [ffjson](https://github.com/pquerna/ffjson) to optimize JSON encoding/decoding.\n\nTo regenerate\n\n```\ngo generate ./...\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuyash%2Fbots","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsuyash%2Fbots","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsuyash%2Fbots/lists"}