{"id":37207741,"url":"https://github.com/arkjxu/loafer","last_synced_at":"2026-01-14T23:53:10.194Z","repository":{"id":55902488,"uuid":"317822962","full_name":"arkjxu/loafer","owner":"arkjxu","description":"Slack App Building library to handle app distribution, commands, interactions.","archived":true,"fork":false,"pushed_at":"2021-02-06T01:20:34.000Z","size":40,"stargazers_count":1,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-25T22:32:29.776Z","etag":null,"topics":["go","golang","nike","productivity","slack","slackbot","slackbot-framework"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/arkjxu.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":"2020-12-02T10:14:15.000Z","updated_at":"2023-01-28T08:20:57.000Z","dependencies_parsed_at":"2022-08-15T09:01:05.223Z","dependency_job_id":null,"html_url":"https://github.com/arkjxu/loafer","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/arkjxu/loafer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arkjxu%2Floafer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arkjxu%2Floafer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arkjxu%2Floafer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arkjxu%2Floafer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/arkjxu","download_url":"https://codeload.github.com/arkjxu/loafer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/arkjxu%2Floafer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28439575,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T22:37:52.437Z","status":"ssl_error","status_checked_at":"2026-01-14T22:37:31.496Z","response_time":107,"last_error":"SSL_read: 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":["go","golang","nike","productivity","slack","slackbot","slackbot-framework"],"created_at":"2026-01-14T23:53:09.555Z","updated_at":"2026-01-14T23:53:10.186Z","avatar_url":"https://github.com/arkjxu.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Loafer\n\nNote: \n```\nThis is a quick library to help with my development work at Nike Inc, please check the supported features before you use.\nFeel free to fork and add your features if needed\n```\n\nA Simple Slack App library to help you quickly spin up Slack Apps that are capable of app distribution and signature checking without the hassle.\n\n* All command, interactions, events endpoints are secured with slack signature checking.\n* App distribution is automatically enabled, howered, you need to handle the token storage by using the `OnAppInstall` function\n* You can enable custom routes if needed, such as for external select inputs for slack, or for your other service needs\n\n## Supported Features\n* Block Kit UI:\n  * Button - Action\n  * Text Section\n  * Text Fields\n  * Modal\n  * Actions\n  * Plain Text Input\n  * (multi/single) Static Select Input\n  * (multi/single) External Select Input\n  * Multi-Conversation Select Input\n  * (multi/single) User Select Input\n  * Date Picker Input\n  * Time Picker Input\n  * Radio Input\n  * Checkbox Input\n  * Header\n  * Context\n  * Image\n  * Divider\n* Slack API:\n  * Open View\n  * Update View\n  * Find User by Email\n  * Find User by Slack ID\n  * Update Message\n  * Post Message\n  * File upload\n\n\n## Slack Context\n\nAll handlers are functions with the `loafer.SlackContext` parameter passed to it, and the format is as followed:\n```golang\ntype SlackContext struct {\n\tBody      []byte              // Body of the request\n\tToken     string              // Token of the corresponding workspace\n\tWorkspace string              // Workspace where event is coming from\n\tReq       *http.Request       // http request\n\tRes       http.ResponseWriter // http response\n}\n```\n\n## To Start an App\n```golang\npackage main\n\n// TokenCache - Implementation of the loafer.TokensCache interface, this allows you to control how you store/access your tokens\ntype TokenCache struct {\n\ttokens map[string]string\n}\n\n// Get - Getting the token for the corresponding workspace\nfunc (t *TokenCache) Get(workspace string) string {\n\treturn t.tokens[workspace]\n}\n\n// Set - Setting the token for the corresponding workspace\nfunc (t *TokenCache) Set(workspace string, token string) {\n\tt.tokens[workspace] = token\n}\n\n// Remove - Removing the token for the corresponding workspace\nfunc (t *TokenCache) Remove(workspace string) {\n\tdelete(t.tokens, workspace)\n}\n\n// main - entrypoint of program\nfunc main {\n  // Initialize an instance of the TokenCache\n\tmyTokenCache := TokenCache{\n\t\ttokens: make(map[string]string)}\n  \n  // Set up the options for the slack app, clientId \u0026 client secret is not needed if you don't need app distribution\n\topts := loafer.SlackAppOptions{\n\t\tName:          \"Dev Bot\",\n\t\tPrefix:        \"dev\",\n\t\tTokensCache:   \u0026myTokenCache,\n\t\tSigningSecret: \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\",\n\t\tClientID:      \"xxxxxxxxxxxx.xxxxxxxxxx\",\n\t\tClientSecret:  \"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}\n    \n  // Initialize your Slack App with the options\n\tapp := loafer.InitializeSlackApp(\u0026opts)\n  \n  // Add handler to command /coaching\n\tapp.OnCommand(\"/coaching\", handleDevCommand)\n  \n  // Serve the app on PORT 8080, you can use callback here if needed\n\tapp.ServeApp(8080, nil)\n}\n```\n\nApp will now serve on the route:\n\nhttp://0.0.0.0:8080/{prefix}/install - For app distribution\nhttp://0.0.0.0:8080/{prefix}/events - For event subscription\nhttp://0.0.0.0:8080/{prefix}/commands - For app commands\nhttp://0.0.0.0:8080/{prefix}/interactions - For app interactions\nhttp://0.0.0.0:8080/{prefix}/${custom_route_pattern} - For app custom routes\n\n# API Reference\n\n## App\n\n### InitializeSlackApp(opts *SlackAppOptions) SlackApp\n\nReturns:\n* `app` SlackApp\n\nGet an instance of a slack app with options:\n- `Name` - Name of slack app\n- `Prefix` - Prefix of slack app route\n- `TokensCache` - Token cache that implemented the TokensCache interface from loafer\n- `ClientSecret` - Client secret of slack app, used for app distribution\n- `ClientID` - Client ID of slack app, used for app distribution\n- `SigningSecret` - Signning secret for slack app, used for slack request verification\n\n### ServeApp(port uint16, cb func())\n\nServe Slack app on port and cb when server first starts\n\n### Close(ctx context.Context)\n\nShutdown Slack app\n\n### OnCommand(cmd string, handler func(ctx *SlackContext))\n\nAdd handler to command\n\n### RemoveCommand(cmd string)\n\nRemove handler to command\n\n### OnAction(actionID string, handler func(ctx *SlackContext))\n\nAdd handler to action\n\n### OnShortcut(callbackID string, handler func(ctx *SlackContext))\n\nAdd handler to shortcut\n\n### OnViewSubmission(callbackID string, handler func(ctx *SlackContext))\n\nAdd handler to view submission\n\n### OnViewClose(callbackID string, handler, handler func(ctx *SlackContext))\n\nAdd handler to view close\n\n### OnEvent(eventType string, handler func(ctx *SlackContext))\n\nAdd handler to view close\n\n### OnError(handler func(res http.ResponseWriter, req *http.Request, err error))\n\nAdd handler to errors\n\n### OnAppInstall(cb func(installRes *SlackOauth2Response, res http.ResponseWriter, req *http.Request) bool\n\nReturns:\n* `avoidDefaultPage` bool\n\nhandle the app distribute, once app distribution is successfull, it will call the `cb` function,\ncb function should return a boolean value.\n- `true` - you want to use your own html/redirection\n- `false` - show default installation html page\n\n### CustomRoute(pattern string, handler func(res http.ResponseWriter, req *http.Request))\n\nAdd handler to a custom pattern\n\n### Response(ctx *SlackContext, code int, message []byte, headers map[string]string)\n\nResponse back to Slack\n\n### ConvertState(state ISlackBlockKitUI, dst interface{})\n\nResponse interaction event state to your form state struct type\n\n## Slack APIs\n\n### OpenView(view SlackModal, triggerID string, token string) error\n\nReturns:\n* `err` error\n\nOpen a modal within slack\n\n### UpdateView(view SlackInteractionView, viewID string, token string) error\n\nReturns:\n* `err` error\n\nUpdates a modal within slack\n\n### FindUserByEmail(email string, token string) (*SlackUser, error)\n\nReturns:\n* `user` SlackUser\n* `err` error\n\nFind a user within the slack workspace\n\n### FindUserByID(id string, token string) (*SlackUser, error)\n\nReturns:\n* `user` SlackUser\n* `err` error\n\nFind a user within the slack workspace\n\n### PostMessage(channel string, blocks ISlackBlockKitUI, text string, token string) error\n\nReturns:\n* `err` error\n\nPost a message to a slack channel/user/conversation\n\n### UpdateMessage(channel string, ts string, blocks ISlackBlockKitUI, text string, token string) error\n\nReturns:\n* `err` error\n\nUpdate a message of a slack channel/user/conversation given a time stamp of the original message\n\n### FileUpload(channels []string, filename string, content string, filetype string, token string) error\n\nReturns:\n* `err` error\n\nUpload a file to the Slack workspace and share to the list of channels/users/conversations\n\n## Block Kit UIs\n\n### MakeSlackButton(text string, value string, actionID string) SlackBlockButton\n\nReturns:\n* `button` SlackBlockButton\n\nMake a Block Kit Button\n\n### MakeSlackTextSection(text string) SlackBlockSection\n\nReturns:\n* `section` SlackBlockSection\n\nMake a Block Kit text section\n\n### MakeSlackTextFieldsSection(texts []string) SlackBlockTextFields\n\nReturns:\n* `textField` SlackBlockTextFields\n\nMake a Block Kit text field section\n\n### MakeSlackModal(title string, callbackID string, blocks ISlackBlockKitUI, submitText string, closeText string, notifyOnClose bool) SlackModal\n\nReturns:\n* `modal` SlackModal\n\nMake a Block Kit modal\n\n### MakeSlackActions(actions ISlackBlockKitUI) SlackBlockActions\n\nReturns:\n* `actions` SlackBlockActions\n\nMake a Block Kit actions section\n\n### MakeSlackModalTextInput(label string, placeholder string, actionID string, isMultiline bool, isDispatch bool, maxLength uint16) SlackInputElement\n\nReturns:\n* `input` SlackInputElement\n\nMake a Block Kit modal plain text input\n\n### MakeSlackModalStaticSelectInput(label string, placeholder string, options []SlackInputOption, initialOption *SlackInputOption, actionID string, isMulti bool, isOptional bool) SlackModalSelect\n\nReturns:\n* `select` SlackModalSelect\n\nMake a Block Kit modal static select input\n\n### MakeSlackModalExternalStaticSelectInput(label string, placeholder string, initialOption *SlackInputOption, actionID string, isMulti bool, minQueryLength uint16, isOptional bool) SlackModalSelect\n\nReturns:\n* `externalSelect` SlackModalSelect\n\nMake a Block Kit modal external static select input\n\n### MakeSlackBlockExternalStaticSelectInput(label string, placeholder string, initialOptions []SlackInputOption, actionID string, isMulti bool, minQueryLength uint16) SlackBlockSection\n\nReturns:\n* `blockSection` SlackBlockSection\n\nMake a Block Kit block section external section\n\n### MakeSlackBlockExternalStaticSelectInput(label string, placeholder string, initialOptions []SlackInputOption, actionID string, isMulti bool) SlackBlockSection\n\nReturns:\n* `blockSection` SlackBlockSection\n\nMake a Block Kit block section static select section\n\n### MakeSlackBlockButton(label string, text string, value string, actionID string) SlackBlockSection\n\nReturns:\n* `button` SlackBlockSection\n\nMake a Block Kit block section with button\n\n### MakeSlackModalMultiConversationSelectInput(label string, placeholder string, initialConversations []string, actionID string) SlackModalSelect\n\nReturns:\n* `select` SlackModalSelect\n\nMake a Block Kit modal multi conversation select list\n\n### MakeSlackActionExternalStaticSelectInput(label string, placeholder string, initialOption *SlackInputOption, actionID string, isMulti bool, minQueryLength uint16) SlackActionSelect\n\nReturns:\n* `select` SlackActionSelect\n\nMake a Block Kit modal external select list\n\n### MakeSlackModalMultiUserSelectInput(label string, placeholder string, initialUsers []string, actionID string) SlackModalSelect\n\nReturns:\n* `select` SlackModalSelect\n\nMake a Block Kit modal multi user select list\n\n### MakeSlackModalUserSelectInput(label string, placeholder string, initialUser string, actionID string) SlackModalSelect\n\nReturns:\n* `select` SlackModalSelect\n\nMake a Block Kit modal user select list\n\n### MakeSlackModalDatePickerInput(label string, placeholder string, initialDate string, actionID string) SlackInputElement\n\nReturns:\n* `picker` SlackInputElement\n\nMake a Block Kit modal date picker\n\n### MakeSlackModalCheckboxesInput(label string, placeholder string, options []SlackInputOption, initialOptions []SlackInputOption, actionID string) SlackInputElement\n\nReturns:\n* `picker` SlackInputElement\n\nMake a Block Kit modal checkbox picker\n\n### MakeSlackModalRadioInput(label string, placeholder string, options []SlackInputOption, actionID string) SlackInputElement\n\nReturns:\n* `picker` SlackInputElement\n\nMake a Block Kit modal radio picker\n\n### MakeSlackInputOption(text string, value string) SlackInputOption\n\nReturns:\n* `option` SlackInputOption\n\nMake a Block Kit select option\n\n### MakeSlackModalTimePickerInput(label string, placeholder string, initialTime string, actionID string) SlackInputElement\n\nReturns:\n* `picker` SlackInputElement\n\nMake a Block Kit time picker\n\n### MakeSlackHeader(text string) SlackBlockSection\n\nReturns:\n* `header` SlackBlockSection\n\nMake a Block Kit header\n\n### MakeSlackDivider() SlackDivider\n\nReturns:\n* `divider` SlackDivider\n\nMake a Block Kit divider\n\n### MakeSlackContext(text string) SlackBlockActions\n\nReturns:\n* `ctx` SlackBlockActions\n\nMake a Block Kit context section\n\n### MakeSlackImage(title string, imageURL string, altText string) SlackBlockAccessory\n\nReturns:\n* `img` SlackBlockAccessory\n\nMake a Block Kit image section\n\n# Contributing\nKevin Xu\n\n\n# License\nApache 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farkjxu%2Floafer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Farkjxu%2Floafer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Farkjxu%2Floafer/lists"}