{"id":13340583,"url":"https://github.com/drewlakee/chattweiler","last_synced_at":"2026-05-09T09:50:06.213Z","repository":{"id":42870834,"uuid":"461597231","full_name":"drewlakee/chattweiler","owner":"drewlakee","description":"🐶 A chat toy","archived":false,"fork":false,"pushed_at":"2022-11-05T12:07:19.000Z","size":244,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-11T18:51:37.226Z","etag":null,"topics":["api","bot","chatbot","golang","postgres","vk","vkontakte","warden"],"latest_commit_sha":null,"homepage":"https://vk.com/dev/bots_docs","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/drewlakee.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":"2022-02-20T19:43:16.000Z","updated_at":"2022-11-01T14:42:40.000Z","dependencies_parsed_at":"2023-01-22T02:52:03.566Z","dependency_job_id":null,"html_url":"https://github.com/drewlakee/chattweiler","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drewlakee%2Fchattweiler","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drewlakee%2Fchattweiler/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drewlakee%2Fchattweiler/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/drewlakee%2Fchattweiler/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/drewlakee","download_url":"https://codeload.github.com/drewlakee/chattweiler/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247838388,"owners_count":21004576,"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":["api","bot","chatbot","golang","postgres","vk","vkontakte","warden"],"created_at":"2024-07-29T19:23:37.335Z","updated_at":"2026-05-09T09:50:01.164Z","avatar_url":"https://github.com/drewlakee.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Chattweiler\r\n\r\nChattweiler is a server-side application that handles events which come from VK community chat\r\n\r\nInspirations for the development are:\r\n- Requesting of content from custom sources\r\n- Customized responses\r\n- Fake users filtering\r\n- Forcing to join a community, in case, if you're wanting to be a part of a chat\r\n- Fun 🤖\r\n\r\n## Features\r\n\r\n- Customized chat responses for chat joins, leavings, warnings, failed commands etc.\r\n- Customized commands for content (e.g. pictures, audio, videos)\r\n- Automatic membership checking and warning\r\n\r\n## Application context schema\r\n\r\n\u003cdiv align=\"center\"\u003e\r\n    \u003cimg src=\"https://user-images.githubusercontent.com/44072343/200119060-c7feda44-b3ca-40ab-afb5-23cef2fddc8a.jpg\" alt=\"logo\"\u003e\r\n\u003c/div\u003e\r\n\r\nAs you might already have noticed the application uses for storage [Yandex Object Storage](https://cloud.yandex.com/en-ru/services/storage) solution, \r\nso for using the application you have to have access to such resource. Storage configuration for the application is mentioned further in Quickstart.\r\n\r\nIn brief, the application operates over csv files that are stored in cloud. That type of file is picked up because it's very straightforward to store and edit.\r\nThe application caches these files and invalidates over time. That way makes positive effect on performance during events handling.\r\n\r\n# Quickstart\r\n## Application deployment preparations\r\n### Setting up a community chat\r\n\r\n1. Create a public community in [VK](https://vk.com)\r\n2. Create a new chat inside the community: Manage \u003e Chats \u003e Create Chat\r\n3. Once you got to a page of the chat, remember the chat's number (e.g. 9)\r\n\r\n![Screenshot 2022-10-31 165551](https://user-images.githubusercontent.com/44072343/199024841-a4da7cb9-829d-43ed-9abc-df60378b124f.png)\r\n\r\n### Setting up a Yandex Object Storage\r\n\r\nThe application uses several [buckets](https://console.cloud.yandex.com/folders): \r\n\r\n- commands\r\n  - commands_production.csv\r\n- membership-warnings (optional, used automatically by the application if so configured)\r\n- phrases\r\n  - phrases_production.csv\r\n\r\nFor further configurations you have to have such buckets in your environment.\r\n\r\n#### Phrases\r\n\r\nFile must contain rows with a specific structure:\r\n\r\n```go\r\ntype PhraseType string\r\n\r\nconst (\r\n\t// for new users in chat\r\n\tWelcomeType           PhraseType = \"welcome\"\r\n\t// for users who left\r\n\tGoodbyeType           PhraseType = \"goodbye\"\r\n\t// for users who in chat but not in a community\r\n\tMembershipWarningType PhraseType = \"membership_warning\"\r\n\t// for some general info like commands description\r\n\tInfoType              PhraseType = \"info\"\r\n\t// for responses with content requests\r\n\tContentRequestType    PhraseType = \"content_request\" \r\n\t// for cases where the application failed to find something\r\n\tRetryType             PhraseType = \"retry_request\"\r\n)\r\n\r\ntype Phrase struct {\r\n\tPhraseID   int        `csv:\"phrase_id\"`\r\n\t// used for probability\r\n\t// https://en.wikipedia.org/wiki/Fitness_proportionate_selection\r\n\t// in brief, if its value more than others` value it has more chances to be picked up\r\n\tWeight     int        `csv:\"weight\"`\r\n\tPhraseType PhraseType `csv:\"phrase_type\"`\r\n\t// use null if command is not supposed to use it\r\n\tVkAudioId  string     `csv:\"vk_audio_id\"`\r\n\t// use null if command is not supposed to use it\r\n\tVkGifId    string     `csv:\"vk_gif_id\"`\r\n\t// actual text of a phrase\r\n\tText       string     `csv:\"text\"`\r\n}\r\n```\r\n```\r\ncsv file:\r\n\r\nphrase_id1,weight,phrase_type,vk_audio_id,vk_gif_id,text\r\nphrase_id2,weight,phrase_type,vk_audio_id,vk_gif_id,text\r\n...\r\n1,100,welcome,null,doc120747496_641221964,\"Hello there, %username%!\"\r\n5,100,membership_warning,null,doc120747496_641228085,\"%username%, this chat is only for community members 👻\\nPlease subscribe quickly!\"\r\n18,100,retry_request,null,doc120747496_646353718,\"%username%, oops, we've failed, try again 👉🏻👈🏻\"\r\n```\r\n\r\nPhrases are used for responses on different types of events.\r\n\r\n#### Commands\r\n\r\nFile must contain rows with a specific structure: \r\n\r\n```go\r\ntype CommandType string\r\n\r\nconst (\r\n\tInfoCommand    CommandType = \"info\"\r\n\tContentCommand CommandType = \"content\"\r\n)\r\n\r\n// CsvCommand storage specific object of Command\r\ntype CsvCommand struct {\r\n\tID                int         `csv:\"id\"`\r\n\tCommands          string      `csv:\"commands\"`\r\n\tType              CommandType `csv:\"command_type\"`\r\n\tMediaContentTypes string      `csv:\"media_types\"`\r\n\tCommunityIDs      string      `csv:\"community_ids\"`\r\n}\r\n```\r\n\r\n```\r\ncsv file:\r\n\r\nid1,\"alias1,alias2\",command_type,\"media_type1,media_type2\",\"community_id1,community_id2\"\r\nid2,\"alias1,alias2\",command_type,\"media_type1,media_type2\",\"community_id1,community_id2\"\r\n...\r\n6,\"jazzy music,🥸\",content,audio,jazzjazz\r\n26,\"👾,commands\",info,,\r\n```\r\n\r\n- A command could have several aliases which users can call on in chat\r\n\r\n- A command can use several communities to fetch content from it\r\n\r\n- A command can has several media-content types to fetch from communities (randomly chosen per call)\r\n\r\n- `command_type` used for different types of command. There's a couple of them right now, command with `info` type sends in chat a phrase with the same type \r\n\r\n#### Membership warnings\r\n\r\n```go\r\ntype MembershipWarning struct {\r\n\tWarningID      int       `csv:\"warning_id\"`\r\n\tUserID         int       `csv:\"user_id\"`\r\n\tUsername       string    `csv:\"username\"`\r\n\t// when user got first warning in chat about community membership\r\n\tFirstWarningTs time.Time `csv:\"first_warning_ts\"`\r\n\t// a period in which he has to subscribe, or he'll be kicked eventually\r\n\tGracePeriod    string    `csv:\"grace_period\"`\r\n\t// actual status of a warning \r\n\t// if a user got a warning and subscribed, then status will be updated\r\n\tIsRelevant     bool      `csv:\"is_relevant\"`\r\n}\r\n```\r\n\r\nIf you want to use such feature, then that structure will be used to upload actual status about warnings to a storage bucket by days.\r\n\r\n- 2022-23-10\r\n- 2022-24-10\r\n- 2022-25-10\r\n- .....\r\n\r\nSuch files occur only if warnings happen in a day, so there could be some gaps between files.\r\n\r\n## Local application deployment\r\n\r\n### Application configurations\r\n\r\n**Mandatory configurations**\r\n\r\n- `vk.community.bot.token`\r\n\r\nA specific token for your community (e.g. \"956c94e96...6039be4e\")\r\n\r\nHow to get: Enter your community \u003e Manage \u003e Settings \u003e API usage \u003e Access tokens\r\n\r\n- `vk.community.id`\r\n\r\nA specific community id (e.g. \"161...464\" as a number)\r\n\r\nYou can get it somewhere in a community or by picking up from some wallpost's url `https://vk.com/community?w=wall-\u003cid\u003e_3394`\r\n\r\n- `vk.community.chat.id`\r\n\r\nAn actual number of a chat, we've mentioned it earlier in the Quickstart\r\n\r\n- Yandex Object Storage\r\n  - `yandex.object.storage.access.key.id` (e.g. some token like `YCN1Ze...SJv`)\r\n  - `yandex.object.storage.secret.access.key` (e.g. some token like `YCA...cQ`)\r\n  - `yandex.object.storage.region` (e.g. `ru-central1`)\r\n  - `yandex.object.storage.phrases.bucket` (e.g. `phrases-bucket`)\r\n  - `yandex.object.storage.phrases.bucket.key` (e.g. `phrases_production.csv`)\r\n  - `yandex.object.storage.content.command.bucket` (e.g. `command-bucket`)\r\n  - `yandex.object.storage.content.command.bucket.key` (e.g `command_production.csv`)\r\n  - `yandex.object.storage.membership.warning.bucket` (e.g. `membership-warning-bucket`)\r\n\r\nRead [the documentation](https://cloud.yandex.com/en-ru/docs/storage/) how to get these values\r\n\r\n**Optional configurations**\r\n\r\n- `vk.admin.user.token` (by default not specified) if you're supposed to use content requesting, you have to have that one. Read [the documentation](https://dev.vk.com/api/access-token/implicit-flow-user) how to get such token\r\n\r\n- `chat.warden.membership.check.interval` (default: `10m`) a periodic interval after which the application goes to VK-API to compare actual members in a chat\r\n- `chat.warden.membership.grace.period` (default: `1h`) a period after which the application checks if a warned user subscribed to a community\r\n- `chat.use.first.name.instead.username` (default: `false`) either uses actual name of a user or his url-uid for communication (e.g. \"John\" or \"john_2001\")\r\n- `content.command.cache.refresh.interval` (default: `15m`) a periodic interval after which the application invalidates its cache with commands\r\n- `content.requests.queue.size` (default: `100`) a buffered channel size between event handler and command executors\r\n- `content.garbage.collectors.cleaning.interval` (default: `10m`) a periodic interval after which the application removes already unused content collectors which are cached\r\n- `phrases.cache.refresh.interval` (default: `15m`) a periodic interval after which the application invalidates its cache with phrases\r\n- `content.audio.max.cached.attachments` (default: `100`) a max number of content that could be stored in an application's cache\r\n- `content.audio.cache.refresh.threshold` (default: `0.2`) a threshold for a cache with content after which the cache fills out by new content\r\n- `content.picture.max.cached.attachments` (default: `100`) a max number of content that could be stored in an application's cache\r\n- `content.picture.cache.refresh.threshold` (default: `0.2`) a threshold for a cache with content after which the cache fills out by new content\r\n- `content.video.max.cached.attachments` (default: `100`) a max number of content that could be stored in an application's cache\r\n- `content.video.cache.refresh.threshold` (default: `0.2`) a threshold for a cache with content after which the cache fills out by new content\r\n- `bot.functionality.welcome.new.members` (default: `true`) enables welcome functionality\r\n- `bot.functionality.goodbye.members` (default: `true`) enables goodbye functionality\r\n- `bot.functionality.membership.checking` (default: `false`) enables membership checking functionality\r\n- `bot.functionality.content.commands` (default: `false`) enables requesting of media content functionality\r\n- `bot.log.file` (default: `false`) enables writing of a log file near an execution file\r\n\r\n### Deployment\r\n\r\n1. Clone the project `git clone git@github.com:drewlakee/chattweiler.git`\r\n2. Build a docker image `./chattweiler/build.sh`\r\n3. Create a configuration file `touch bot.env` and fill the mandatory variables\r\n4. Run a container with the image you've just built `./chattweiler/run.sh`\r\n5. Make fun out of it 👾\r\n\r\n\u003cdetails\u003e\r\n  \u003csummary\u003e\u003cb\u003eUsage examples\u003c/b\u003e\u003c/summary\u003e\r\n  \r\n![Screenshot 2022-10-31 at 16-11-04 Messenger](https://user-images.githubusercontent.com/44072343/199244389-1d16c36d-5136-4223-b8c8-959e29da4aeb.png)\r\n  \r\n![Screenshot 2022-10-31 at 16-13-06 Messenger](https://user-images.githubusercontent.com/44072343/199244378-b49e6aa0-7d94-41a7-b723-da94ed4d7ec5.png)\r\n\r\n\u003c/details\u003e\r\n\r\n** If you are supposed to use file logging, you can make a volume by adding to the command in `./chattweiler/run.sh` a piece of settings `docker run -v /path/to/your/log/directory:/application/logs ...`\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrewlakee%2Fchattweiler","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdrewlakee%2Fchattweiler","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdrewlakee%2Fchattweiler/lists"}