sleepyunicorn/main.go

179 lines
5.0 KiB
Go

package main
import (
"bufio"
"context"
"fmt"
"log"
"os"
"os/exec"
"strings"
"github.com/gotd/td/telegram"
"github.com/gotd/td/telegram/message"
"github.com/gotd/td/telegram/message/styling"
"github.com/gotd/td/telegram/uploader"
"github.com/gotd/td/tg"
"go.uber.org/zap"
"golang.org/x/xerrors"
"gopkg.in/yaml.v2"
)
var (
config Config
logger *zap.Logger
)
func init() {
zapConfig := zap.NewProductionConfig()
tmpLogger, err := zapConfig.Build()
if err != nil {
log.Fatalf("failed to initialise zap: %v", err)
}
logger = tmpLogger
defer logger.Sync()
configFilename := "config.yaml"
if len(os.Args) == 2 {
configFilename = os.Args[1]
}
dat, err := os.ReadFile(configFilename)
if err != nil {
logger.Fatal("failed to read config.yaml", zap.Error(err))
}
err = yaml.Unmarshal(dat, &config)
if err != nil {
logger.Fatal("failed to parse config.yaml", zap.Error(err))
}
}
func ItemInSlice(slice []int, val int) bool {
for _, i := range slice {
if i == val {
return true
}
}
return false
}
func main() {
defer logger.Sync()
dispatcher := tg.NewUpdateDispatcher()
client := telegram.NewClient(config.ApiId, config.ApiHash, telegram.Options{
Logger: logger,
UpdateHandler: dispatcher,
})
ctx := context.Background()
err := client.Run(ctx, func(ctx context.Context) error {
auth := client.Auth()
status, err := auth.Status(ctx)
if err != nil {
return xerrors.Errorf("failed to check auth status: %v", err)
}
if !status.Authorized {
_, err = auth.Bot(ctx, config.BotToken)
if err != nil {
return xerrors.Errorf("failed to authorize: %v", err)
}
}
api := tg.NewClient(client)
sender := message.NewSender(api)
dispatcher.OnNewMessage(func(ctx context.Context, entities tg.Entities, u *tg.UpdateNewMessage) error {
m, ok := u.Message.(*tg.Message)
if !ok || m.Out {
return nil
}
switch peer := m.GetPeerID().(type) {
case *tg.PeerUser:
if !ItemInSlice(config.AllowedUserIds, peer.UserID) {
return nil
}
default:
return nil
}
split := strings.SplitN(m.Message, "\n", 2)
if len(split) == 0 {
return nil
}
command := exec.Command("sh", "-c", split[0])
if len(split) == 2 {
command.Stdin = strings.NewReader(split[1])
}
stdout, err := os.CreateTemp("", "sleepyunicorn*.txt")
if err != nil {
return xerrors.Errorf("failed to create temporary file: %v", err)
}
defer os.Remove(stdout.Name())
command.Stdout = stdout
command.Stderr = stdout
err = command.Start()
if err != nil {
return xerrors.Errorf("failed to start process: %v", err)
}
replyUpdate, err := sender.Reply(entities, u).StyledText(ctx, styling.Code(fmt.Sprintf("+++ executing process %d +++", command.Process.Pid)))
if err != nil {
return xerrors.Errorf("failed to send executing message: %v", err)
}
reply, ok := replyUpdate.(*tg.UpdateShortSentMessage)
if !ok {
return xerrors.New("failed to cast to tg.UpdateShortSentMessage")
}
err = command.Wait()
exitCode := 0
if err != nil {
if ee, ok := err.(*exec.ExitError); ok {
exitCode = ee.ExitCode()
} else {
return xerrors.Errorf("error occured in command.Run: %v", err)
}
}
exitCodeMessage := fmt.Sprintf("+++ exited with %d +++", exitCode)
fileSize, err := stdout.Seek(0, 2)
if err != nil {
return xerrors.Errorf("failed to seek to end of file: %v", err)
}
_, err = stdout.Seek(0, 0)
if err != nil {
return xerrors.Errorf("failed to seek to start of file: %v", err)
}
reader := bufio.NewReader(stdout)
if fileSize > 4096-1-int64(len(exitCodeMessage)) {
upl := uploader.NewUploader(api)
sender := sender.WithUploader(upl)
_, err = sender.Answer(entities, u).Edit(reply.ID).StyledText(ctx, styling.Code("+++ uploading output +++"))
if err != nil {
return xerrors.Errorf("failed to edit message: %v", err)
}
upload, err := upl.FromReader(ctx, "output.txt", reader)
if err != nil {
return xerrors.Errorf("failed to upload file: %v", err)
}
doc := message.UploadedDocument(upload, styling.Code(exitCodeMessage))
doc.Filename("output.txt")
_, err = sender.Reply(entities, u).Media(ctx, doc)
if err != nil {
return xerrors.Errorf("failed to send file to chat: %v", err)
}
_, err = sender.Answer(entities, u).Revoke().Messages(ctx, reply.ID)
if err != nil {
return xerrors.Errorf("failed to delete message: %v", err)
}
return nil
}
var builder strings.Builder
_, err = reader.WriteTo(&builder)
if err != nil {
return xerrors.Errorf("error occured in bufio.Reader.WriteTo: %v", err)
}
_, err = sender.Answer(entities, u).Edit(reply.ID).StyledText(ctx, styling.Code(exitCodeMessage), styling.Plain("\n"), styling.Pre(builder.String()))
if err != nil {
return xerrors.Errorf("failed to edit message: %v", err)
}
return nil
})
return telegram.RunUntilCanceled(ctx, client)
})
if err != nil {
logger.Fatal("error occured in client.Run", zap.Error(err))
}
}