├── .gitignore ├── Dockerfile ├── README.md ├── boot.go ├── bot.yml ├── cmd └── bot │ └── main.go ├── go.mod ├── init.sh ├── internal ├── bot │ ├── bot.go │ ├── middle │ │ └── middle.go │ └── start.go └── database │ └── database.go ├── locales └── en.yml ├── pkg └── .gitkeep └── sql └── 001_create_users.sql /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .vscode 3 | 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine as builder 2 | 3 | WORKDIR /src 4 | 5 | COPY go.mod go.sum ./ 6 | 7 | RUN go mod download 8 | 9 | COPY . . 10 | 11 | RUN go build -o bot ./cmd/bot 12 | 13 | FROM alpine 14 | 15 | WORKDIR /app 16 | 17 | COPY --from=builder /src/bot . 18 | 19 | COPY --from=builder /src/bot.yml . 20 | 21 | COPY --from=builder /src/locales locales 22 | 23 | COPY --from=builder /src/sql sql 24 | 25 | ENTRYPOINT ["/app/bot"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Telebot Template 2 | 3 | ```bash 4 | $ git clone https://github.com/go-telebot/template . 5 | $ chmod +x init.sh; ./init.sh 6 | NOTE The script will delete itself after the configuration. 7 | 8 | Project name: yourbot 9 | Module path: github.com/username/yourbot 10 | 11 | Dialect (sqlite3|mysql|postgres): postgres 12 | Driver (github.com/lib/pq): 13 | ``` 14 | 15 | ## Overview 16 | 17 | This is a project structure template for bots developed using [`telebot.v3`](https://github.com/tucnak/telebot/tree/v3). There are two ways of organizing the root, and the version in this branch sticks to [this project structure](https://github.com/golang-standards/project-layout). Prefer [a simpler layout](https://github.com/go-telebot/template/tree/alt) for simpler apps without `pkg` and `internal` directories, keeping every package in the root. But, in the huge projects, where you may have lots of packages as has to be hidden, as exposed, the separation becomes really useful and much more convenient. 18 | 19 | So, this is a good example of structuring your advanced bot, when there is too much code for an ordinary `main.go` file. 20 | 21 | ## Directories 22 | 23 | ### `/` 24 | 25 | The root package, with the name of the module, usually should contain highly generic data. Consider storing some kind of _bootstrap_ structure here, which defines the basic dependencies required for a successful application startup. It should be used later in `/internal` subpackages for proper initializing. 26 | 27 | ### `/locales` 28 | 29 | This directory consists of bot locales in `*.yml` files respectively to the `telebot/layout` format. If you don't need localization in your project, leave a single file and specify its locale code in a `lt.DefaultLocale("..")` call. 30 | 31 | ### `/sql` 32 | 33 | Optional, if you don't use a relational database. It's a directory of `*.sql` files, formatted in a way to use with `goose` migration tool. 34 | 35 | ### `/cmd` 36 | 37 | Main binaries for the project. The directory name for each application should match the name of the executable you want to have. Use `/cmd/bot` for the bot's primary executable. It is common to have a small main function that imports and invokes the code from the `/internal` and `/pkg` directories and nothing more. 38 | 39 | ### `/pkg` 40 | 41 | Library code that's ok to use by external applications. 42 | 43 | ### `/internal` 44 | 45 | Private application and library code. This is the code you don't want others importing into their applications or libraries. Note that this layout pattern is enforced by the Go compiler itself. 46 | 47 | ### `/internal/bot` 48 | 49 | The core of the bot. It consists of all the handlers and bot behavior, logically grouped in the files. Use `bot/middle` subpackage storing custom middlewares. 50 | 51 | For example, imagine your bot need to have settings implemented as an inline menu sent on the `/settings` command. There are several parameters to be configured, let's say the user's name and delivery address. Where you should put this logic? The best place is `settings.go` file in the `bot` package with three functions inside, which are responsible for sending settings menu, asking for a new value to update, and actual updating operation of the specific setting. That way we have three ascending actions relying on each other, and it makes them intuitive by gathering in one place. 52 | 53 | ### `/internal/database` 54 | 55 | A wrapper to simplify the communication with your database. If you're ok with using ORM in your projects, then most likely there is no need for you in this package. 56 | 57 | ### `/internal/worker` 58 | 59 | This becomes useful when you have some background routine to do. One file for each *worker* logic accordingly. 60 | 61 | ```go 62 | boot := Bootstrap{...} 63 | 64 | go worker.ProcessPayments(boot) 65 | go worker.CollectStatistics(boot) 66 | 67 | b.Start() 68 | ``` 69 | -------------------------------------------------------------------------------- /boot.go: -------------------------------------------------------------------------------- 1 | package ${PROJECT} 2 | 3 | import ( 4 | "${MODULE}/internal/database" 5 | ) 6 | 7 | type Bootstrap struct { 8 | DB *database.DB 9 | // ... 10 | } -------------------------------------------------------------------------------- /bot.yml: -------------------------------------------------------------------------------- 1 | settings: 2 | token_env: TOKEN 3 | parse_mode: html 4 | 5 | config: 6 | 7 | commands: 8 | 9 | buttons: 10 | 11 | markups: -------------------------------------------------------------------------------- /cmd/bot/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "${MODULE}" 8 | "${MODULE}/internal/bot" 9 | "${MODULE}/internal/database" 10 | ) 11 | 12 | func main() { 13 | db, err := database.Open(os.Getenv("DB_URL")) 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | boot := ${PROJECT}.Bootstrap{ 19 | DB: db, 20 | } 21 | 22 | b, err := bot.New("bot.yml", boot) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | b.Start() 28 | } -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module ${MODULE} 2 | 3 | go 1.16 -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo -e "\033[0;31mNOTE\033[0m The script will delete itself after the configuration." 4 | 5 | echo 6 | 7 | while [[ -z "$PROJECT" ]]; do 8 | echo -n -e "\033[0;34mProject name:\033[0m "; read PROJECT 9 | done 10 | 11 | while [[ -z "$MODULE" ]]; do 12 | echo -n -e "\033[0;34mModule path:\033[0m "; read MODULE 13 | done 14 | 15 | echo 16 | 17 | while [[ -z "$SQL_DIALECT" ]]; do 18 | echo -n -e "\033[0;34mDialect (sqlite3|mysql|postgres):\033[0m "; read SQL_DIALECT 19 | done 20 | 21 | case $SQL_DIALECT in 22 | sqlite3) DEF_DRIVER="github.com/mattn/go-sqlite3" ;; 23 | mysql) DEF_DRIVER="github.com/go-sql-driver/mysql" ;; 24 | postgres) DEF_DRIVER="github.com/lib/pq" ;; 25 | esac 26 | 27 | echo -n -e "\033[0;34mDriver ($DEF_DRIVER):\033[0m "; read SQL_DRIVER 28 | if [ -z $SQL_DRIVER ]; then SQL_DRIVER=$DEF_DRIVER; fi 29 | 30 | MODULE=$(echo $MODULE | sed "s#\.#\\\.#g" | sed "s#/#\\\/#g") 31 | SQL_DRIVER=$(echo $SQL_DRIVER | sed "s#\.#\\\.#g" | sed "s#/#\\\/#g") 32 | 33 | grep --exclude={.git,\*.sh} -rl '${PROJECT}' . | while read -r path; do 34 | sed -i '' -e "s/\${PROJECT}/$PROJECT/g" $path 35 | done 36 | 37 | grep --exclude={.git,\*.sh} -rl '${MODULE}' . | while read -r path; do 38 | sed -i '' -e "s/\${MODULE}/$MODULE/g" $path 39 | done 40 | 41 | grep --exclude={.git,\*.sh} -rl '${SQL_DIALECT}' . | while read -r path; do 42 | sed -i '' -e "s/\${SQL_DIALECT}/$SQL_DIALECT/g" $path 43 | done 44 | 45 | grep --exclude={.git,\*.sh} -rl '${SQL_DRIVER}' . | while read -r path; do 46 | sed -i '' -e "s/\${SQL_DRIVER}/$SQL_DRIVER/g" $path 47 | done 48 | 49 | echo 50 | 51 | go mod tidy 52 | rm pkg/.gitkeep 53 | rm README.md 54 | rm init.sh -------------------------------------------------------------------------------- /internal/bot/bot.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | "${MODULE}" 5 | "${MODULE}/internal/database" 6 | 7 | tele "gopkg.in/telebot.v3" 8 | "gopkg.in/telebot.v3/layout" 9 | "gopkg.in/telebot.v3/middleware" 10 | ) 11 | 12 | type Bot struct { 13 | *tele.Bot 14 | *layout.Layout 15 | db *database.DB 16 | } 17 | 18 | func New(path string, boot ${PROJECT}.Bootstrap) (*Bot, error) { 19 | lt, err := layout.New(path) 20 | if err != nil { 21 | return nil, err 22 | } 23 | 24 | b, err := tele.NewBot(lt.Settings()) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | if cmds := lt.Commands(); cmds != nil { 30 | if err := b.SetCommands(cmds); err != nil { 31 | return nil, err 32 | } 33 | } 34 | 35 | return &Bot{ 36 | Bot: b, 37 | Layout: lt, 38 | db: boot.DB, 39 | }, nil 40 | } 41 | 42 | func (b *Bot) Start() { 43 | // Middlewares 44 | b.Use(middleware.Logger()) 45 | b.Use(middleware.AutoRespond()) 46 | b.Use(b.Middleware("en")) 47 | 48 | // Handlers 49 | b.Handle("/start", b.onStart) 50 | 51 | b.Bot.Start() 52 | } 53 | -------------------------------------------------------------------------------- /internal/bot/middle/middle.go: -------------------------------------------------------------------------------- 1 | package middle 2 | -------------------------------------------------------------------------------- /internal/bot/start.go: -------------------------------------------------------------------------------- 1 | package bot 2 | 3 | import ( 4 | tele "gopkg.in/telebot.v3" 5 | ) 6 | 7 | func (b Bot) onStart(c tele.Context) error { 8 | return c.Send( 9 | b.Text(c, "start"), 10 | b.Markup(c, "menu"), 11 | tele.NoPreview, 12 | ) 13 | } -------------------------------------------------------------------------------- /internal/database/database.go: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import ( 4 | "database/sql" 5 | 6 | _ "${SQL_DRIVER}" 7 | ) 8 | 9 | type DB struct { 10 | *sql.DB 11 | } 12 | 13 | func Open(url string) (*DB, error) { 14 | db, err := sql.Open("${SQL_DIALECT}", url) 15 | if err != nil { 16 | return nil, err 17 | } 18 | return &DB{DB: db}, nil 19 | } -------------------------------------------------------------------------------- /locales/en.yml: -------------------------------------------------------------------------------- 1 | start: |- 2 | Hello, world! -------------------------------------------------------------------------------- /pkg/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/go-telebot/template/53b8fe7b387aca8b00b3d488eeb526b75c4be608/pkg/.gitkeep -------------------------------------------------------------------------------- /sql/001_create_users.sql: -------------------------------------------------------------------------------- 1 | -- +goose Up 2 | 3 | create table users ( 4 | created_at timestamptz not null default now(), 5 | updated_at timestamptz not null default now(), 6 | id bigint primary key 7 | ); --------------------------------------------------------------------------------