mirror of
https://github.com/kemko/memes-telegram-integration.git
synced 2026-01-01 15:55:41 +03:00
feat: implement memogram
This commit is contained in:
1
.air/build-errors.log
Normal file
1
.air/build-errors.log
Normal file
@@ -0,0 +1 @@
|
|||||||
|
exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1
|
||||||
BIN
.air/memogram
Executable file
BIN
.air/memogram
Executable file
Binary file not shown.
3
.env.example
Normal file
3
.env.example
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
SERVER_ADDR=demo.usememos.com
|
||||||
|
ACCESS_TOKEN=your_access_token
|
||||||
|
BOT_TOKEN=your_bot_token
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
11
bin/memogram/main.go
Normal file
11
bin/memogram/main.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "github.com/usememos/memogram"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
service, err := memogram.NewService()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
service.Start()
|
||||||
|
}
|
||||||
22
client.go
Normal file
22
client.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package memogram
|
||||||
|
|
||||||
|
import (
|
||||||
|
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MemosClient struct {
|
||||||
|
AuthService v1pb.AuthServiceClient
|
||||||
|
UserService v1pb.UserServiceClient
|
||||||
|
MemoService v1pb.MemoServiceClient
|
||||||
|
ResourceService v1pb.ResourceServiceClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemosClient(conn *grpc.ClientConn) *MemosClient {
|
||||||
|
return &MemosClient{
|
||||||
|
AuthService: v1pb.NewAuthServiceClient(conn),
|
||||||
|
UserService: v1pb.NewUserServiceClient(conn),
|
||||||
|
MemoService: v1pb.NewMemoServiceClient(conn),
|
||||||
|
ResourceService: v1pb.NewResourceServiceClient(conn),
|
||||||
|
}
|
||||||
|
}
|
||||||
25
config.go
Normal file
25
config.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package memogram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/caarlos0/env"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
ServerAddr string `env:"SERVER_ADDR,required"`
|
||||||
|
BotToken string `env:"BOT_TOKEN,required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getConfigFromEnv() (*Config, error) {
|
||||||
|
err := godotenv.Load(".env")
|
||||||
|
if err != nil {
|
||||||
|
panic(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
config := Config{}
|
||||||
|
if err := env.Parse(&config); err != nil {
|
||||||
|
return nil, errors.Wrap(err, "invalid configuration")
|
||||||
|
}
|
||||||
|
return &config, nil
|
||||||
|
}
|
||||||
25
go.mod
Normal file
25
go.mod
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
module github.com/usememos/memogram
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible
|
||||||
|
github.com/usememos/memos v0.21.1-0.20240509141027-584c66906883
|
||||||
|
google.golang.org/grpc v1.63.2
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-telegram/bot v1.2.2
|
||||||
|
github.com/joho/godotenv v1.5.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
golang.org/x/net v0.24.0 // indirect
|
||||||
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
|
golang.org/x/text v0.15.0 // indirect
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be // indirect
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
)
|
||||||
36
go.sum
Normal file
36
go.sum
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs=
|
||||||
|
github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-telegram/bot v1.2.2 h1:LwGbSzjcSi0w4Ke8JUpgbBhJwwYTl2ITmhubeM2WvN8=
|
||||||
|
github.com/go-telegram/bot v1.2.2/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is=
|
||||||
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM=
|
||||||
|
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/usememos/memos v0.21.1-0.20240509141027-584c66906883 h1:eEEnUzReqdPbYZZtMTTes6p976POfHeW6lxz9LZuyFI=
|
||||||
|
github.com/usememos/memos v0.21.1-0.20240509141027-584c66906883/go.mod h1:EOB3cAQEaeYvVggKIe7oMyOJYQoZ0w2vTNS5uWUYSaU=
|
||||||
|
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
|
||||||
|
golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8=
|
||||||
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be h1:Zz7rLWqp0ApfsR/l7+zSHhY3PMiH2xqgxlfYfAfNpoU=
|
||||||
|
google.golang.org/genproto/googleapis/api v0.0.0-20240415180920-8c6c420018be/go.mod h1:dvdCTIoAGbkWbcIKBniID56/7XHTt6WfxXNMxuziJ+w=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be h1:LG9vZxsWGOmUKieR8wPAUR3u3MpnYFQZROPIMaXh7/A=
|
||||||
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240415180920-8c6c420018be/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
||||||
|
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
|
||||||
|
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
134
memogram.go
Normal file
134
memogram.go
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
package memogram
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/go-telegram/bot"
|
||||||
|
"github.com/go-telegram/bot/models"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
v1pb "github.com/usememos/memos/proto/gen/api/v1"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// userAccessTokenCache is a cache for user access token.
|
||||||
|
// Key is the user id from telegram.
|
||||||
|
// Value is the access token from memos.
|
||||||
|
var userAccessTokenCache sync.Map // map[int64]string
|
||||||
|
|
||||||
|
type Service struct {
|
||||||
|
config *Config
|
||||||
|
client *MemosClient
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewService() (*Service, error) {
|
||||||
|
config, err := getConfigFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to get config from env")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := grpc.Dial(config.ServerAddr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to connect to server", slog.Any("err", err))
|
||||||
|
return nil, errors.Wrap(err, "failed to connect to server")
|
||||||
|
}
|
||||||
|
client := NewMemosClient(conn)
|
||||||
|
|
||||||
|
return &Service{
|
||||||
|
config,
|
||||||
|
client,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) Start() {
|
||||||
|
config, err := getConfigFromEnv()
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to get config from env", slog.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
opts := []bot.Option{
|
||||||
|
bot.WithDefaultHandler(s.handler),
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := bot.New(config.BotToken, opts...)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to create bot", slog.Any("err", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("memogram started")
|
||||||
|
|
||||||
|
b.Start(context.Background())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) handler(ctx context.Context, b *bot.Bot, m *models.Update) {
|
||||||
|
if strings.HasPrefix(m.Message.Text, "/start ") {
|
||||||
|
s.startHandler(ctx, b, m)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userID := m.Message.From.ID
|
||||||
|
if _, ok := userAccessTokenCache.Load(userID); !ok {
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: "Please start the bot with /start <access_token>",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
message := m.Message
|
||||||
|
text := message.Text
|
||||||
|
if text == "" {
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: "Please input memo content",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
accessToken, _ := userAccessTokenCache.Load(userID)
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("Authorization", fmt.Sprintf("Bearer %s", accessToken.(string))))
|
||||||
|
memo, err := s.client.MemoService.CreateMemo(ctx, &v1pb.CreateMemoRequest{
|
||||||
|
Content: text,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("failed to create memo", slog.Any("err", err))
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: "Failed to create memo",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: fmt.Sprintf("Memo created with %s", memo.Name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) startHandler(ctx context.Context, b *bot.Bot, m *models.Update) {
|
||||||
|
userID := m.Message.From.ID
|
||||||
|
accessToken := strings.TrimPrefix(m.Message.Text, "/start ")
|
||||||
|
|
||||||
|
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("Authorization", fmt.Sprintf("Bearer %s", accessToken)))
|
||||||
|
user, err := s.client.AuthService.GetAuthStatus(ctx, &v1pb.GetAuthStatusRequest{})
|
||||||
|
if err != nil {
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: "Invalid access token",
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
userAccessTokenCache.Store(userID, accessToken)
|
||||||
|
b.SendMessage(ctx, &bot.SendMessageParams{
|
||||||
|
ChatID: m.Message.Chat.ID,
|
||||||
|
Text: fmt.Sprintf("Hello %s!", user.Nickname),
|
||||||
|
})
|
||||||
|
}
|
||||||
16
scripts/.air.toml
Normal file
16
scripts/.air.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
root = "."
|
||||||
|
tmp_dir = ".air"
|
||||||
|
|
||||||
|
[build]
|
||||||
|
bin = "./.air/memogram"
|
||||||
|
cmd = "go build -o ./.air/memogram ./bin/memogram/main.go"
|
||||||
|
delay = 1000
|
||||||
|
exclude_dir = [".air", "build"]
|
||||||
|
include_ext = ["go", "mod", "sum"]
|
||||||
|
exclude_file = []
|
||||||
|
exclude_regex = []
|
||||||
|
exclude_unchanged = true
|
||||||
|
follow_symlink = false
|
||||||
|
send_interrupt = true
|
||||||
|
kill_delay = 2000
|
||||||
|
stop_on_error = true
|
||||||
Reference in New Issue
Block a user