175 lines
4.9 KiB
Go
175 lines
4.9 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()
|
|
dat, err := os.ReadFile("config.yaml")
|
|
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))
|
|
}
|
|
}
|