1
Fork 0

first commit

This commit is contained in:
Jan-Lukas Else 2024-03-29 11:18:39 +01:00
commit 3b2ff4d4a2
6 changed files with 227 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
secrets.env

13
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Launch Package",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "${fileDirname}",
"envFile": "${workspaceFolder}/secrets.env"
}
]
}

10
Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM golang:1.22-alpine as builder
WORKDIR /app
COPY go.mod go.sum main.go /app
RUN go build -o app
FROM alpine:latest
WORKDIR /app
COPY --from=builder /app/app .
EXPOSE 8080
CMD ["./app"]

7
go.mod Normal file
View File

@ -0,0 +1,7 @@
module git.jlel.se/jlelse/serverless-tgbot
go 1.22.0
require github.com/carlmjohnson/requests v0.23.5
require golang.org/x/net v0.15.0 // indirect

4
go.sum Normal file
View File

@ -0,0 +1,4 @@
github.com/carlmjohnson/requests v0.23.5 h1:NPANcAofwwSuC6SIMwlgmHry2V3pLrSqRiSBKYbNHHA=
github.com/carlmjohnson/requests v0.23.5/go.mod h1:zG9P28thdRnN61aD7iECFhH5iGGKX2jIjKQD9kqYH+o=
golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=

192
main.go Normal file
View File

@ -0,0 +1,192 @@
package main
import (
"context"
"encoding/json"
"fmt"
"io"
"log"
"mime/multipart"
"net/http"
"os"
"slices"
"strconv"
"strings"
"github.com/carlmjohnson/requests"
)
type TelegramMessage struct {
MessageID int `json:"message_id,omitempty"`
From TelegramUser `json:"from,omitempty"`
Chat TelegramChat `json:"chat,omitempty"`
Voice *TelegramVoice `json:"voice,omitempty"`
}
type TelegramUser struct {
ID int `json:"id,omitempty"`
Username string `json:"username,omitempty"`
}
type TelegramChat struct {
ID int `json:"id,omitempty"`
}
type TelegramVoice struct {
FileID string `json:"file_id,omitempty"`
}
type TelegramSendMessage struct {
ChatID int `json:"chat_id,omitempty"`
Text string `json:"text,omitempty"`
ReplyParameters *TelegramReplyParameters `json:"reply_parameters,omitempty"`
}
type TelegramReplyParameters struct {
MessageID int `json:"message_id,omitempty"`
}
func main() {
// Retrieve bot token from environment variable
botToken := os.Getenv("BOT_TOKEN")
if botToken == "" {
log.Fatal("BOT_TOKEN environment variable is not set")
}
// Retrieve OpenAI API key
openaiApiKey := os.Getenv("OPENAI_KEY")
if openaiApiKey == "" {
log.Fatal("OPENAI_KEY environment variable is not set")
}
// Retrieve allowed users
allowedUsers := strings.Split(os.Getenv("ALLOWED_USERS"), ",")
if len(allowedUsers) == 0 {
log.Fatal("ALLOWED_USERS environment variable is not set")
}
log.Println("Allowed users:", allowedUsers)
// Set up the HTTP server
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "Hi!")
})
http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
// Decode the incoming message
var update struct {
Message *TelegramMessage `json:"message"`
}
if err := json.NewDecoder(r.Body).Decode(&update); err != nil {
log.Println("Error decoding update:", err)
return
}
if update.Message == nil {
return
}
// Check allowed users
if !(slices.Contains(allowedUsers, update.Message.From.Username) || slices.Contains(allowedUsers, strconv.Itoa(update.Message.From.ID))) {
sendMessage(botToken, update.Message.Chat.ID, "Sorry, you are not allowed to use this bot!", update.Message.MessageID)
return
}
if update.Message.Voice != nil {
transcript, err := transcriptVoiceMessage(botToken, openaiApiKey, update.Message.Voice.FileID)
if err != nil {
log.Println("Error transcribing voice message:", err)
sendMessage(botToken, update.Message.Chat.ID, "Failed to transcribe voice message", update.Message.MessageID)
} else {
sendMessage(botToken, update.Message.Chat.ID, transcript, update.Message.MessageID)
}
} else {
sendMessage(botToken, update.Message.Chat.ID, "Please send a voice message", update.Message.MessageID)
}
})
// Start the HTTP server
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
log.Printf("Starting server on :%s", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
func sendMessage(botToken string, chatID int, text string, replyTo int) {
msg := &TelegramSendMessage{
ChatID: chatID,
Text: text,
}
if replyTo != 0 {
msg.ReplyParameters = &TelegramReplyParameters{
MessageID: replyTo,
}
}
err := requests.URL(fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", botToken)).
Method(http.MethodPost).
BodyJSON(msg).
Fetch(context.Background())
if err != nil {
log.Println("Error sending message:", err)
return
}
}
func transcriptVoiceMessage(botToken, openaiApiKey, fileID string) (string, error) {
// Get file path
var fileResponse struct {
OK bool `json:"ok"`
Result struct {
FilePath string `json:"file_path"`
} `json:"result"`
}
err := requests.URL(fmt.Sprintf("https://api.telegram.org/bot%s/getFile", botToken)).
Param("file_id", fileID).
ToJSON(&fileResponse).
Fetch(context.Background())
if err != nil {
return "", err
}
// Download the file
voiceDownload := fmt.Sprintf("https://api.telegram.org/file/bot%s/%s", botToken, fileResponse.Result.FilePath)
voiceReader, voiceWriter := io.Pipe()
go func() {
voiceWriter.CloseWithError(
requests.URL(voiceDownload).
ToWriter(voiceWriter).
Fetch(context.Background()),
)
}()
// Transcribe message
multipartReader, multipartWriter := io.Pipe()
mpw := multipart.NewWriter(multipartWriter)
go func() {
defer multipartWriter.Close()
mpw.WriteField("model", "whisper-1")
fileWriter, err := mpw.CreateFormFile("file", "file.oga")
if err != nil {
multipartWriter.CloseWithError(err)
return
}
_, err = io.Copy(fileWriter, voiceReader)
if err != nil {
multipartWriter.CloseWithError(err)
return
}
multipartWriter.CloseWithError(mpw.Close())
}()
var transcription struct {
Text string `json:"text"`
}
err = requests.URL("https://api.openai.com/v1/audio/transcriptions").
Method(http.MethodPost).
Header("Authorization", fmt.Sprintf("Bearer %s", openaiApiKey)).
ContentType(mpw.FormDataContentType()).
BodyReader(multipartReader).
ToJSON(&transcription).
Fetch(context.Background())
return transcription.Text, err
}