diff --git a/Dockerfile b/Dockerfile index 2aa45230..5938a378 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,36 @@ -FROM python:3.13-slim +# Stage 1: Build the Go binary +FROM golang:1.25-alpine AS builder WORKDIR /app -COPY requirements.txt . +# Install build dependencies +RUN apk add --no-cache git -RUN apt-get update -y && apt-get upgrade -y \ - && apt-get install -y --no-install-recommends ffmpeg curl unzip \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +# Copy go.mod and go.sum files +COPY go.mod go.sum ./ -RUN pip3 install -U pip && pip3 install -U -r requirements.txt +# Download dependencies +RUN go mod download +# Copy the rest of the source code COPY . . -CMD ["bash", "start"] +# Build the bot binary +RUN go build -o bot ./cmd/bot/main.go + +# Stage 2: Final image +FROM alpine:latest + +WORKDIR /app + +# Install runtime dependencies +RUN apk add --no-cache ffmpeg ca-certificates + +# Copy the binary from the builder stage +COPY --from=builder /app/bot . + +# Expose any necessary ports (if applicable) +# EXPOSE 8080 + +# Command to run the bot +CMD ["./bot"] diff --git a/Procfile b/Procfile index 36ad736e..a6b2737a 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -worker: bash start +worker: ./bot diff --git a/bot b/bot new file mode 100755 index 00000000..a5fb41a5 Binary files /dev/null and b/bot differ diff --git a/cmd/bot/main.go b/cmd/bot/main.go new file mode 100644 index 00000000..44028411 --- /dev/null +++ b/cmd/bot/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "syscall" + + "github.com/AloneXMusic/AloneXMusic-Go/internal/bot" + "github.com/AloneXMusic/AloneXMusic-Go/internal/config" + "github.com/AloneXMusic/AloneXMusic-Go/internal/db" +) + +func main() { + cfg := config.Load() + cfg.Check() + + ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) + defer cancel() + + database, err := db.NewMongoDB(cfg.MongoURL) + if err != nil { + log.Fatalf("Failed to connect to MongoDB: %v", err) + } + defer database.Close(context.Background()) + + h := bot.NewHandlers(nil) // We'll set the sender later + b, err := bot.NewClient(cfg, "", true, h) + if err != nil { + log.Fatalf("Failed to create bot client: %v", err) + } + h.SetSender(b.API()) + + go func() { + if err := b.Start(ctx, cfg.BotToken); err != nil { + log.Fatalf("Bot failed: %v", err) + } + }() + + log.Println("AloneX Music Bot (Go) is running...") + <-ctx.Done() + log.Println("Shutting down...") +} diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..9f65faad --- /dev/null +++ b/go.mod @@ -0,0 +1,51 @@ +module github.com/AloneXMusic/AloneXMusic-Go + +go 1.25.0 + +require ( + github.com/gotd/td v0.144.0 + github.com/joho/godotenv v1.5.1 + go.mongodb.org/mongo-driver/v2 v2.6.0 +) + +require ( + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/coder/websocket v1.8.14 // indirect + github.com/dlclark/regexp2 v1.11.5 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/ghodss/yaml v1.0.0 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/go-faster/jx v1.2.0 // indirect + github.com/go-faster/xor v1.0.0 // indirect + github.com/go-faster/yaml v0.4.6 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gotd/ige v0.2.2 // indirect + github.com/gotd/neo v0.1.5 // indirect + github.com/klauspost/compress v1.18.6 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/ogen-go/ogen v1.19.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.2.0 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opentelemetry.io/otel v1.43.0 // indirect + go.opentelemetry.io/otel/metric v1.43.0 // indirect + go.opentelemetry.io/otel/trace v1.43.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + golang.org/x/tools v0.43.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + rsc.io/qr v0.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..86cbd7f6 --- /dev/null +++ b/go.sum @@ -0,0 +1,140 @@ +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= +github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ= +github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI= +github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE= +github.com/go-faster/xor v0.3.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ= +github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38= +github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ= +github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I= +github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk= +github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0= +github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ= +github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ= +github.com/gotd/td v0.144.0 h1:FOSnbzutdWGDL/6w8v/pilqbUJNWqbKI8GrqusOQeOM= +github.com/gotd/td v0.144.0/go.mod h1:h56ixbXbenLoRQKiy0Qq668I9JWHMbeuv6BuFRmOaPI= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/ogen-go/ogen v1.19.0 h1:YvdNpeQJ8A8dLLpS6Vs4WxXL53BT6tBPxH0VSjfALhA= +github.com/ogen-go/ogen v1.19.0/go.mod h1:DeShwO+TEpLYXNCuZliSAedphphXsJaTGGbmSomWUjE= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.2.0 h1:bYKF2AEwG5rqd1BumT4gAnvwU/M9nBp2pTSxeZw7Wvs= +github.com/xdg-go/scram v1.2.0/go.mod h1:3dlrS0iBaWKYVt2ZfA4cj48umJZ+cAEbR6/SjLA88I8= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.6.0 h1:b9sJOYrkmt4l8bY43ZenFBcPlhYIjaOfYHLtbB/5qi8= +go.mongodb.org/mongo-driver/v2 v2.6.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= +go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY= +golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y= +nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= +rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY= +rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs= diff --git a/internal/bot/client.go b/internal/bot/client.go new file mode 100644 index 00000000..81ec9693 --- /dev/null +++ b/internal/bot/client.go @@ -0,0 +1,70 @@ +package bot + +import ( + "context" + "fmt" + "log" + + "github.com/gotd/td/telegram" + "github.com/gotd/td/tg" + "github.com/AloneXMusic/AloneXMusic-Go/internal/config" +) + +type Client struct { + tgClient *telegram.Client + api *tg.Client + isBot bool +} + +func NewClient(cfg *config.Config, session string, isBot bool, handler telegram.UpdateHandler) (*Client, error) { + opts := telegram.Options{} + if handler != nil { + opts.UpdateHandler = handler + } + client := telegram.NewClient(cfg.APIID, cfg.APIHash, opts) + + return &Client{ + tgClient: client, + api: tg.NewClient(client), + isBot: isBot, + }, nil +} + +func (c *Client) Start(ctx context.Context, token string) error { + if !c.isBot { + // For userbot, we would need a different flow or session storage + // This is a simplified version + log.Println("Starting Userbot...") + } else { + log.Println("Starting Bot...") + } + + return c.tgClient.Run(ctx, func(ctx context.Context) error { + if c.isBot { + if _, err := c.tgClient.Auth().Bot(ctx, token); err != nil { + return fmt.Errorf("bot auth: %w", err) + } + } else { + // Userbot auth logic would go here + } + + status, err := c.tgClient.Auth().Status(ctx) + if err != nil { + return fmt.Errorf("auth status: %w", err) + } + + if !status.Authorized { + if c.isBot { + return fmt.Errorf("bot not authorized") + } + // For userbot, manual auth or session loading is required + } + + log.Println("Client started and authorized") + return nil + }) +} + +func (c *Client) API() *tg.Client { + return c.api +} diff --git a/internal/bot/handlers.go b/internal/bot/handlers.go new file mode 100644 index 00000000..6d3571ee --- /dev/null +++ b/internal/bot/handlers.go @@ -0,0 +1,93 @@ +package bot + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/gotd/td/telegram/message" + "github.com/gotd/td/tg" +) + +type Handlers struct { + sender *message.Sender +} + +func NewHandlers(api *tg.Client) *Handlers { + h := &Handlers{} + if api != nil { + h.sender = message.NewSender(api) + } + return h +} + +func (h *Handlers) SetSender(api *tg.Client) { + h.sender = message.NewSender(api) +} + +func (h *Handlers) Handle(ctx context.Context, u tg.UpdatesClass) error { + switch updates := u.(type) { + case *tg.Updates: + for _, u := range updates.Updates { + if err := h.handleUpdate(ctx, u); err != nil { + return err + } + } + case *tg.UpdateShortMessage: + // Handle short message + case *tg.UpdateShortChatMessage: + // Handle short chat message + case *tg.UpdatesCombined: + for _, u := range updates.Updates { + if err := h.handleUpdate(ctx, u); err != nil { + return err + } + } + case *tg.UpdateShort: + return h.handleUpdate(ctx, updates.Update) + } + return nil +} + +func (h *Handlers) handleUpdate(ctx context.Context, u tg.UpdateClass) error { + switch update := u.(type) { + case *tg.UpdateNewMessage: + msg, ok := update.Message.(*tg.Message) + if !ok || msg.Out { + return nil + } + + if strings.HasPrefix(msg.Message, "/start") { + return h.handleStart(ctx, msg) + } else if strings.HasPrefix(msg.Message, "/ping") { + return h.handlePing(ctx, msg) + } + } + return nil +} + +func (h *Handlers) handleStart(ctx context.Context, msg *tg.Message) error { + peer := &tg.InputPeerUser{ + UserID: msg.PeerID.(*tg.PeerUser).UserID, + AccessHash: 0, // In a real app, you'd get this from your peer storage + } + + text := "Welcome to AloneX Music Bot (Go Edition)!\n\nUse /ping to check the latency." + _, err := h.sender.To(peer).Text(ctx, text) + return err +} + +func (h *Handlers) handlePing(ctx context.Context, msg *tg.Message) error { + start := time.Now() + peer := &tg.InputPeerUser{ + UserID: msg.PeerID.(*tg.PeerUser).UserID, + AccessHash: 0, + } + + latency := time.Since(start).Milliseconds() + text := fmt.Sprintf("🏓 Pong!\nLatency: %dms", latency) + + _, err := h.sender.To(peer).Text(ctx, text) + return err +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..c2a986a9 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,124 @@ +package config + +import ( + "log" + "os" + "strconv" + "strings" + + "github.com/joho/godotenv" +) + +type Config struct { + APIID int + APIHash string + BotToken string + MongoURL string + LoggerID int64 + OwnerID int64 + Session1 string + SupportChannel string + SupportChat string + AutoEnd bool + AutoLeave bool + VideoPlay bool + QueueLimit int + DurationLimit int + PlaylistLimit int + YoutubeAPIKey string + DefaultThumb string + PingImg string + StartImg string +} + +func Load() *Config { + err := godotenv.Load() + if err != nil { + log.Println("Warning: .env file not found") + } + + return &Config{ + APIID: getEnvInt("API_ID", 17596251), + APIHash: getEnv("API_HASH", "e58343b4c0193e293e391daf97603fcd"), + BotToken: getEnv("BOT_TOKEN", ""), + MongoURL: getEnv("MONGO_URL", ""), + LoggerID: getEnvInt64("LOGGER_ID", 0), + OwnerID: getEnvInt64("OWNER_ID", 0), + Session1: getEnv("SESSION", ""), + SupportChannel: getEnv("SUPPORT_CHANNEL", "https://t.me/AloneUpdates"), + SupportChat: getEnv("SUPPORT_CHAT", "https://t.me/AloneBotSupport"), + AutoEnd: getEnvBool("AUTO_END", false), + AutoLeave: getEnvBool("AUTO_LEAVE", false), + VideoPlay: getEnvBool("VIDEO_PLAY", true), + QueueLimit: getEnvInt("QUEUE_LIMIT", 50), + DurationLimit: getEnvInt("DURATION_LIMIT", 5400), + PlaylistLimit: getEnvInt("PLAYLIST_LIMIT", 20), + YoutubeAPIKey: getEnv("YOUTUBE_API_KEY", "INFLEX68575028D"), + DefaultThumb: getEnv("DEFAULT_THUMB", "https://te.legra.ph/file/3e40a408286d4eda24191.jpg"), + PingImg: getEnv("PING_IMG", "https://files.catbox.moe/haagg2.png"), + StartImg: getEnv("START_IMG", "https://files.catbox.moe/zvziwk.jpg"), + } +} + +func (c *Config) Check() { + var missing []string + if c.APIID == 0 { + missing = append(missing, "API_ID") + } + if c.APIHash == "" { + missing = append(missing, "API_HASH") + } + if c.BotToken == "" { + missing = append(missing, "BOT_TOKEN") + } + if c.MongoURL == "" { + missing = append(missing, "MONGO_URL") + } + if c.Session1 == "" { + missing = append(missing, "SESSION") + } + + if len(missing) > 0 { + log.Fatalf("Missing required environment variables: %s", strings.Join(missing, ", ")) + } +} + +func getEnv(key, fallback string) string { + if value, ok := os.LookupEnv(key); ok { + return value + } + return fallback +} + +func getEnvInt(key string, fallback int) int { + if value, ok := os.LookupEnv(key); ok { + i, err := strconv.Atoi(value) + if err != nil { + return fallback + } + return i + } + return fallback +} + +func getEnvInt64(key string, fallback int64) int64 { + if value, ok := os.LookupEnv(key); ok { + i, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return fallback + } + return i + } + return fallback +} + +func getEnvBool(key string, fallback bool) bool { + if value, ok := os.LookupEnv(key); ok { + b, err := strconv.ParseBool(value) + if err != nil { + return fallback + } + return b + } + return fallback +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..eb06f755 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,23 @@ +package config + +import ( + "os" + "testing" +) + +func TestLoad(t *testing.T) { + os.Setenv("API_ID", "12345") + os.Setenv("API_HASH", "test_hash") + os.Setenv("BOT_TOKEN", "test_token") + os.Setenv("MONGO_URL", "mongodb://localhost:27017") + os.Setenv("SESSION", "test_session") + + cfg := Load() + + if cfg.APIID != 12345 { + t.Errorf("Expected APIID 12345, got %d", cfg.APIID) + } + if cfg.APIHash != "test_hash" { + t.Errorf("Expected APIHash test_hash, got %s", cfg.APIHash) + } +} diff --git a/internal/db/mongo.go b/internal/db/mongo.go new file mode 100644 index 00000000..5608410a --- /dev/null +++ b/internal/db/mongo.go @@ -0,0 +1,109 @@ +package db + +import ( + "context" + "log" + "time" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +type MongoDB struct { + client *mongo.Client + db *mongo.Database +} + +func NewMongoDB(uri string) (*MongoDB, error) { + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + client, err := mongo.Connect(options.Client().ApplyURI(uri)) + if err != nil { + return nil, err + } + + err = client.Ping(ctx, nil) + if err != nil { + return nil, err + } + + return &MongoDB{ + client: client, + db: client.Database("Anon"), + }, nil +} + +func (m *MongoDB) GetSudoers(ctx context.Context) ([]int64, error) { + collection := m.db.Collection("cache") + var result struct { + UserIDs []int64 `bson:"user_ids"` + } + err := collection.FindOne(ctx, bson.M{"_id": "sudoers"}).Decode(&result) + if err == mongo.ErrNoDocuments { + return []int64{}, nil + } + return result.UserIDs, err +} + +func (m *MongoDB) IsBlacklisted(ctx context.Context, id int64) (bool, error) { + collection := m.db.Collection("cache") + + // Check blacklisted users + var userResult struct { + UserIDs []int64 `bson:"user_ids"` + } + err := collection.FindOne(ctx, bson.M{"_id": "bl_users"}).Decode(&userResult) + if err == nil { + for _, v := range userResult.UserIDs { + if v == id { + return true, nil + } + } + } + + // Check blacklisted chats + var chatResult struct { + ChatIDs []int64 `bson:"chat_ids"` + } + err = collection.FindOne(ctx, bson.M{"_id": "bl_chats"}).Decode(&chatResult) + if err == nil { + for _, v := range chatResult.ChatIDs { + if v == id { + return true, nil + } + } + } + + return false, nil +} + +func (m *MongoDB) GetAssistant(ctx context.Context, chatID int64) (int, error) { + collection := m.db.Collection("assistant") + var result struct { + Num int `bson:"num"` + } + err := collection.FindOne(ctx, bson.M{"_id": chatID}).Decode(&result) + if err == mongo.ErrNoDocuments { + return 1, nil // Default assistant + } + return result.Num, err +} + +func (m *MongoDB) AddChat(ctx context.Context, chatID int64) error { + collection := m.db.Collection("chats") + _, err := collection.UpdateOne( + ctx, + bson.M{"_id": chatID}, + bson.M{"$set": bson.M{"_id": chatID}}, + options.UpdateOne().SetUpsert(true), + ) + return err +} + +func (m *MongoDB) Close(ctx context.Context) { + if err := m.client.Disconnect(ctx); err != nil { + log.Printf("Error disconnecting from MongoDB: %v", err) + } +}