From 98390f3e22680b0cda9ff1882a65705271de65c7 Mon Sep 17 00:00:00 2001 From: divyam234 Date: Mon, 7 Aug 2023 01:02:46 +0530 Subject: [PATCH] initial commit --- .gitignore | 28 +++ .vscode/launch.json | 15 ++ README.md | 1 + cache/bigcache.go | 99 ++++++++ cache/cache.go | 72 ++++++ cache/cachutil.go | 63 ++++++ database/database.go | 48 ++++ go.mod | 81 +++++++ go.sum | 373 ++++++++++++++++++++++++++++++ main.go | 33 +++ models/file.model.go | 24 ++ routes/file.routes.go | 73 ++++++ routes/main.go | 8 + schemas/file.schema.go | 68 ++++++ services/file.service.go | 475 +++++++++++++++++++++++++++++++++++++++ types/main.go | 15 ++ utils/main.go | 80 +++++++ utils/tgclient.go | 128 +++++++++++ 18 files changed, 1684 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 README.md create mode 100644 cache/bigcache.go create mode 100644 cache/cache.go create mode 100644 cache/cachutil.go create mode 100644 database/database.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 models/file.model.go create mode 100644 routes/file.routes.go create mode 100644 routes/main.go create mode 100644 schemas/file.schema.go create mode 100644 services/file.service.go create mode 100644 types/main.go create mode 100644 utils/main.go create mode 100644 utils/tgclient.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7565dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +sessions + +*.env +*.env.example +*.env.local +*.env.staging \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6deb9a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "main.go" + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ab44c69 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +teldrive-go \ No newline at end of file diff --git a/cache/bigcache.go b/cache/bigcache.go new file mode 100644 index 0000000..27a7489 --- /dev/null +++ b/cache/bigcache.go @@ -0,0 +1,99 @@ +package cache + +import ( + "bytes" + "context" + "encoding/gob" + "errors" + + "github.com/allegro/bigcache/v3" +) + +type bigCache struct { + cache *bigcache.BigCache +} + +func newBigCache(cacheConfig *cacheConfig) (*bigCache, error) { + cache, err := bigcache.New(context.Background(), bigcache.Config{ + Shards: 16, + LifeWindow: cacheConfig.ttl, + CleanWindow: cacheConfig.cleanFreq, + MaxEntriesInWindow: 1000 * 10 * 60, + MaxEntrySize: 500, + Verbose: false, + HardMaxCacheSize: cacheConfig.size, + StatsEnabled: true, + }) + if err != nil { + return nil, err + } + return &bigCache{ + cache: cache, + }, nil +} + +// Set inserts the key/value pair into the cache. +// Only the exported fields of the given struct will be +// serialized and stored +func (c *bigCache) Set(key, value interface{}) error { + keyString, ok := key.(string) + if !ok { + return errors.New("a cache key must be a string") + } + + valueBytes, err := serializeGOB(value) + if err != nil { + return err + } + + return c.cache.Set(keyString, valueBytes) +} + +// Get returns the value correlating to the key in the cache +func (c *bigCache) Get(key interface{}) (interface{}, error) { + // Assert the key is of string type + keyString, ok := key.(string) + if !ok { + return nil, errors.New("a cache key must be a string") + } + + // Get the value in the byte format it is stored in + valueBytes, err := c.cache.Get(keyString) + if err != nil { + return nil, err + } + + // Deserialize the bytes of the value + value, err := deserializeGOB(valueBytes) + if err != nil { + return nil, err + } + + return value, nil +} + +func serializeGOB(value interface{}) ([]byte, error) { + buf := bytes.Buffer{} + enc := gob.NewEncoder(&buf) + gob.Register(value) + + err := enc.Encode(&value) + if err != nil { + return nil, err + } + + return buf.Bytes(), nil +} + +func deserializeGOB(valueBytes []byte) (interface{}, error) { + var value interface{} + buf := bytes.NewBuffer(valueBytes) + dec := gob.NewDecoder(buf) + + err := dec.Decode(&value) + if err != nil { + return nil, err + } + + return value, nil +} diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 0000000..f059e06 --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,72 @@ +package cache + +import ( + "time" +) + +type cacheConfig struct { + size int // Size in MB + ttl time.Duration + cleanFreq time.Duration +} + +// Interface to wrap any caching implementation +type Cache interface { + Set(key, value interface{}) error // Only exported fields in struct will be stored + Get(key interface{}) (interface{}, error) +} + +// New builds a new default cache. You may pass options to modify the default values +func New(opts ...Option) (Cache, error) { + cacheConfig := &cacheConfig{ + size: 1, + ttl: 60 * time.Second, + cleanFreq: 30 * time.Second, + } + + for _, opt := range opts { + opt.apply(cacheConfig) + } + + cache, err := newBigCache(cacheConfig) + if err != nil { + return nil, err + } + return cache, nil +} + +type Option interface { + apply(cacheConfig *cacheConfig) +} + +type optionFunc func(*cacheConfig) + +func (opt optionFunc) apply(cacheConfig *cacheConfig) { + opt(cacheConfig) +} + +// WithSizeInMB sets the size of the cache in MBs +// The minimum size of the cache is 1 MB +// If a size of 0 or less is passed the cache will have unlimited size +func WithSizeInMB(size int) Option { + return optionFunc(func(cacheConfig *cacheConfig) { + cacheConfig.size = size + }) +} + +// WithTTL will cause the cache to expire any item that lives longer +// than the given ttl +func WithTTL(ttl time.Duration) Option { + return optionFunc(func(cacheConfig *cacheConfig) { + cacheConfig.ttl = ttl + }) +} + +// WithCleanFrequency sets how often the cache will clean out expired items +// The lowest the frequency may be is 1 second +// If the time is 0 then no cleaning will happen and items will never be removed +func WithCleanFrequency(cleanFreq time.Duration) Option { + return optionFunc(func(cacheConfig *cacheConfig) { + cacheConfig.cleanFreq = cleanFreq + }) +} diff --git a/cache/cachutil.go b/cache/cachutil.go new file mode 100644 index 0000000..b2bee5c --- /dev/null +++ b/cache/cachutil.go @@ -0,0 +1,63 @@ +package cache + +import ( + "reflect" + "time" +) + +var globalCache Cache + +func CacheInit() { + + var err error + globalCache, err = New( + WithSizeInMB(10), + WithTTL(12*time.Hour), + WithCleanFrequency(24*time.Hour), + ) + if err != nil { + panic("Failed to initialize global cache: " + err.Error()) + } +} + +func GetCache() Cache { + return globalCache +} + +func CachedFunction(fn interface{}, key string) func(...interface{}) (interface{}, error) { + return func(args ...interface{}) (interface{}, error) { + + // Check if the result is already cached + if cachedResult, err := globalCache.Get(key); err == nil { + return cachedResult, nil + } + + // If not cached, call the original function to get the result + f := reflect.ValueOf(fn) + if len(args) == 0 { + args = nil // Ensure nil is passed when there are no arguments. + } + result := f.Call(getArgs(args)) + + // Check if the function returned an error as the last return value + if err, ok := result[len(result)-1].Interface().(error); ok && err != nil { + return nil, err + } + + // Extract the result from the function call + finalResult := result[0].Interface() + + // Cache the result with a default TTL (time-to-live) + globalCache.Set(key, finalResult) + + return finalResult, nil + } +} + +func getArgs(args []interface{}) []reflect.Value { + var values []reflect.Value + for _, arg := range args { + values = append(values, reflect.ValueOf(arg)) + } + return values +} diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..61a620a --- /dev/null +++ b/database/database.go @@ -0,0 +1,48 @@ +package database + +import ( + "os" + "time" + + "gorm.io/driver/postgres" + "gorm.io/gorm" + "gorm.io/gorm/schema" +) + +var DB *gorm.DB + +func InitDB() { + + var err error + + DB, err = gorm.Open(postgres.Open(os.Getenv("DATABASE_URL")), &gorm.Config{ + NamingStrategy: schema.NamingStrategy{ + TablePrefix: "teldrive.", + SingularTable: false, + }, + PrepareStmt: false, + NowFunc: func() time.Time { + return time.Now().UTC() + }, + }) + if err != nil { + panic(err) + } + + sqlDB, err := DB.DB() + if err != nil { + panic(err) + } + sqlDB.SetMaxIdleConns(10) + sqlDB.SetMaxOpenConns(100) + + sqlDB.SetConnMaxLifetime(time.Hour) + + go func() { + DB.Exec("select 1") + }() + + // Auto-migrate the File struct to the database + //db.AutoMigrate(&File{}) + +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..3f2cbb1 --- /dev/null +++ b/go.mod @@ -0,0 +1,81 @@ +module github.com/divyam234/teldrive-go + +go 1.20 + +require ( + github.com/allegro/bigcache/v3 v3.1.0 + github.com/gin-gonic/gin v1.9.1 + github.com/jackc/pgx/v4 v4.18.1 + github.com/mitchellh/mapstructure v1.5.0 + golang.org/x/time v0.3.0 + gorm.io/gorm v1.25.2 +) + +require ( + github.com/PeteProgrammer/go-automapper v0.0.0-20200419053654-7c63d5bb0eb4 // indirect + github.com/jackc/pgx/v5 v5.3.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/lib/pq v1.10.2 // indirect +) + +require ( + github.com/bytedance/sonic v1.9.1 // indirect + github.com/cenkalti/backoff/v4 v4.2.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/devfeel/mapper v0.7.13 + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-faster/errors v0.6.1 // indirect + github.com/go-faster/jx v1.0.1 // indirect + github.com/go-faster/xor v1.0.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/gotd/contrib v0.19.0 + github.com/gotd/ige v0.2.2 // indirect + github.com/gotd/neo v0.1.5 // indirect + github.com/gotd/td v0.84.0 + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgtype v1.14.0 + github.com/jinzhu/gorm v1.9.16 + github.com/joho/godotenv v1.5.1 + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/lynchborg/automapper v0.0.0-20230411105854-fb8a35df335a + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pkg/errors v0.9.1 + github.com/quantumsheep/range-parser v1.1.0 + github.com/segmentio/asm v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.3 + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect + go.uber.org/atomic v1.11.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.25.0 + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.11.0 // indirect + golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b + golang.org/x/net v0.12.0 // indirect + golang.org/x/sync v0.3.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.2 + nhooyr.io/websocket v1.8.7 // indirect + rsc.io/qr v0.2.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..f4cee68 --- /dev/null +++ b/go.sum @@ -0,0 +1,373 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/PeteProgrammer/go-automapper v0.0.0-20200419053654-7c63d5bb0eb4 h1:S+0ODzOh5zBv4dlixiY+a6ZcwlbSkRXtkK8cJm+aKJo= +github.com/PeteProgrammer/go-automapper v0.0.0-20200419053654-7c63d5bb0eb4/go.mod h1:gzkRbKGjRtfeCc53wW2SyIPPx9wU5oM6bzr6qF/kWuI= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= +github.com/allegro/bigcache/v3 v3.1.0/go.mod h1:aPyh7jEvrog9zAwx5N7+JUQX5dZTSGpxF1LAR4dr35I= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= +github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/devfeel/mapper v0.7.13 h1:IE6ZITgrMH8W/xhUvJ6PInjVV8QW29idDJAUZPc9bbw= +github.com/devfeel/mapper v0.7.13/go.mod h1:foz4u16jrssGoDfnWYQGFcthjlU6uBV5UV8uYJfKneA= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/go-faster/errors v0.6.1 h1:nNIPOBkprlKzkThvS/0YaX8Zs9KewLCOSFQS5BU06FI= +github.com/go-faster/errors v0.6.1/go.mod h1:5MGV2/2T9yvlrbhe9pD9LO5Z/2zCSq2T8j+Jpi2LAyY= +github.com/go-faster/jx v1.0.1 h1:NhSJEZtqj6KmXf63On7Hg7/sjUX+gotSc/eM6bZCZ00= +github.com/go-faster/jx v1.0.1/go.mod h1:vKDNikrKoyUmpzaJ0OkIkRQClNHFX/nF3dnTJZb3skg= +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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gotd/contrib v0.19.0 h1:O6GvMrRVeFslIHLUcpaHVzcl9/5PcgR2jQTIIeTyds0= +github.com/gotd/contrib v0.19.0/go.mod h1:LzPxzRF0FvtpBt/WyODWQnPpk0tm/G9z6RHUoPqMakU= +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.84.0 h1:oWMp5HczCAFSgKWgWFCuYjELBgcRVcRpGLdQ1bP2kpg= +github.com/gotd/td v0.84.0/go.mod h1:3dQsGL9rxMcS1Z9Na3S7U8e/pLMzbLIT2jM3E5IuUk0= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= +github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= +github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw= +github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0= +github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lynchborg/automapper v0.0.0-20230411105854-fb8a35df335a h1:3aE6b7HFuFpFklAhZG77EFUY0XM5gWfo4+dH52EYlN8= +github.com/lynchborg/automapper v0.0.0-20230411105854-fb8a35df335a/go.mod h1:3Wk1b4pHcmQr6bICtYkRr3ymaQ4OvlxVIPFfEFIgu1g= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quantumsheep/range-parser v1.1.0 h1:k4f1F58f8FF54FBYc9dYBRM+8JkAxFo11gC3IeMH4rU= +github.com/quantumsheep/range-parser v1.1.0/go.mod h1:acv4Vt2PvpGvRsvGju7Gk2ahKluZJsIUNR69W53J22I= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +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/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +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/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= +go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= +go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b h1:r+vk0EmXNmekl0S0BascoeeoHk/L7wmaW2QF90K+kYI= +golang.org/x/exp v0.0.0-20230801115018-d63ba01acd4b/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +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.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +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.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +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/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= +golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= +gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU= +gorm.io/gorm v1.25.0/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +gorm.io/gorm v1.25.2/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +nhooyr.io/websocket v1.8.7 h1:usjR2uOr/zjjkVMy0lW+PPohFok7PCow5sDjLgX4P4g= +nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +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/main.go b/main.go new file mode 100644 index 0000000..1062a36 --- /dev/null +++ b/main.go @@ -0,0 +1,33 @@ +package main + +import ( + "github.com/divyam234/teldrive-go/cache" + "github.com/divyam234/teldrive-go/database" + "github.com/divyam234/teldrive-go/routes" + "github.com/divyam234/teldrive-go/utils" + + "github.com/gin-gonic/gin" + "github.com/joho/godotenv" +) + +func main() { + + gin.SetMode(gin.ReleaseMode) + + router := gin.Default() + + godotenv.Load() + + database.InitDB() + + cache.CacheInit() + + utils.StartClients() + + router.Use(gin.ErrorLogger()) + + routes.GetRoutes(router) + + router.Run(":8080") + +} diff --git a/models/file.model.go b/models/file.model.go new file mode 100644 index 0000000..3a2969b --- /dev/null +++ b/models/file.model.go @@ -0,0 +1,24 @@ +package models + +import ( + "time" + + "github.com/jackc/pgtype" +) + +type File struct { + ID string `gorm:"type:text;primary_key;default:generate_uid(16)"` + Name string `gorm:"type:text"` + Type string `gorm:"type:text"` + Parts pgtype.JSONB `gorm:"type:jsonb"` + MimeType string `gorm:"type:text"` + ChannelID int64 `gorm:"type:bigint"` + Path string `gorm:"index;type:text"` + Size int64 `gorm:"type:bigint"` + Starred bool `gorm:"default:false"` + Depth int `gorm:"type:integer"` + UserID int `gorm:"type:bigint"` + ParentID string `gorm:"index;type:text"` + CreatedAt time.Time `gorm:"default:timezone('utc'::text, now())"` + UpdatedAt time.Time `gorm:"default:timezone('utc'::text, now())"` +} diff --git a/routes/file.routes.go b/routes/file.routes.go new file mode 100644 index 0000000..d7c8d18 --- /dev/null +++ b/routes/file.routes.go @@ -0,0 +1,73 @@ +package routes + +import ( + "context" + "net/http" + "os" + "strconv" + + "github.com/divyam234/teldrive-go/database" + "github.com/divyam234/teldrive-go/services" + + "github.com/gin-gonic/gin" +) + +func addFileRoutes(rg *gin.RouterGroup) { + + r := rg.Group("/files") + channelID, _ := strconv.ParseInt(os.Getenv("CHANNEL_ID"), 10, 64) + fileService := services.FileService{Db: database.DB, ChannelID: channelID} + + r.GET("/", func(c *gin.Context) { + res, err := fileService.ListFiles(c) + + if err != nil { + c.AbortWithError(err.Code, err.Error) + return + } + + c.JSON(http.StatusOK, res) + }) + + r.POST("/", func(c *gin.Context) { + + res, err := fileService.CreateFile(c) + + if err != nil { + c.AbortWithError(err.Code, err.Error) + return + } + + c.JSON(http.StatusOK, res) + }) + + r.GET("/:fileID", func(c *gin.Context) { + + res, err := fileService.GetFileByID(c) + + if err != nil { + c.AbortWithError(http.StatusNotFound, err) + return + } + + c.JSON(http.StatusOK, res) + }) + + r.PATCH("/:fileID", func(c *gin.Context) { + + res, err := fileService.UpdateFile(c) + + if err != nil { + c.AbortWithError(err.Code, err.Error) + return + } + + c.JSON(http.StatusOK, res) + }) + + r.GET("/:fileID/:fileName", func(c *gin.Context) { + + fileService.GetFileStream(context.Background())(c) + }) + +} diff --git a/routes/main.go b/routes/main.go new file mode 100644 index 0000000..8a739a2 --- /dev/null +++ b/routes/main.go @@ -0,0 +1,8 @@ +package routes + +import "github.com/gin-gonic/gin" + +func GetRoutes(router *gin.Engine) { + api := router.Group("/api") + addFileRoutes(api) +} diff --git a/schemas/file.schema.go b/schemas/file.schema.go new file mode 100644 index 0000000..4860b8a --- /dev/null +++ b/schemas/file.schema.go @@ -0,0 +1,68 @@ +package schemas + +import ( + "time" + + "github.com/jackc/pgtype" +) + +type PaginationQuery struct { + PerPage int `form:"perPage"` + NextPageToken string `form:"nextPageToken"` +} + +type SortingQuery struct { + Sort string `form:"sort"` + Order string `form:"order"` +} + +type FileQuery struct { + Name string `form:"name" mapstructure:"name,omitempty"` + Search string `form:"search" mapstructure:"search,omitempty"` + Type string `form:"type" mapstructure:"type,omitempty"` + Path string `form:"path" mapstructure:"path,omitempty"` + Op string `form:"op" mapstructure:"op,omitempty"` + Starred *bool `form:"starred" mapstructure:"starred,omitempty"` + MimeType string `form:"mimeType" mapstructure:"mime_type,omitempty"` + ParentID string `form:"parentId" mapstructure:"parent_id,omitempty"` + UpdatedAt *time.Time `form:"updatedAt" mapstructure:"updated_at,omitempty"` +} + +type FileIn struct { + Name string `json:"name" mapstructure:"name,omitempty"` + Type string `json:"type" mapstructure:"type,omitempty"` + Parts *[]Part `json:"parts,omitempty" mapstructure:"parts,omitempty"` + MimeType string `json:"mimeType" mapstructure:"mime_type,omitempty"` + ChannelID int64 `json:"channelId,omitempty" mapstructure:"channel_id,omitempty"` + Path string `json:"path" mapstructure:"path,omitempty"` + Size int64 `json:"size" mapstructure:"size,omitempty"` + Starred *bool `json:"starred" mapstructure:"starred,omitempty"` + Depth int `json:"depth,omitempty" mapstructure:"depth,omitempty"` + UserID int `json:"userId" mapstructure:"user_id,omitempty"` + ParentID string `json:"parentId" mapstructure:"parent_id,omitempty"` +} + +type FileOut struct { + ID string `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + MimeType string `json:"mimeType" mapstructure:"mime_type"` + Path string `json:"path,omitempty" mapstructure:"path,omitempty"` + Size int64 `json:"size,omitempty" mapstructure:"size,omitempty"` + Starred *bool `json:"starred"` + ParentID string `json:"parentId,omitempty" mapstructure:"parent_id"` + UpdatedAt time.Time `json:"updatedAt,omitempty" mapstructure:"updated_at"` +} + +type FileResponse struct { + Results []FileOut `json:"results"` + NextPageToken string `json:"nextPageToken,omitempty"` +} +type FileOutFull struct { + FileOut + Parts pgtype.JSONB `json:"parts,omitempty"` +} + +type Part struct { + ID int64 `json:"id"` +} diff --git a/services/file.service.go b/services/file.service.go new file mode 100644 index 0000000..f2524f8 --- /dev/null +++ b/services/file.service.go @@ -0,0 +1,475 @@ +package services + +import ( + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "net/http" + "strconv" + "strings" + + "github.com/divyam234/teldrive-go/cache" + "github.com/divyam234/teldrive-go/models" + "github.com/divyam234/teldrive-go/schemas" + "github.com/divyam234/teldrive-go/utils" + + "github.com/divyam234/teldrive-go/types" + + "github.com/gin-gonic/gin" + "github.com/gotd/td/telegram" + "github.com/gotd/td/tg" + "github.com/jackc/pgtype" + "github.com/mitchellh/mapstructure" + range_parser "github.com/quantumsheep/range-parser" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type FileService struct { + Db *gorm.DB + ChannelID int64 +} + +func (fs *FileService) CreateFile(c *gin.Context) (*schemas.FileOut, *types.AppError) { + + var fileIn schemas.FileIn + if err := c.ShouldBindJSON(&fileIn); err != nil { + return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest} + } + + fileIn.Path = strings.TrimSpace(fileIn.Path) + + if fileIn.Path != "" { + var parent models.File + if err := fs.Db.Where("type = ? AND path = ?", "folder", fileIn.Path).First(&parent).Error; err != nil { + return nil, &types.AppError{Error: errors.New("parent directory not found"), Code: http.StatusNotFound} + } + fileIn.ParentID = parent.ID + } + + if fileIn.Type == "folder" { + fileIn.MimeType = "drive/folder" + var fullPath string + if fileIn.Path == "/" { + fullPath = "/" + fileIn.Name + } else { + fullPath = fileIn.Path + "/" + fileIn.Name + } + fileIn.Path = fullPath + fileIn.Depth = len(strings.Split(fileIn.Path, "/")) - 1 + } else if fileIn.Type == "file" { + fileIn.Path = "" + fileIn.ChannelID = fs.ChannelID + } + + fileIn.UserID = 815607893 + fileIn.Starred = utils.BoolPointer(false) + + payload := map[string]interface{}{} + + err := mapstructure.Decode(fileIn, &payload) + + if err != nil { + return nil, &types.AppError{Error: err, Code: http.StatusBadRequest} + } + + if payload["parts"] != nil { + parts, _ := json.Marshal(payload["parts"]) + payload["parts"] = pgtype.JSONB{Bytes: parts, Status: pgtype.Present} + } else { + delete(payload, "parts") + } + + if err := fs.Db.Model(&models.File{}).Create(&payload).Error; err != nil { + return nil, &types.AppError{Error: errors.New("failed to create a file"), Code: http.StatusBadRequest} + + } + + res := schemas.FileOut{} + + mapstructure.Decode(payload, &res) + + return &res, nil +} + +func (fs *FileService) UpdateFile(c *gin.Context) (*schemas.FileOut, *types.AppError) { + + fileID := c.Param("fileID") + + var fileUpdate schemas.FileIn + + var files []models.File + + if err := c.ShouldBindJSON(&fileUpdate); err != nil { + return nil, &types.AppError{Error: errors.New("invalid request payload"), Code: http.StatusBadRequest} + } + + payload := map[string]interface{}{} + + err := mapstructure.Decode(fileUpdate, &payload) + + if err != nil { + return nil, &types.AppError{Error: err, Code: http.StatusBadRequest} + } + + if err := fs.Db.Model(&files).Clauses(clause.Returning{}).Where("id = ?", fileID).Updates(payload).Error; err != nil { + return nil, &types.AppError{Error: errors.New("failed to update the file"), Code: http.StatusInternalServerError} + } + + if len(files) == 0 { + return nil, &types.AppError{Error: errors.New("file not found"), Code: http.StatusNotFound} + } + + file := mapFileToFileOut(files[0]) + + return &file, nil + +} + +func (fs *FileService) GetFileByID(c *gin.Context) (*schemas.FileOutFull, error) { + + fileID := c.Param("fileID") + + var file []schemas.FileOutFull + + fs.Db.Model(&models.File{}).Where("id = ?", fileID).Find(&file) + + if len(file) == 0 { + return nil, errors.New("file not found") + } + + return &file[0], nil +} + +// listFiles is the handler function for listing files based on the provided query parameters + +func (fs *FileService) ListFiles(c *gin.Context) (*schemas.FileResponse, *types.AppError) { + + var pagingParams schemas.PaginationQuery + pagingParams.PerPage = 200 + if err := c.ShouldBindQuery(&pagingParams); err != nil { + return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest} + } + + var sortingParams schemas.SortingQuery + sortingParams.Order = "asc" + sortingParams.Sort = "name" + if err := c.ShouldBindQuery(&sortingParams); err != nil { + return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest} + } + + var fileQuery schemas.FileQuery + fileQuery.Op = "list" + if err := c.ShouldBindQuery(&fileQuery); err != nil { + return nil, &types.AppError{Error: errors.New(""), Code: http.StatusBadRequest} + } + + query := fs.Db.Model(&models.File{}).Limit(pagingParams.PerPage) + + if fileQuery.Op == "list" { + filters := []string{} + filters = setOrderFilter(&pagingParams, &sortingParams, filters) + + query = query.Order("type DESC").Order(getOrder(sortingParams)). + Where("parent_id in (?)", fs.Db.Model(&models.File{}).Select("id").Where("path = ?", fileQuery.Path)). + Where(strings.Join(filters, " AND ")) + + } else if fileQuery.Op == "find" { + filters := []string{} + + filterQuery := map[string]interface{}{} + + err := mapstructure.Decode(fileQuery, &filterQuery) + + if err != nil { + return nil, &types.AppError{Error: err, Code: http.StatusBadRequest} + } + + delete(filterQuery, "op") + + if filterQuery["updated_at"] == nil { + delete(filterQuery, "updated_at") + } + + filters = setOrderFilter(&pagingParams, &sortingParams, filters) + + query = query.Order("type DESC").Order(getOrder(sortingParams)).Where(filterQuery). + Where(filters) + + } else if fileQuery.Op == "search" { + filters := []string{ + fmt.Sprintf("gitdrive.get_tsquery('%s') @@ gitdrive.get_tsvector(name)", fileQuery.Search), + } + filters = setOrderFilter(&pagingParams, &sortingParams, filters) + + query = query.Order(getOrder(sortingParams)).Where(strings.Join(filters, " AND ")) + } + + var results []schemas.FileOut + + query.Find(&results) + + token := "" + + if len(results) == pagingParams.PerPage { + lastItem := results[len(results)-1] + token = utils.GetField(&lastItem, utils.CamelToPascalCase(sortingParams.Sort)) + token = base64.StdEncoding.EncodeToString([]byte(token)) + } + + res := &schemas.FileResponse{Results: results, NextPageToken: token} + + return res, nil +} + +func (fs *FileService) GetFileStream(ctx context.Context) gin.HandlerFunc { + + return func(c *gin.Context) { + + fileID := c.Param("fileID") + + tgClient := utils.GetTgClient() + + tgClient.Workload++ + + w := c.Writer + r := c.Request + + res, err := cache.CachedFunction(fs.GetFileByID, fmt.Sprintf("files:%s", fileID))(c) + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + file := res.(*schemas.FileOutFull) + + w.Header().Set("Accept-Ranges", "bytes") + + var start, end int64 + + rangeHeader := r.Header.Get("Range") + + if rangeHeader == "" { + start = 0 + end = file.Size - 1 + w.WriteHeader(http.StatusOK) + } else { + ranges, err := range_parser.Parse(file.Size, r.Header.Get("Range")) + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + start = ranges[0].Start + end = ranges[0].End + w.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, file.Size)) + w.WriteHeader(http.StatusPartialContent) + } + + contentLength := end - start + 1 + + w.Header().Set("Content-Type", file.MimeType) + + w.Header().Set("Content-Length", strconv.FormatInt(contentLength, 10)) + + w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", file.Name)) + + parts, err := fs.getParts(ctx, tgClient.Tg, file) + + if err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + parts = rangedParts(parts, int64(start), int64(end)) + + ir, iw := io.Pipe() + + go func() { + defer iw.Close() + for _, part := range parts { + streamFilePart(ctx, tgClient.Tg, iw, &part, part.Start, part.End, 1024*1024) + } + }() + + if r.Method != "HEAD" { + io.CopyN(w, ir, contentLength) + + } + + defer func() { + tgClient.Workload-- + }() + } +} + +func (fs *FileService) getParts(ctx context.Context, tgClient *telegram.Client, file *schemas.FileOutFull) ([]types.Part, error) { + + ids := []tg.InputMessageID{} + + file.Parts.AssignTo(&ids) + + s := make([]tg.InputMessageClass, len(ids)) + + for i := range ids { + s[i] = &ids[i] + } + + api := tgClient.API() + + res, err := cache.CachedFunction(utils.GetChannelById, fmt.Sprintf("channels:%s", strconv.FormatInt(fs.ChannelID, 10)))(ctx, api, fs.ChannelID) + + if err != nil { + return nil, err + } + + channel := res.(*tg.Channel) + + messageRequest := tg.ChannelsGetMessagesRequest{Channel: &tg.InputChannel{ChannelID: fs.ChannelID, AccessHash: channel.AccessHash}, + ID: s} + + res, err = cache.CachedFunction(api.ChannelsGetMessages, fmt.Sprintf("messages:%s", file.ID))(ctx, &messageRequest) + + if err != nil { + return nil, err + } + + messages := res.(*tg.MessagesChannelMessages) + + parts := []types.Part{} + + for _, message := range messages.Messages { + item := message.(*tg.Message) + media := item.Media.(*tg.MessageMediaDocument) + document := media.Document.(*tg.Document) + location := document.AsInputDocumentFileLocation() + parts = append(parts, types.Part{Location: location, Start: 0, End: document.Size - 1, Size: document.Size}) + } + return parts, nil +} + +func mapFileToFileOut(file models.File) schemas.FileOut { + return schemas.FileOut{ + ID: file.ID, + Name: file.Name, + Type: file.Type, + MimeType: file.MimeType, + Path: file.Path, + Size: file.Size, + Starred: &file.Starred, + ParentID: file.ParentID, + UpdatedAt: file.UpdatedAt, + } +} + +func setOrderFilter(pagingParams *schemas.PaginationQuery, sortingParams *schemas.SortingQuery, filters []string) []string { + if pagingParams.NextPageToken != "" { + sortColumn := sortingParams.Sort + if sortColumn == "name" { + sortColumn = "name collate numeric" + } else { + sortColumn = sortingParams.Sort + } + + tokenValue, err := base64.StdEncoding.DecodeString(pagingParams.NextPageToken) + if err == nil { + if sortingParams.Order == "asc" { + filters = append(filters, fmt.Sprintf("%s > '%s'", sortColumn, string(tokenValue))) + } else { + filters = append(filters, fmt.Sprintf("%s < '%s'", sortColumn, string(tokenValue))) + } + } + } + return filters +} + +func getOrder(sortingParams schemas.SortingQuery) string { + sortColumn := sortingParams.Sort + if sortingParams.Sort == "name" { + sortColumn = "name collate numeric" + } + + return fmt.Sprintf("%s %s", sortColumn, strings.ToUpper(sortingParams.Order)) +} + +func chunk(ctx context.Context, tgClient *telegram.Client, part *types.Part, offset int64, limit int64) ([]byte, error) { + + req := &tg.UploadGetFileRequest{ + Offset: offset, + Limit: int(limit), + Location: part.Location, + } + + r, err := tgClient.API().UploadGetFile(ctx, req) + + if err != nil { + return nil, err + } + + switch result := r.(type) { + case *tg.UploadFile: + return result.Bytes, nil + default: + return nil, fmt.Errorf("unexpected type %T", r) + } +} + +func streamFilePart(ctx context.Context, tgClient *telegram.Client, writer *io.PipeWriter, part *types.Part, start, end, chunkSize int64) error { + + offset := start - (start % chunkSize) + firstPartCut := start - offset + lastPartCut := (end % chunkSize) + 1 + + partCount := int(math.Ceil(float64(end+1)/float64(chunkSize))) - int(math.Floor(float64(offset)/float64(chunkSize))) + + currentPart := 1 + + for { + r, _ := chunk(ctx, tgClient, part, offset, chunkSize) + + if len(r) == 0 { + break + } else if partCount == 1 { + r = r[firstPartCut:lastPartCut] + + } else if currentPart == 1 { + r = r[firstPartCut:] + + } else if currentPart == partCount { + r = r[:lastPartCut] + + } + + writer.Write(r) + + currentPart++ + + offset += chunkSize + + if currentPart > partCount { + break + } + + } + + return nil +} + +func rangedParts(parts []types.Part, start, end int64) []types.Part { + + chunkSize := parts[0].Size + + startPartNumber := utils.Max(int64(math.Ceil(float64(start)/float64(chunkSize)))-1, 0) + + endPartNumber := int64(math.Ceil(float64(end) / float64(chunkSize))) + + partsToDownload := parts[startPartNumber:endPartNumber] + partsToDownload[0].Start = start % chunkSize + partsToDownload[len(partsToDownload)-1].End = end % chunkSize + + return partsToDownload +} diff --git a/types/main.go b/types/main.go new file mode 100644 index 0000000..2a724ba --- /dev/null +++ b/types/main.go @@ -0,0 +1,15 @@ +package types + +import "github.com/gotd/td/tg" + +type AppError struct { + Error error + Code int +} + +type Part struct { + Location *tg.InputDocumentFileLocation + Size int64 + Start int64 + End int64 +} diff --git a/utils/main.go b/utils/main.go new file mode 100644 index 0000000..7bd688b --- /dev/null +++ b/utils/main.go @@ -0,0 +1,80 @@ +package utils + +import ( + "context" + "fmt" + "strings" + "time" + + "reflect" + + "github.com/gotd/td/tg" + "golang.org/x/exp/constraints" + + "unicode" +) + +func Max[T constraints.Ordered](a, b T) T { + if a > b { + return a + } + return b +} + +func CamelToPascalCase(input string) string { + var result strings.Builder + upperNext := true + + for _, char := range input { + if unicode.IsLetter(char) || unicode.IsDigit(char) { + if upperNext { + result.WriteRune(unicode.ToUpper(char)) + upperNext = false + } else { + result.WriteRune(char) + } + } else { + upperNext = true + } + } + + return result.String() +} + +func GetField(v interface{}, field string) string { + r := reflect.ValueOf(v) + f := reflect.Indirect(r).FieldByName(field) + fieldValue := f.Interface() + + switch v := fieldValue.(type) { + case string: + return v + case time.Time: + return v.Format(time.RFC3339) + default: + return "" + } +} + +func GetChannelById(ctx context.Context, client *tg.Client, channelID int64) (*tg.Channel, error) { + inputChannel := &tg.InputChannel{ + ChannelID: channelID, + AccessHash: 0, + } + channels, err := client.ChannelsGetChannels(ctx, []tg.InputChannelClass{inputChannel}) + + if err != nil { + return nil, fmt.Errorf("failed to fetch channel: %w", err) + } + + if len(channels.GetChats()) == 0 { + return nil, fmt.Errorf("no channels found") + } + + channel := channels.GetChats()[0].(*tg.Channel) + return channel, nil +} + +func BoolPointer(b bool) *bool { + return &b +} diff --git a/utils/tgclient.go b/utils/tgclient.go new file mode 100644 index 0000000..193b871 --- /dev/null +++ b/utils/tgclient.go @@ -0,0 +1,128 @@ +package utils + +import ( + "context" + "fmt" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/gotd/contrib/bg" + "github.com/gotd/contrib/middleware/ratelimit" + "github.com/gotd/td/telegram" + "github.com/pkg/errors" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + "golang.org/x/time/rate" +) + +type Client struct { + Tg *telegram.Client + Token string + Workload int +} + +var clients map[int]*Client + +func initClient(appID int, appHash, clientName, sessionDir string) *telegram.Client { + + sessionStorage := &telegram.FileSessionStorage{ + Path: filepath.Join(sessionDir, clientName+".json"), + } + + options := telegram.Options{ + SessionStorage: sessionStorage, + Middlewares: []telegram.Middleware{ + ratelimit.New(rate.Every(time.Millisecond*100), 5), + }, + } + + client := telegram.NewClient(appID, appHash, options) + + return client + +} + +func startClient(ctx context.Context, client *Client, lg *zap.Logger) (bg.StopFunc, error) { + + stop, err := bg.Connect(client.Tg) + + if err != nil { + return nil, errors.Wrap(err, "failed to start client") + } + + tguser, err := client.Tg.Self(ctx) + + if err != nil { + + if _, err := client.Tg.Auth().Bot(ctx, client.Token); err != nil { + return nil, err + } + tguser, _ = client.Tg.Self(ctx) + } + + lg.Info("started Client", zap.String("user", tguser.Username)) + return stop, nil +} + +func StartClients() { + + appID, err := strconv.Atoi(os.Getenv("APP_ID")) + + if err != nil { + return + } + + appHash := os.Getenv("APP_HASH") + + if appHash == "" { + return + } + + sessionDir := "sessions" + + if err := os.MkdirAll(sessionDir, 0700); err != nil { + return + } + + lg, _ := zap.NewDevelopment(zap.IncreaseLevel(zapcore.InfoLevel), zap.AddStacktrace(zapcore.FatalLevel)) + + var keysToSort []string + + for _, e := range os.Environ() { + if strings.HasPrefix(e, "MULTI_TOKEN") { + if i := strings.Index(e, "="); i >= 0 { + keysToSort = append(keysToSort, e[:i]) + } + } + } + + sort.Strings(keysToSort) + + clients = make(map[int]*Client) + + for idx, key := range keysToSort { + client := initClient(appID, appHash, fmt.Sprintf("client%d", idx), sessionDir) + clients[idx] = &Client{Tg: client, Token: os.Getenv(key)} + } + + ctx := context.Background() + + for _, client := range clients { + go startClient(ctx, client, lg) + } + +} + +func GetTgClient() *Client { + smallest := clients[0] + for _, client := range clients { + if client.Workload < smallest.Workload { + smallest = client + } + } + return smallest +}