Spaces:
Paused
Paused
Mohammad Shahid commited on
Commit ·
aa300d8
1
Parent(s): 2fa5701
added user managment
Browse files- cmd/fsb/main.go +1 -1
- cmd/fsb/run.go +8 -8
- cmd/fsb/session.go +1 -1
- go.mod +3 -2
- go.sum +22 -53
- internal/bot/client.go +1 -1
- internal/bot/helper.go +1 -1
- internal/bot/refresher.go +2 -2
- internal/bot/userbot.go +1 -1
- internal/bot/workers.go +1 -1
- internal/cache/cache.go +1 -1
- internal/commands/admin.go +292 -0
- internal/commands/batch.go +2 -2
- internal/commands/history.go +146 -0
- internal/commands/search.go +124 -0
- internal/commands/settings.go +250 -0
- internal/commands/start.go +2 -2
- internal/commands/stats.go +131 -0
- internal/commands/stream.go +20 -4
- internal/db/database.go +5 -1
- internal/db/models.go +74 -1
- internal/db/user_manager.go +417 -0
- internal/routes/internal_api.go +2 -2
- internal/routes/stream.go +24 -2
- internal/utils/hashing.go +2 -2
- internal/utils/helpers.go +45 -5
cmd/fsb/main.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package main
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
"fmt"
|
| 6 |
"os"
|
| 7 |
|
|
|
|
| 1 |
package main
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
"fmt"
|
| 6 |
"os"
|
| 7 |
|
cmd/fsb/run.go
CHANGED
|
@@ -3,14 +3,14 @@
|
|
| 3 |
package main
|
| 4 |
|
| 5 |
import (
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
| 10 |
-
"
|
| 11 |
-
"
|
| 12 |
-
"
|
| 13 |
-
"
|
| 14 |
"fmt"
|
| 15 |
"net/http"
|
| 16 |
"strings"
|
|
|
|
| 3 |
package main
|
| 4 |
|
| 5 |
import (
|
| 6 |
+
"TelegramCloud/tgf/config"
|
| 7 |
+
"TelegramCloud/tgf/internal/bot"
|
| 8 |
+
"TelegramCloud/tgf/internal/cache"
|
| 9 |
+
"TelegramCloud/tgf/internal/commands"
|
| 10 |
+
"TelegramCloud/tgf/internal/db"
|
| 11 |
+
"TelegramCloud/tgf/internal/routes"
|
| 12 |
+
"TelegramCloud/tgf/internal/types"
|
| 13 |
+
"TelegramCloud/tgf/internal/utils"
|
| 14 |
"fmt"
|
| 15 |
"net/http"
|
| 16 |
"strings"
|
cmd/fsb/session.go
CHANGED
|
@@ -3,7 +3,7 @@ package main
|
|
| 3 |
import (
|
| 4 |
"fmt"
|
| 5 |
|
| 6 |
-
"
|
| 7 |
|
| 8 |
"github.com/spf13/cobra"
|
| 9 |
)
|
|
|
|
| 3 |
import (
|
| 4 |
"fmt"
|
| 5 |
|
| 6 |
+
"TelegramCloud/tgf/pkg/qrlogin"
|
| 7 |
|
| 8 |
"github.com/spf13/cobra"
|
| 9 |
)
|
go.mod
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
module
|
| 2 |
|
| 3 |
go 1.21.3
|
| 4 |
|
|
@@ -13,6 +13,8 @@ require (
|
|
| 13 |
go.mongodb.org/mongo-driver v1.17.4 // Or a similar version number
|
| 14 |
)
|
| 15 |
|
|
|
|
|
|
|
| 16 |
require (
|
| 17 |
github.com/AnimeKaizoku/cacher v1.0.1 // indirect
|
| 18 |
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
|
@@ -20,7 +22,6 @@ require (
|
|
| 20 |
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
| 21 |
github.com/dustin/go-humanize v1.0.1 // indirect
|
| 22 |
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
| 23 |
-
github.com/glebarez/sqlite v1.11.0 // indirect
|
| 24 |
github.com/go-faster/errors v0.7.1 // indirect
|
| 25 |
github.com/go-faster/jx v1.1.0 // indirect
|
| 26 |
github.com/go-faster/xor v1.0.0 // indirect
|
|
|
|
| 1 |
+
module TelegramCloud/tgf
|
| 2 |
|
| 3 |
go 1.21.3
|
| 4 |
|
|
|
|
| 13 |
go.mongodb.org/mongo-driver v1.17.4 // Or a similar version number
|
| 14 |
)
|
| 15 |
|
| 16 |
+
require github.com/glebarez/sqlite v1.11.0
|
| 17 |
+
|
| 18 |
require (
|
| 19 |
github.com/AnimeKaizoku/cacher v1.0.1 // indirect
|
| 20 |
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
|
|
|
| 22 |
github.com/chenzhuoyu/iasm v0.9.1 // indirect
|
| 23 |
github.com/dustin/go-humanize v1.0.1 // indirect
|
| 24 |
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
|
|
|
| 25 |
github.com/go-faster/errors v0.7.1 // indirect
|
| 26 |
github.com/go-faster/jx v1.1.0 // indirect
|
| 27 |
github.com/go-faster/xor v1.0.0 // indirect
|
go.sum
CHANGED
|
@@ -4,12 +4,8 @@ github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1
|
|
| 4 |
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
| 5 |
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
| 6 |
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
| 7 |
-
github.com/celestix/gotgproto v1.0.0-beta16 h1:xV0h7L1V3DFWJe+wcY7KAtHFuN1RkP07vANHO5fjq9Q=
|
| 8 |
-
github.com/celestix/gotgproto v1.0.0-beta16/go.mod h1:Ey7AMTGRCXpG2iWR/eSFWwRrLCrmZ+l7HZq52NLEo7c=
|
| 9 |
github.com/celestix/gotgproto v1.0.0-beta18 h1:7884H/il+mzNreOQ4SqoMa4S5njt3UmGPKZTxPu38fU=
|
| 10 |
github.com/celestix/gotgproto v1.0.0-beta18/go.mod h1:osZOlN5irPByA0+3IPsZOH+Ibs0tOMSKmIdgGYEBRgE=
|
| 11 |
-
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
|
| 12 |
-
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
| 13 |
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
| 14 |
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
| 15 |
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
@@ -38,8 +34,6 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
|
| 38 |
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
| 39 |
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
| 40 |
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
| 41 |
-
github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc=
|
| 42 |
-
github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA=
|
| 43 |
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
| 44 |
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
| 45 |
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
|
@@ -59,16 +53,13 @@ github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtP
|
|
| 59 |
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
| 60 |
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
| 61 |
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
| 62 |
-
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
| 63 |
-
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
| 64 |
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
| 65 |
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
| 66 |
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
| 67 |
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 68 |
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
| 69 |
-
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
|
| 70 |
-
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
|
| 71 |
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
|
|
|
| 72 |
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
| 73 |
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 74 |
github.com/gotd/contrib v0.19.0 h1:O6GvMrRVeFslIHLUcpaHVzcl9/5PcgR2jQTIIeTyds0=
|
|
@@ -77,8 +68,6 @@ github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
|
|
| 77 |
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
| 78 |
github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
| 79 |
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
| 80 |
-
github.com/gotd/td v0.97.0 h1:EplGV6M6xFISLktsRFJZKm1NPyPjxR0XK9vbys0i/Qk=
|
| 81 |
-
github.com/gotd/td v0.97.0/go.mod h1:6SwTJiw/fkw81QU+WHqB2HZ+38s0UJJH1a2nqwezCfA=
|
| 82 |
github.com/gotd/td v0.105.0 h1:FjU9pgmL5Qt10+cosPCz4agvQT/hMBz6QMi1fFH7ekY=
|
| 83 |
github.com/gotd/td v0.105.0/go.mod h1:aVe5/LP/nNIyAqaW3CwB0Ckum+MkcfvazwMOLHV0bqQ=
|
| 84 |
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
@@ -93,13 +82,9 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr
|
|
| 93 |
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
| 94 |
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
| 95 |
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
| 96 |
-
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
|
| 97 |
-
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
|
| 98 |
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
| 99 |
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
| 100 |
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
| 101 |
-
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
|
| 102 |
-
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
| 103 |
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
| 104 |
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
| 105 |
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
@@ -149,9 +134,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|
| 149 |
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 150 |
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 151 |
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
| 152 |
-
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
| 153 |
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
| 154 |
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
|
|
| 155 |
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
| 156 |
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
| 157 |
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
@@ -165,16 +150,10 @@ github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gi
|
|
| 165 |
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
| 166 |
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
| 167 |
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
| 168 |
-
go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk=
|
| 169 |
-
go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo=
|
| 170 |
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
| 171 |
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
| 172 |
-
go.opentelemetry.io/otel v1.23.1 h1:Za4UzOqJYS+MUczKI320AtqZHZb7EqxO00jAHE0jmQY=
|
| 173 |
-
go.opentelemetry.io/otel v1.23.1/go.mod h1:Td0134eafDLcTS4y+zQ26GE8u3dEuRBiBCTUIRHaikA=
|
| 174 |
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
| 175 |
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
| 176 |
-
go.opentelemetry.io/otel/trace v1.23.1 h1:4LrmmEd8AU2rFvU1zegmvqW7+kWarxtNOPyeL6HmYY8=
|
| 177 |
-
go.opentelemetry.io/otel/trace v1.23.1/go.mod h1:4IpnpJFwr1mo/6HL8XIPJaE9y0+u1KcVmuW7dwFSVrI=
|
| 178 |
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
| 179 |
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
| 180 |
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|
@@ -183,8 +162,6 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
|
| 183 |
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
| 184 |
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
| 185 |
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
| 186 |
-
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
| 187 |
-
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
| 188 |
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
| 189 |
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
| 190 |
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
@@ -192,29 +169,21 @@ golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
|
|
| 192 |
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
| 193 |
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
| 194 |
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
| 195 |
-
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
| 196 |
-
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
| 197 |
-
golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
| 198 |
-
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
| 199 |
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
| 200 |
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
| 201 |
golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0=
|
| 202 |
golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
| 203 |
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
|
|
|
|
|
|
| 204 |
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 205 |
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
| 206 |
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
| 207 |
-
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
| 208 |
-
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
| 209 |
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
| 210 |
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
| 211 |
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 212 |
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 213 |
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 214 |
-
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
| 215 |
-
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
| 216 |
-
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
| 217 |
-
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
| 218 |
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
| 219 |
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
| 220 |
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
@@ -224,10 +193,6 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
|
| 224 |
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 225 |
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 226 |
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 227 |
-
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
| 228 |
-
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 229 |
-
golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
| 230 |
-
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 231 |
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
| 232 |
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 233 |
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
@@ -236,10 +201,6 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
|
| 236 |
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
| 237 |
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
| 238 |
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
| 239 |
-
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
| 240 |
-
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
| 241 |
-
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
| 242 |
-
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
| 243 |
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
| 244 |
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
| 245 |
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
@@ -247,6 +208,8 @@ golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
|
| 247 |
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 248 |
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 249 |
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
|
|
|
|
|
|
| 250 |
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 251 |
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
| 252 |
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
@@ -258,26 +221,32 @@ gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYs
|
|
| 258 |
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 259 |
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
| 260 |
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 261 |
-
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
|
| 262 |
-
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
| 263 |
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
| 264 |
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
| 265 |
-
modernc.org/
|
| 266 |
-
modernc.org/
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 267 |
modernc.org/libc v1.55.2 h1:UN5eoBYrKp1b+gPYx8nZj5H7uxeybvyoQJfvcg+Bqjc=
|
| 268 |
modernc.org/libc v1.55.2/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
| 269 |
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
| 270 |
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
| 271 |
-
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
|
| 272 |
-
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
|
| 273 |
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
| 274 |
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
| 275 |
-
modernc.org/
|
| 276 |
-
modernc.org/
|
|
|
|
|
|
|
| 277 |
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
|
| 278 |
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
|
| 279 |
-
|
| 280 |
-
|
|
|
|
|
|
|
| 281 |
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
|
| 282 |
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
| 283 |
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
|
|
|
| 4 |
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
|
| 5 |
github.com/bytedance/sonic v1.10.2 h1:GQebETVBxYB7JGWJtLBi07OVzWwt+8dWA00gEVW2ZFE=
|
| 6 |
github.com/bytedance/sonic v1.10.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4=
|
|
|
|
|
|
|
| 7 |
github.com/celestix/gotgproto v1.0.0-beta18 h1:7884H/il+mzNreOQ4SqoMa4S5njt3UmGPKZTxPu38fU=
|
| 8 |
github.com/celestix/gotgproto v1.0.0-beta18/go.mod h1:osZOlN5irPByA0+3IPsZOH+Ibs0tOMSKmIdgGYEBRgE=
|
|
|
|
|
|
|
| 9 |
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
| 10 |
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
| 11 |
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
|
|
|
| 34 |
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
| 35 |
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
| 36 |
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
|
|
|
|
|
|
| 37 |
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
| 38 |
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
| 39 |
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
|
|
|
| 53 |
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
| 54 |
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
| 55 |
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
|
|
|
|
|
|
| 56 |
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
| 57 |
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
| 58 |
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
| 59 |
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
| 60 |
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
|
|
|
|
|
| 61 |
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo=
|
| 62 |
+
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
| 63 |
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
| 64 |
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
| 65 |
github.com/gotd/contrib v0.19.0 h1:O6GvMrRVeFslIHLUcpaHVzcl9/5PcgR2jQTIIeTyds0=
|
|
|
|
| 68 |
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
| 69 |
github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
| 70 |
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
|
|
|
|
|
|
| 71 |
github.com/gotd/td v0.105.0 h1:FjU9pgmL5Qt10+cosPCz4agvQT/hMBz6QMi1fFH7ekY=
|
| 72 |
github.com/gotd/td v0.105.0/go.mod h1:aVe5/LP/nNIyAqaW3CwB0Ckum+MkcfvazwMOLHV0bqQ=
|
| 73 |
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
|
|
|
| 82 |
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
| 83 |
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
|
| 84 |
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
|
|
|
|
|
|
|
| 85 |
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
|
| 86 |
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
|
| 87 |
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
|
|
|
|
|
|
| 88 |
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
| 89 |
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
| 90 |
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
|
|
|
| 134 |
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
| 135 |
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
| 136 |
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
|
|
|
| 137 |
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
| 138 |
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
| 139 |
+
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
| 140 |
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
| 141 |
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
| 142 |
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
|
|
|
| 150 |
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
| 151 |
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI=
|
| 152 |
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
|
|
|
|
|
|
| 153 |
go.mongodb.org/mongo-driver v1.17.4 h1:jUorfmVzljjr0FLzYQsGP8cgN/qzzxlY9Vh0C9KFXVw=
|
| 154 |
go.mongodb.org/mongo-driver v1.17.4/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ=
|
|
|
|
|
|
|
| 155 |
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
| 156 |
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
|
|
|
|
|
|
| 157 |
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
|
| 158 |
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
| 159 |
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
|
|
|
| 162 |
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
| 163 |
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
| 164 |
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
|
|
|
|
|
|
| 165 |
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
| 166 |
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
| 167 |
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
|
|
|
| 169 |
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
| 170 |
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
| 171 |
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
|
| 173 |
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
|
| 174 |
golang.org/x/exp v0.0.0-20230116083435-1de6713980de h1:DBWn//IJw30uYCgERoxCg84hWtA97F4wMiKOIh00Uf0=
|
| 175 |
golang.org/x/exp v0.0.0-20230116083435-1de6713980de/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
|
| 176 |
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
| 177 |
+
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
|
| 178 |
+
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
| 179 |
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
| 180 |
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
| 181 |
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
|
|
|
|
|
|
| 182 |
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
| 183 |
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
| 184 |
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 185 |
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
| 186 |
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
| 188 |
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
| 189 |
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
|
|
| 193 |
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 194 |
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
| 195 |
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 196 |
golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM=
|
| 197 |
golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
| 198 |
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
|
|
|
| 201 |
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
| 202 |
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
| 203 |
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
| 205 |
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
| 206 |
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
|
|
|
| 208 |
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
| 209 |
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
| 210 |
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
| 211 |
+
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
|
| 212 |
+
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
|
| 213 |
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
| 214 |
google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I=
|
| 215 |
google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
|
|
|
| 221 |
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
| 222 |
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
| 223 |
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
|
|
|
|
|
|
| 224 |
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
| 225 |
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
| 226 |
+
modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ=
|
| 227 |
+
modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ=
|
| 228 |
+
modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y=
|
| 229 |
+
modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s=
|
| 230 |
+
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
|
| 231 |
+
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
|
| 232 |
+
modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw=
|
| 233 |
+
modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU=
|
| 234 |
modernc.org/libc v1.55.2 h1:UN5eoBYrKp1b+gPYx8nZj5H7uxeybvyoQJfvcg+Bqjc=
|
| 235 |
modernc.org/libc v1.55.2/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w=
|
| 236 |
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
|
| 237 |
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
|
|
|
|
|
|
|
| 238 |
modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E=
|
| 239 |
modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU=
|
| 240 |
+
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
|
| 241 |
+
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
| 242 |
+
modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc=
|
| 243 |
+
modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss=
|
| 244 |
modernc.org/sqlite v1.30.2 h1:IPVVkhLu5mMVnS1dQgh3h0SAACRWcVk7aoLP9Us3UCk=
|
| 245 |
modernc.org/sqlite v1.30.2/go.mod h1:DUmsiWQDaAvU4abhc/N+djlom/L2o8f7gZ95RCvyoLU=
|
| 246 |
+
modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
|
| 247 |
+
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
| 248 |
+
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
| 249 |
+
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
| 250 |
nhooyr.io/websocket v1.8.11 h1:f/qXNc2/3DpoSZkHt1DQu6rj4zGC8JmkkLkWss0MgN0=
|
| 251 |
nhooyr.io/websocket v1.8.11/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
| 252 |
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
internal/bot/client.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
"context"
|
| 6 |
"time"
|
| 7 |
|
|
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
"context"
|
| 6 |
"time"
|
| 7 |
|
internal/bot/helper.go
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
package bot
|
| 3 |
|
| 4 |
import (
|
| 5 |
-
"
|
| 6 |
"context"
|
| 7 |
"fmt"
|
| 8 |
"math/rand"
|
|
|
|
| 2 |
package bot
|
| 3 |
|
| 4 |
import (
|
| 5 |
+
"TelegramCloud/tgf/internal/utils"
|
| 6 |
"context"
|
| 7 |
"fmt"
|
| 8 |
"math/rand"
|
internal/bot/refresher.go
CHANGED
|
@@ -2,8 +2,8 @@
|
|
| 2 |
package bot
|
| 3 |
|
| 4 |
import (
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
"context"
|
| 8 |
"sync/atomic"
|
| 9 |
"time"
|
|
|
|
| 2 |
package bot
|
| 3 |
|
| 4 |
import (
|
| 5 |
+
"TelegramCloud/tgf/internal/db"
|
| 6 |
+
"TelegramCloud/tgf/internal/utils"
|
| 7 |
"context"
|
| 8 |
"sync/atomic"
|
| 9 |
"time"
|
internal/bot/userbot.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
"errors"
|
| 6 |
|
| 7 |
"github.com/celestix/gotgproto"
|
|
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
"errors"
|
| 6 |
|
| 7 |
"github.com/celestix/gotgproto"
|
internal/bot/workers.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
"context"
|
| 6 |
"fmt"
|
| 7 |
"os"
|
|
|
|
| 1 |
package bot
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
"context"
|
| 6 |
"fmt"
|
| 7 |
"os"
|
internal/cache/cache.go
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
package cache
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
"bytes"
|
| 6 |
"encoding/gob"
|
| 7 |
"sync"
|
|
|
|
| 1 |
package cache
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/types"
|
| 5 |
"bytes"
|
| 6 |
"encoding/gob"
|
| 7 |
"sync"
|
internal/commands/admin.go
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package commands
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
+
"TelegramCloud/tgf/internal/db"
|
| 6 |
+
"TelegramCloud/tgf/internal/utils"
|
| 7 |
+
"context"
|
| 8 |
+
"fmt"
|
| 9 |
+
"strconv"
|
| 10 |
+
"strings"
|
| 11 |
+
|
| 12 |
+
"github.com/celestix/gotgproto/dispatcher"
|
| 13 |
+
"github.com/celestix/gotgproto/dispatcher/handlers"
|
| 14 |
+
"github.com/celestix/gotgproto/ext"
|
| 15 |
+
"github.com/gotd/td/telegram/message/styling"
|
| 16 |
+
"go.uber.org/zap"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
func (m *command) LoadAdmin(dispatcher dispatcher.Dispatcher) {
|
| 20 |
+
log := m.log.Named("admin")
|
| 21 |
+
defer log.Sugar().Info("Loaded admin commands")
|
| 22 |
+
dispatcher.AddHandler(handlers.NewCommand("promote", promoteHandler))
|
| 23 |
+
dispatcher.AddHandler(handlers.NewCommand("demote", demoteHandler))
|
| 24 |
+
dispatcher.AddHandler(handlers.NewCommand("ban", banHandler))
|
| 25 |
+
dispatcher.AddHandler(handlers.NewCommand("userinfo", userinfoHandler))
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
func promoteHandler(ctx *ext.Context, u *ext.Update) error {
|
| 29 |
+
log := utils.Logger.Named("promoteHandler")
|
| 30 |
+
adminID := u.EffectiveUser().ID
|
| 31 |
+
|
| 32 |
+
// Check if user is admin
|
| 33 |
+
if !isUserAdmin(adminID) {
|
| 34 |
+
ctx.Reply(u, "❌ You don't have permission to use this command.", nil)
|
| 35 |
+
return dispatcher.EndGroups
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
// Check if database is available
|
| 39 |
+
if db.UserMgr == nil {
|
| 40 |
+
ctx.Reply(u, "❌ Admin commands are currently unavailable. Database not connected.", nil)
|
| 41 |
+
return dispatcher.EndGroups
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// Parse command arguments
|
| 45 |
+
args := strings.Fields(u.EffectiveMessage.GetMessage())
|
| 46 |
+
if len(args) < 3 {
|
| 47 |
+
helpText := `👑 **Promote User**
|
| 48 |
+
|
| 49 |
+
Usage: ` + "`/promote <user_id> <role>`" + `
|
| 50 |
+
|
| 51 |
+
**Available roles:**
|
| 52 |
+
• ` + "`basic`" + ` - Basic user (default limits)
|
| 53 |
+
• ` + "`premium`" + ` - Premium user (higher limits)
|
| 54 |
+
• ` + "`admin`" + ` - Administrator (unlimited)
|
| 55 |
+
|
| 56 |
+
**Example:**
|
| 57 |
+
` + "`/promote 123456789 premium`" + ``
|
| 58 |
+
|
| 59 |
+
styledMessage := []styling.StyledTextOption{
|
| 60 |
+
styling.Plain(helpText),
|
| 61 |
+
}
|
| 62 |
+
ctx.Reply(u, styledMessage, nil)
|
| 63 |
+
return dispatcher.EndGroups
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
// Parse user ID
|
| 67 |
+
userID, err := strconv.ParseInt(args[1], 10, 64)
|
| 68 |
+
if err != nil {
|
| 69 |
+
ctx.Reply(u, "❌ Invalid user ID. Please provide a valid numeric user ID.", nil)
|
| 70 |
+
return dispatcher.EndGroups
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
// Parse role
|
| 74 |
+
role := strings.ToLower(args[2])
|
| 75 |
+
if role != db.RoleBasic && role != db.RolePremium && role != db.RoleAdmin {
|
| 76 |
+
ctx.Reply(u, "❌ Invalid role. Available roles: basic, premium, admin", nil)
|
| 77 |
+
return dispatcher.EndGroups
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
// Promote user
|
| 81 |
+
err = db.UserMgr.PromoteUser(context.Background(), userID, role)
|
| 82 |
+
if err != nil {
|
| 83 |
+
log.Error("Failed to promote user", zap.Error(err))
|
| 84 |
+
ctx.Reply(u, "❌ Failed to promote user. Please try again later.", nil)
|
| 85 |
+
return dispatcher.EndGroups
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// Get updated user stats to show limits
|
| 89 |
+
stats, err := db.UserMgr.GetOrCreateUserStats(context.Background(), userID)
|
| 90 |
+
if err != nil {
|
| 91 |
+
log.Error("Failed to get user stats after promotion", zap.Error(err))
|
| 92 |
+
ctx.Reply(u, fmt.Sprintf("✅ User %d has been promoted to **%s** role.", userID, role), nil)
|
| 93 |
+
return dispatcher.EndGroups
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
roleEmoji := "👤"
|
| 97 |
+
switch role {
|
| 98 |
+
case db.RolePremium:
|
| 99 |
+
roleEmoji = "⭐"
|
| 100 |
+
case db.RoleAdmin:
|
| 101 |
+
roleEmoji = "👑"
|
| 102 |
+
}
|
| 103 |
+
|
| 104 |
+
limitsText := fmt.Sprintf("Daily: %d, Monthly: %d", stats.DailyLimit, stats.MonthlyLimit)
|
| 105 |
+
if role == db.RoleAdmin {
|
| 106 |
+
limitsText = "Unlimited"
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
successText := fmt.Sprintf(`✅ **User Promoted Successfully**
|
| 110 |
+
|
| 111 |
+
👤 **User ID:** %d
|
| 112 |
+
%s **New Role:** %s
|
| 113 |
+
📊 **New Limits:** %s
|
| 114 |
+
|
| 115 |
+
The user will see updated limits in their next ` + "`/stats`" + ` command.`,
|
| 116 |
+
userID, roleEmoji, role, limitsText,
|
| 117 |
+
)
|
| 118 |
+
|
| 119 |
+
styledMessage := []styling.StyledTextOption{
|
| 120 |
+
styling.Plain(successText),
|
| 121 |
+
}
|
| 122 |
+
ctx.Reply(u, styledMessage, nil)
|
| 123 |
+
return dispatcher.EndGroups
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
func demoteHandler(ctx *ext.Context, u *ext.Update) error {
|
| 127 |
+
log := utils.Logger.Named("demoteHandler")
|
| 128 |
+
adminID := u.EffectiveUser().ID
|
| 129 |
+
|
| 130 |
+
// Check if user is admin
|
| 131 |
+
if !isUserAdmin(adminID) {
|
| 132 |
+
ctx.Reply(u, "❌ You don't have permission to use this command.", nil)
|
| 133 |
+
return dispatcher.EndGroups
|
| 134 |
+
}
|
| 135 |
+
|
| 136 |
+
// Check if database is available
|
| 137 |
+
if db.UserMgr == nil {
|
| 138 |
+
ctx.Reply(u, "❌ Admin commands are currently unavailable. Database not connected.", nil)
|
| 139 |
+
return dispatcher.EndGroups
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
// Parse command arguments
|
| 143 |
+
args := strings.Fields(u.EffectiveMessage.GetMessage())
|
| 144 |
+
if len(args) < 2 {
|
| 145 |
+
ctx.Reply(u, "Usage: "+"`/demote <user_id>`"+"\n\nThis will demote the user to basic role.", nil)
|
| 146 |
+
return dispatcher.EndGroups
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
// Parse user ID
|
| 150 |
+
userID, err := strconv.ParseInt(args[1], 10, 64)
|
| 151 |
+
if err != nil {
|
| 152 |
+
ctx.Reply(u, "❌ Invalid user ID. Please provide a valid numeric user ID.", nil)
|
| 153 |
+
return dispatcher.EndGroups
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
// Demote user to basic role
|
| 157 |
+
err = db.UserMgr.PromoteUser(context.Background(), userID, db.RoleBasic)
|
| 158 |
+
if err != nil {
|
| 159 |
+
log.Error("Failed to demote user", zap.Error(err))
|
| 160 |
+
ctx.Reply(u, "❌ Failed to demote user. Please try again later.", nil)
|
| 161 |
+
return dispatcher.EndGroups
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
successText := fmt.Sprintf("✅ User %d has been demoted to **basic** role with default limits.", userID)
|
| 165 |
+
ctx.Reply(u, successText, nil)
|
| 166 |
+
return dispatcher.EndGroups
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
func userinfoHandler(ctx *ext.Context, u *ext.Update) error {
|
| 170 |
+
log := utils.Logger.Named("userinfoHandler")
|
| 171 |
+
adminID := u.EffectiveUser().ID
|
| 172 |
+
|
| 173 |
+
// Check if user is admin
|
| 174 |
+
if !isUserAdmin(adminID) {
|
| 175 |
+
ctx.Reply(u, "❌ You don't have permission to use this command.", nil)
|
| 176 |
+
return dispatcher.EndGroups
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
// Check if database is available
|
| 180 |
+
if db.UserMgr == nil {
|
| 181 |
+
ctx.Reply(u, "❌ Admin commands are currently unavailable. Database not connected.", nil)
|
| 182 |
+
return dispatcher.EndGroups
|
| 183 |
+
}
|
| 184 |
+
|
| 185 |
+
// Parse command arguments
|
| 186 |
+
args := strings.Fields(u.EffectiveMessage.GetMessage())
|
| 187 |
+
if len(args) < 2 {
|
| 188 |
+
ctx.Reply(u, "Usage: "+"`/userinfo <user_id>`"+"\n\nGet detailed information about a user.", nil)
|
| 189 |
+
return dispatcher.EndGroups
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
// Parse user ID
|
| 193 |
+
userID, err := strconv.ParseInt(args[1], 10, 64)
|
| 194 |
+
if err != nil {
|
| 195 |
+
ctx.Reply(u, "❌ Invalid user ID. Please provide a valid numeric user ID.", nil)
|
| 196 |
+
return dispatcher.EndGroups
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Get user stats
|
| 200 |
+
stats, err := db.UserMgr.GetOrCreateUserStats(context.Background(), userID)
|
| 201 |
+
if err != nil {
|
| 202 |
+
log.Error("Failed to get user stats", zap.Error(err))
|
| 203 |
+
ctx.Reply(u, "❌ Failed to get user information. Please try again later.", nil)
|
| 204 |
+
return dispatcher.EndGroups
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
// Get today's usage
|
| 208 |
+
today := "2024-01-01" // You might want to use time.Now().Format("2006-01-02")
|
| 209 |
+
dailyUsage, err := db.UserMgr.GetDailyUsage(context.Background(), userID, today)
|
| 210 |
+
if err != nil {
|
| 211 |
+
dailyUsage = &db.DailyUsage{FilesCount: 0, BandwidthUsed: 0}
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
// Format information
|
| 215 |
+
roleEmoji := "👤"
|
| 216 |
+
switch stats.Role {
|
| 217 |
+
case db.RolePremium:
|
| 218 |
+
roleEmoji = "⭐"
|
| 219 |
+
case db.RoleAdmin:
|
| 220 |
+
roleEmoji = "👑"
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
limitsText := fmt.Sprintf("Daily: %d, Monthly: %d", stats.DailyLimit, stats.MonthlyLimit)
|
| 224 |
+
if stats.Role == db.RoleAdmin {
|
| 225 |
+
limitsText = "Unlimited"
|
| 226 |
+
}
|
| 227 |
+
|
| 228 |
+
userInfoText := fmt.Sprintf(`👤 **User Information**
|
| 229 |
+
|
| 230 |
+
🆔 **User ID:** %d
|
| 231 |
+
%s **Role:** %s
|
| 232 |
+
📊 **Limits:** %s
|
| 233 |
+
|
| 234 |
+
**Statistics:**
|
| 235 |
+
📁 Files uploaded: %d
|
| 236 |
+
💾 Total storage: %s
|
| 237 |
+
📥 Total downloads: %d
|
| 238 |
+
🌐 Bandwidth used: %s
|
| 239 |
+
|
| 240 |
+
**Today's Usage:**
|
| 241 |
+
📁 Files: %d
|
| 242 |
+
🌐 Bandwidth: %s
|
| 243 |
+
|
| 244 |
+
**Account Info:**
|
| 245 |
+
📅 Member since: %s
|
| 246 |
+
🕒 Last active: %s`,
|
| 247 |
+
userID, roleEmoji, stats.Role, limitsText,
|
| 248 |
+
stats.FilesUploaded,
|
| 249 |
+
formatBytes(stats.TotalSize),
|
| 250 |
+
stats.TotalDownloads,
|
| 251 |
+
formatBytes(stats.BandwidthUsed),
|
| 252 |
+
dailyUsage.FilesCount,
|
| 253 |
+
formatBytes(dailyUsage.BandwidthUsed),
|
| 254 |
+
stats.CreatedAt.Format("Jan 2, 2006"),
|
| 255 |
+
stats.LastActive.Format("Jan 2, 15:04"),
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
styledMessage := []styling.StyledTextOption{
|
| 259 |
+
styling.Plain(userInfoText),
|
| 260 |
+
}
|
| 261 |
+
ctx.Reply(u, styledMessage, nil)
|
| 262 |
+
return dispatcher.EndGroups
|
| 263 |
+
}
|
| 264 |
+
|
| 265 |
+
func banHandler(ctx *ext.Context, u *ext.Update) error {
|
| 266 |
+
// This is a placeholder for ban functionality
|
| 267 |
+
// You could implement this by adding a "banned" field to user stats
|
| 268 |
+
ctx.Reply(u, "🚧 Ban functionality is not yet implemented.", nil)
|
| 269 |
+
return dispatcher.EndGroups
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
// isUserAdmin checks if a user ID is an admin
|
| 273 |
+
func isUserAdmin(userID int64) bool {
|
| 274 |
+
// Check against bot owner or admin list from config
|
| 275 |
+
// For now, we'll use a simple check - you can enhance this
|
| 276 |
+
adminUsers := []int64{
|
| 277 |
+
// Add your admin user IDs here
|
| 278 |
+
// config.ValueOf.BotOwnerID, // if you have this in config
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
// If no admin users configured, check if user is in allowed users and assume first one is admin
|
| 282 |
+
if len(adminUsers) == 0 && len(config.ValueOf.AllowedUsers) > 0 {
|
| 283 |
+
return userID == config.ValueOf.AllowedUsers[0]
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
for _, adminID := range adminUsers {
|
| 287 |
+
if userID == adminID {
|
| 288 |
+
return true
|
| 289 |
+
}
|
| 290 |
+
}
|
| 291 |
+
return false
|
| 292 |
+
}
|
internal/commands/batch.go
CHANGED
|
@@ -3,8 +3,8 @@
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
"fmt"
|
| 9 |
"math/rand"
|
| 10 |
"strings"
|
|
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
+
"TelegramCloud/tgf/internal/state"
|
| 7 |
+
"TelegramCloud/tgf/internal/utils"
|
| 8 |
"fmt"
|
| 9 |
"math/rand"
|
| 10 |
"strings"
|
internal/commands/history.go
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package commands
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/db"
|
| 5 |
+
"TelegramCloud/tgf/internal/utils"
|
| 6 |
+
"context"
|
| 7 |
+
"fmt"
|
| 8 |
+
"strings"
|
| 9 |
+
"time"
|
| 10 |
+
|
| 11 |
+
"github.com/celestix/gotgproto/dispatcher"
|
| 12 |
+
"github.com/celestix/gotgproto/dispatcher/handlers"
|
| 13 |
+
"github.com/celestix/gotgproto/ext"
|
| 14 |
+
"github.com/gotd/td/telegram/message/styling"
|
| 15 |
+
"github.com/gotd/td/tg"
|
| 16 |
+
"go.uber.org/zap"
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
func (m *command) LoadHistory(dispatcher dispatcher.Dispatcher) {
|
| 20 |
+
log := m.log.Named("history")
|
| 21 |
+
defer log.Sugar().Info("Loaded history command")
|
| 22 |
+
dispatcher.AddHandler(handlers.NewCommand("history", historyHandler))
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
func historyHandler(ctx *ext.Context, u *ext.Update) error {
|
| 26 |
+
log := utils.Logger.Named("historyHandler")
|
| 27 |
+
userID := u.EffectiveUser().ID
|
| 28 |
+
|
| 29 |
+
// Check if database is available
|
| 30 |
+
if db.UserMgr == nil {
|
| 31 |
+
ctx.Reply(u, "❌ History is currently unavailable. Database not connected.", nil)
|
| 32 |
+
return dispatcher.EndGroups
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
// Get user's file history (last 10 files)
|
| 36 |
+
history, err := db.UserMgr.GetUserHistory(context.Background(), userID, 10, 0)
|
| 37 |
+
if err != nil {
|
| 38 |
+
log.Error("Failed to get user history", zap.Error(err))
|
| 39 |
+
ctx.Reply(u, "❌ Failed to retrieve your history. Please try again later.", nil)
|
| 40 |
+
return dispatcher.EndGroups
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
if len(history) == 0 {
|
| 44 |
+
ctx.Reply(u, "📁 Your file history is empty. Upload some files to see them here!", nil)
|
| 45 |
+
return dispatcher.EndGroups
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
// Build history message
|
| 49 |
+
historyText := "📁 **Your Recent Files** (Last 10)\n\n"
|
| 50 |
+
|
| 51 |
+
for i, file := range history {
|
| 52 |
+
// Truncate filename if too long
|
| 53 |
+
displayName := file.FileName
|
| 54 |
+
if len(displayName) > 30 {
|
| 55 |
+
displayName = displayName[:27] + "..."
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
// Format file size
|
| 59 |
+
sizeFormatted := formatBytes(file.FileSize)
|
| 60 |
+
|
| 61 |
+
// Format time
|
| 62 |
+
timeAgo := formatTimeAgo(file.CreatedAt)
|
| 63 |
+
|
| 64 |
+
// Get file type emoji
|
| 65 |
+
emoji := getFileTypeEmoji(file.MimeType)
|
| 66 |
+
|
| 67 |
+
historyText += fmt.Sprintf("`%d.` %s **%s**\n", i+1, emoji, displayName)
|
| 68 |
+
historyText += fmt.Sprintf(" 💾 %s • 📥 %d downloads • %s\n", sizeFormatted, file.DownloadCount, timeAgo)
|
| 69 |
+
|
| 70 |
+
// Add expiration info if applicable
|
| 71 |
+
if file.ExpiresAt != nil {
|
| 72 |
+
if file.ExpiresAt.Before(time.Now()) {
|
| 73 |
+
historyText += " ⚠️ **Expired**\n"
|
| 74 |
+
} else {
|
| 75 |
+
expiresIn := formatTimeAgo(*file.ExpiresAt)
|
| 76 |
+
historyText += fmt.Sprintf(" ⏰ Expires %s\n", expiresIn)
|
| 77 |
+
}
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
historyText += "\n"
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
historyText += "💡 Use `/search <filename>` to find specific files\n"
|
| 84 |
+
historyText += "💡 Use `/settings` to configure file expiration"
|
| 85 |
+
|
| 86 |
+
// Create styled message
|
| 87 |
+
styledMessage := []styling.StyledTextOption{
|
| 88 |
+
styling.Plain(historyText),
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// Create inline keyboard for navigation
|
| 92 |
+
keyboard := &tg.ReplyInlineMarkup{
|
| 93 |
+
Rows: []tg.KeyboardButtonRow{
|
| 94 |
+
{
|
| 95 |
+
Buttons: []tg.KeyboardButtonClass{
|
| 96 |
+
&tg.KeyboardButtonCallback{Text: "🔄 Refresh", Data: []byte("history_refresh")},
|
| 97 |
+
&tg.KeyboardButtonCallback{Text: "🔍 Search", Data: []byte("history_search")},
|
| 98 |
+
},
|
| 99 |
+
},
|
| 100 |
+
},
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
ctx.Reply(u, styledMessage, &ext.ReplyOpts{Markup: keyboard})
|
| 104 |
+
return dispatcher.EndGroups
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
// getFileTypeEmoji returns appropriate emoji for file type
|
| 108 |
+
func getFileTypeEmoji(mimeType string) string {
|
| 109 |
+
switch {
|
| 110 |
+
case strings.HasPrefix(mimeType, "image/"):
|
| 111 |
+
return "🖼️"
|
| 112 |
+
case strings.HasPrefix(mimeType, "video/"):
|
| 113 |
+
return "🎥"
|
| 114 |
+
case strings.HasPrefix(mimeType, "audio/"):
|
| 115 |
+
return "🎵"
|
| 116 |
+
case strings.Contains(mimeType, "pdf"):
|
| 117 |
+
return "📄"
|
| 118 |
+
case strings.Contains(mimeType, "zip") || strings.Contains(mimeType, "rar") || strings.Contains(mimeType, "7z"):
|
| 119 |
+
return "📦"
|
| 120 |
+
case strings.Contains(mimeType, "text"):
|
| 121 |
+
return "📝"
|
| 122 |
+
default:
|
| 123 |
+
return "📄"
|
| 124 |
+
}
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
// formatTimeAgo formats time duration to human readable string
|
| 128 |
+
func formatTimeAgo(t time.Time) string {
|
| 129 |
+
duration := time.Since(t)
|
| 130 |
+
|
| 131 |
+
if duration < time.Minute {
|
| 132 |
+
return "just now"
|
| 133 |
+
} else if duration < time.Hour {
|
| 134 |
+
minutes := int(duration.Minutes())
|
| 135 |
+
return fmt.Sprintf("%d min ago", minutes)
|
| 136 |
+
} else if duration < 24*time.Hour {
|
| 137 |
+
hours := int(duration.Hours())
|
| 138 |
+
return fmt.Sprintf("%d hr ago", hours)
|
| 139 |
+
} else {
|
| 140 |
+
days := int(duration.Hours() / 24)
|
| 141 |
+
if days == 1 {
|
| 142 |
+
return "1 day ago"
|
| 143 |
+
}
|
| 144 |
+
return fmt.Sprintf("%d days ago", days)
|
| 145 |
+
}
|
| 146 |
+
}
|
internal/commands/search.go
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package commands
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/db"
|
| 5 |
+
"TelegramCloud/tgf/internal/utils"
|
| 6 |
+
"context"
|
| 7 |
+
"fmt"
|
| 8 |
+
"strings"
|
| 9 |
+
|
| 10 |
+
"github.com/celestix/gotgproto/dispatcher"
|
| 11 |
+
"github.com/celestix/gotgproto/dispatcher/handlers"
|
| 12 |
+
"github.com/celestix/gotgproto/ext"
|
| 13 |
+
"github.com/gotd/td/telegram/message/styling"
|
| 14 |
+
"github.com/gotd/td/tg"
|
| 15 |
+
"go.uber.org/zap"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
func (m *command) LoadSearch(dispatcher dispatcher.Dispatcher) {
|
| 19 |
+
log := m.log.Named("search")
|
| 20 |
+
defer log.Sugar().Info("Loaded search command")
|
| 21 |
+
dispatcher.AddHandler(handlers.NewCommand("search", searchHandler))
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
func searchHandler(ctx *ext.Context, u *ext.Update) error {
|
| 25 |
+
log := utils.Logger.Named("searchHandler")
|
| 26 |
+
userID := u.EffectiveUser().ID
|
| 27 |
+
|
| 28 |
+
// Check if database is available
|
| 29 |
+
if db.UserMgr == nil {
|
| 30 |
+
ctx.Reply(u, "❌ Search is currently unavailable. Database not connected.", nil)
|
| 31 |
+
return dispatcher.EndGroups
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
// Parse command arguments
|
| 35 |
+
args := strings.Fields(u.EffectiveMessage.GetMessage())
|
| 36 |
+
if len(args) < 2 {
|
| 37 |
+
helpText := `🔍 **Search Your Files**
|
| 38 |
+
|
| 39 |
+
Usage: ` + "`/search <filename>`" + `
|
| 40 |
+
|
| 41 |
+
Examples:
|
| 42 |
+
• ` + "`/search video`" + ` - Find files with "video" in name
|
| 43 |
+
• ` + "`/search .pdf`" + ` - Find all PDF files
|
| 44 |
+
• ` + "`/search photo 2024`" + ` - Find files with "photo" and "2024"
|
| 45 |
+
|
| 46 |
+
💡 Search is case-insensitive and matches partial names.`
|
| 47 |
+
|
| 48 |
+
styledMessage := []styling.StyledTextOption{
|
| 49 |
+
styling.Plain(helpText),
|
| 50 |
+
}
|
| 51 |
+
ctx.Reply(u, styledMessage, nil)
|
| 52 |
+
return dispatcher.EndGroups
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
// Join all arguments after /search as search query
|
| 56 |
+
query := strings.Join(args[1:], " ")
|
| 57 |
+
|
| 58 |
+
// Search user's files
|
| 59 |
+
results, err := db.UserMgr.SearchUserFiles(context.Background(), userID, query, 15)
|
| 60 |
+
if err != nil {
|
| 61 |
+
log.Error("Failed to search user files", zap.Error(err))
|
| 62 |
+
ctx.Reply(u, "❌ Search failed. Please try again later.", nil)
|
| 63 |
+
return dispatcher.EndGroups
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
if len(results) == 0 {
|
| 67 |
+
notFoundText := fmt.Sprintf("🔍 No files found matching: **%s**\n\n💡 Try different keywords or check your file history with "+"`/history`", query)
|
| 68 |
+
styledMessage := []styling.StyledTextOption{
|
| 69 |
+
styling.Plain(notFoundText),
|
| 70 |
+
}
|
| 71 |
+
ctx.Reply(u, styledMessage, nil)
|
| 72 |
+
return dispatcher.EndGroups
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
// Build search results message
|
| 76 |
+
searchText := fmt.Sprintf("🔍 **Search Results for:** %s\n\nFound %d file(s):\n\n", query, len(results))
|
| 77 |
+
|
| 78 |
+
for i, file := range results {
|
| 79 |
+
// Truncate filename if too long
|
| 80 |
+
displayName := file.FileName
|
| 81 |
+
if len(displayName) > 35 {
|
| 82 |
+
displayName = displayName[:32] + "..."
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
// Format file size
|
| 86 |
+
sizeFormatted := formatBytes(file.FileSize)
|
| 87 |
+
|
| 88 |
+
// Format time
|
| 89 |
+
timeAgo := formatTimeAgo(file.CreatedAt)
|
| 90 |
+
|
| 91 |
+
// Get file type emoji
|
| 92 |
+
emoji := getFileTypeEmoji(file.MimeType)
|
| 93 |
+
|
| 94 |
+
searchText += fmt.Sprintf("`%d.` %s **%s**\n", i+1, emoji, displayName)
|
| 95 |
+
searchText += fmt.Sprintf(" 💾 %s • 📥 %d downloads • %s\n", sizeFormatted, file.DownloadCount, timeAgo)
|
| 96 |
+
|
| 97 |
+
// Show hash for regenerating link if needed
|
| 98 |
+
searchText += fmt.Sprintf(" 🔗 Hash: `%s`\n\n", file.ShortHash)
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
if len(results) == 15 {
|
| 102 |
+
searchText += "⚠️ Showing first 15 results. Use more specific keywords for better results."
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
// Create styled message
|
| 106 |
+
styledMessage := []styling.StyledTextOption{
|
| 107 |
+
styling.Plain(searchText),
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
// Create inline keyboard
|
| 111 |
+
keyboard := &tg.ReplyInlineMarkup{
|
| 112 |
+
Rows: []tg.KeyboardButtonRow{
|
| 113 |
+
{
|
| 114 |
+
Buttons: []tg.KeyboardButtonClass{
|
| 115 |
+
&tg.KeyboardButtonCallback{Text: "🔄 Search Again", Data: []byte("search_again")},
|
| 116 |
+
&tg.KeyboardButtonCallback{Text: "📁 View History", Data: []byte("view_history")},
|
| 117 |
+
},
|
| 118 |
+
},
|
| 119 |
+
},
|
| 120 |
+
}
|
| 121 |
+
|
| 122 |
+
ctx.Reply(u, styledMessage, &ext.ReplyOpts{Markup: keyboard})
|
| 123 |
+
return dispatcher.EndGroups
|
| 124 |
+
}
|
internal/commands/settings.go
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package commands
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/db"
|
| 5 |
+
"TelegramCloud/tgf/internal/utils"
|
| 6 |
+
"context"
|
| 7 |
+
"fmt"
|
| 8 |
+
"strings"
|
| 9 |
+
|
| 10 |
+
"github.com/celestix/gotgproto/dispatcher"
|
| 11 |
+
"github.com/celestix/gotgproto/dispatcher/handlers"
|
| 12 |
+
"github.com/celestix/gotgproto/ext"
|
| 13 |
+
"github.com/gotd/td/telegram/message/styling"
|
| 14 |
+
"github.com/gotd/td/tg"
|
| 15 |
+
"go.uber.org/zap"
|
| 16 |
+
)
|
| 17 |
+
|
| 18 |
+
func (m *command) LoadSettings(dispatcher dispatcher.Dispatcher) {
|
| 19 |
+
log := m.log.Named("settings")
|
| 20 |
+
defer log.Sugar().Info("Loaded settings command")
|
| 21 |
+
dispatcher.AddHandler(handlers.NewCommand("settings", settingsHandler))
|
| 22 |
+
// Add callback handler for settings buttons
|
| 23 |
+
dispatcher.AddHandler(handlers.NewCallbackQuery(nil, settingsCallbackHandler))
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
func settingsHandler(ctx *ext.Context, u *ext.Update) error {
|
| 27 |
+
log := utils.Logger.Named("settingsHandler")
|
| 28 |
+
userID := u.EffectiveUser().ID
|
| 29 |
+
|
| 30 |
+
// Check if database is available
|
| 31 |
+
if db.UserMgr == nil {
|
| 32 |
+
ctx.Reply(u, "❌ Settings are currently unavailable. Database not connected.", nil)
|
| 33 |
+
return dispatcher.EndGroups
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
// Get user settings
|
| 37 |
+
settings, err := db.UserMgr.GetUserSettings(context.Background(), userID)
|
| 38 |
+
if err != nil {
|
| 39 |
+
log.Error("Failed to get user settings", zap.Error(err))
|
| 40 |
+
ctx.Reply(u, "❌ Failed to load settings. Please try again later.", nil)
|
| 41 |
+
return dispatcher.EndGroups
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
// Build settings message
|
| 45 |
+
settingsText := buildSettingsMessage(settings)
|
| 46 |
+
|
| 47 |
+
// Create styled message
|
| 48 |
+
styledMessage := []styling.StyledTextOption{
|
| 49 |
+
styling.Plain(settingsText),
|
| 50 |
+
}
|
| 51 |
+
|
| 52 |
+
// Create settings keyboard
|
| 53 |
+
keyboard := createSettingsKeyboard(settings)
|
| 54 |
+
|
| 55 |
+
ctx.Reply(u, styledMessage, &ext.ReplyOpts{Markup: keyboard})
|
| 56 |
+
return dispatcher.EndGroups
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
func settingsCallbackHandler(ctx *ext.Context, u *ext.Update) error {
|
| 60 |
+
log := utils.Logger.Named("settingsCallback")
|
| 61 |
+
userID := u.CallbackQuery.UserID
|
| 62 |
+
data := string(u.CallbackQuery.Data)
|
| 63 |
+
|
| 64 |
+
// Only handle settings-related callbacks
|
| 65 |
+
if !strings.HasPrefix(data, "settings_") {
|
| 66 |
+
return nil // Let other handlers process this
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Check if database is available
|
| 70 |
+
if db.UserMgr == nil {
|
| 71 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 72 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 73 |
+
Message: "Settings unavailable",
|
| 74 |
+
Alert: true,
|
| 75 |
+
})
|
| 76 |
+
return dispatcher.EndGroups
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
// Get current settings
|
| 80 |
+
settings, err := db.UserMgr.GetUserSettings(context.Background(), userID)
|
| 81 |
+
if err != nil {
|
| 82 |
+
log.Error("Failed to get user settings", zap.Error(err))
|
| 83 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 84 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 85 |
+
Message: "Failed to load settings",
|
| 86 |
+
Alert: true,
|
| 87 |
+
})
|
| 88 |
+
return dispatcher.EndGroups
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
// Process different settings actions
|
| 92 |
+
switch {
|
| 93 |
+
case data == "settings_expiration":
|
| 94 |
+
// Cycle through expiration options: 0 (never) → 24h → 7 days → 30 days → 0
|
| 95 |
+
switch settings.DefaultExpiration {
|
| 96 |
+
case 0:
|
| 97 |
+
settings.DefaultExpiration = 24 // 24 hours
|
| 98 |
+
case 24:
|
| 99 |
+
settings.DefaultExpiration = 168 // 7 days
|
| 100 |
+
case 168:
|
| 101 |
+
settings.DefaultExpiration = 720 // 30 days
|
| 102 |
+
default:
|
| 103 |
+
settings.DefaultExpiration = 0 // never
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
case data == "settings_notifications":
|
| 107 |
+
settings.EnableNotifications = !settings.EnableNotifications
|
| 108 |
+
|
| 109 |
+
case data == "settings_theme":
|
| 110 |
+
if settings.Theme == "light" {
|
| 111 |
+
settings.Theme = "dark"
|
| 112 |
+
} else {
|
| 113 |
+
settings.Theme = "light"
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
case data == "settings_autodelete":
|
| 117 |
+
settings.AutoDelete = !settings.AutoDelete
|
| 118 |
+
|
| 119 |
+
case data == "settings_language":
|
| 120 |
+
// For now, just toggle between en and other (placeholder)
|
| 121 |
+
if settings.Language == "en" {
|
| 122 |
+
settings.Language = "ru" // placeholder
|
| 123 |
+
} else {
|
| 124 |
+
settings.Language = "en"
|
| 125 |
+
}
|
| 126 |
+
|
| 127 |
+
case data == "settings_close":
|
| 128 |
+
// Delete the settings message
|
| 129 |
+
ctx.DeleteMessages(u.CallbackQuery.UserID, []int{u.CallbackQuery.MsgID})
|
| 130 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 131 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 132 |
+
Message: "Settings closed",
|
| 133 |
+
})
|
| 134 |
+
return dispatcher.EndGroups
|
| 135 |
+
|
| 136 |
+
default:
|
| 137 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 138 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 139 |
+
Message: "Unknown setting",
|
| 140 |
+
Alert: true,
|
| 141 |
+
})
|
| 142 |
+
return dispatcher.EndGroups
|
| 143 |
+
}
|
| 144 |
+
|
| 145 |
+
// Save updated settings
|
| 146 |
+
err = db.UserMgr.UpdateUserSettings(context.Background(), userID, settings)
|
| 147 |
+
if err != nil {
|
| 148 |
+
log.Error("Failed to update user settings", zap.Error(err))
|
| 149 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 150 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 151 |
+
Message: "Failed to save settings",
|
| 152 |
+
Alert: true,
|
| 153 |
+
})
|
| 154 |
+
return dispatcher.EndGroups
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
// Update the message with new settings
|
| 158 |
+
settingsText := buildSettingsMessage(settings)
|
| 159 |
+
keyboard := createSettingsKeyboard(settings)
|
| 160 |
+
|
| 161 |
+
// Edit the message
|
| 162 |
+
peer := ctx.PeerStorage.GetInputPeerById(userID)
|
| 163 |
+
_, err = ctx.Raw.MessagesEditMessage(ctx, &tg.MessagesEditMessageRequest{
|
| 164 |
+
Peer: peer,
|
| 165 |
+
ID: u.CallbackQuery.MsgID,
|
| 166 |
+
Message: settingsText,
|
| 167 |
+
ReplyMarkup: keyboard,
|
| 168 |
+
})
|
| 169 |
+
|
| 170 |
+
if err != nil {
|
| 171 |
+
log.Error("Failed to edit settings message", zap.Error(err))
|
| 172 |
+
}
|
| 173 |
+
|
| 174 |
+
// Answer callback query
|
| 175 |
+
ctx.Raw.MessagesSetBotCallbackAnswer(ctx, &tg.MessagesSetBotCallbackAnswerRequest{
|
| 176 |
+
QueryID: u.CallbackQuery.QueryID,
|
| 177 |
+
Message: "Setting updated",
|
| 178 |
+
})
|
| 179 |
+
|
| 180 |
+
return dispatcher.EndGroups
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
func buildSettingsMessage(settings *db.UserSettings) string {
|
| 184 |
+
expirationText := "Never"
|
| 185 |
+
switch settings.DefaultExpiration {
|
| 186 |
+
case 24:
|
| 187 |
+
expirationText = "24 hours"
|
| 188 |
+
case 168:
|
| 189 |
+
expirationText = "7 days"
|
| 190 |
+
case 720:
|
| 191 |
+
expirationText = "30 days"
|
| 192 |
+
}
|
| 193 |
+
|
| 194 |
+
notificationsText := "✅ Enabled"
|
| 195 |
+
if !settings.EnableNotifications {
|
| 196 |
+
notificationsText = "❌ Disabled"
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
autoDeleteText := "✅ Enabled"
|
| 200 |
+
if !settings.AutoDelete {
|
| 201 |
+
autoDeleteText = "❌ Disabled"
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
themeEmoji := "☀️"
|
| 205 |
+
if settings.Theme == "dark" {
|
| 206 |
+
themeEmoji = "🌙"
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
return fmt.Sprintf(`⚙️ **Your Settings**
|
| 210 |
+
|
| 211 |
+
🕒 **Default Expiration:** %s
|
| 212 |
+
📬 **Notifications:** %s
|
| 213 |
+
%s **Theme:** %s
|
| 214 |
+
🗑️ **Auto Delete:** %s
|
| 215 |
+
🌍 **Language:** %s
|
| 216 |
+
|
| 217 |
+
💡 Click buttons below to change settings
|
| 218 |
+
💡 Changes are saved automatically`,
|
| 219 |
+
expirationText,
|
| 220 |
+
notificationsText,
|
| 221 |
+
themeEmoji, strings.Title(settings.Theme),
|
| 222 |
+
autoDeleteText,
|
| 223 |
+
strings.ToUpper(settings.Language),
|
| 224 |
+
)
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
func createSettingsKeyboard(settings *db.UserSettings) *tg.ReplyInlineMarkup {
|
| 228 |
+
return &tg.ReplyInlineMarkup{
|
| 229 |
+
Rows: []tg.KeyboardButtonRow{
|
| 230 |
+
{
|
| 231 |
+
Buttons: []tg.KeyboardButtonClass{
|
| 232 |
+
&tg.KeyboardButtonCallback{Text: "🕒 Expiration", Data: []byte("settings_expiration")},
|
| 233 |
+
&tg.KeyboardButtonCallback{Text: "📬 Notifications", Data: []byte("settings_notifications")},
|
| 234 |
+
},
|
| 235 |
+
},
|
| 236 |
+
{
|
| 237 |
+
Buttons: []tg.KeyboardButtonClass{
|
| 238 |
+
&tg.KeyboardButtonCallback{Text: "🎨 Theme", Data: []byte("settings_theme")},
|
| 239 |
+
&tg.KeyboardButtonCallback{Text: "🗑️ Auto Delete", Data: []byte("settings_autodelete")},
|
| 240 |
+
},
|
| 241 |
+
},
|
| 242 |
+
{
|
| 243 |
+
Buttons: []tg.KeyboardButtonClass{
|
| 244 |
+
&tg.KeyboardButtonCallback{Text: "🌍 Language", Data: []byte("settings_language")},
|
| 245 |
+
&tg.KeyboardButtonCallback{Text: "❌ Close", Data: []byte("settings_close")},
|
| 246 |
+
},
|
| 247 |
+
},
|
| 248 |
+
},
|
| 249 |
+
}
|
| 250 |
+
}
|
internal/commands/start.go
CHANGED
|
@@ -3,8 +3,8 @@
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
"fmt" // Import fmt
|
| 9 |
"math/rand"
|
| 10 |
"strings" // Import strings
|
|
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
+
"TelegramCloud/tgf/config"
|
| 7 |
+
"TelegramCloud/tgf/internal/utils"
|
| 8 |
"fmt" // Import fmt
|
| 9 |
"math/rand"
|
| 10 |
"strings" // Import strings
|
internal/commands/stats.go
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package commands
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/db"
|
| 5 |
+
"TelegramCloud/tgf/internal/utils"
|
| 6 |
+
"context"
|
| 7 |
+
"fmt"
|
| 8 |
+
"time"
|
| 9 |
+
|
| 10 |
+
"github.com/celestix/gotgproto/dispatcher"
|
| 11 |
+
"github.com/celestix/gotgproto/dispatcher/handlers"
|
| 12 |
+
"github.com/celestix/gotgproto/ext"
|
| 13 |
+
"github.com/gotd/td/telegram/message/styling"
|
| 14 |
+
"go.uber.org/zap"
|
| 15 |
+
)
|
| 16 |
+
|
| 17 |
+
func (m *command) LoadStats(dispatcher dispatcher.Dispatcher) {
|
| 18 |
+
log := m.log.Named("stats")
|
| 19 |
+
defer log.Sugar().Info("Loaded stats command")
|
| 20 |
+
dispatcher.AddHandler(handlers.NewCommand("stats", statsHandler))
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
func statsHandler(ctx *ext.Context, u *ext.Update) error {
|
| 24 |
+
log := utils.Logger.Named("statsHandler")
|
| 25 |
+
userID := u.EffectiveUser().ID
|
| 26 |
+
|
| 27 |
+
// Check if database is available
|
| 28 |
+
if db.UserMgr == nil {
|
| 29 |
+
ctx.Reply(u, "❌ Statistics are currently unavailable. Database not connected.", nil)
|
| 30 |
+
return dispatcher.EndGroups
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
// Get user stats
|
| 34 |
+
stats, err := db.UserMgr.GetOrCreateUserStats(context.Background(), userID)
|
| 35 |
+
if err != nil {
|
| 36 |
+
log.Error("Failed to get user stats", zap.Error(err))
|
| 37 |
+
ctx.Reply(u, "❌ Failed to retrieve your statistics. Please try again later.", nil)
|
| 38 |
+
return dispatcher.EndGroups
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
// Get today's usage
|
| 42 |
+
today := time.Now().Format("2006-01-02")
|
| 43 |
+
dailyUsage, err := db.UserMgr.GetDailyUsage(context.Background(), userID, today)
|
| 44 |
+
if err != nil {
|
| 45 |
+
log.Error("Failed to get daily usage", zap.Error(err))
|
| 46 |
+
dailyUsage = &db.DailyUsage{FilesCount: 0, BandwidthUsed: 0}
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
// Format file sizes
|
| 50 |
+
totalSizeFormatted := formatBytes(stats.TotalSize)
|
| 51 |
+
bandwidthFormatted := formatBytes(stats.BandwidthUsed)
|
| 52 |
+
dailyBandwidthFormatted := formatBytes(dailyUsage.BandwidthUsed)
|
| 53 |
+
|
| 54 |
+
// Format role
|
| 55 |
+
roleEmoji := "👤"
|
| 56 |
+
switch stats.Role {
|
| 57 |
+
case db.RolePremium:
|
| 58 |
+
roleEmoji = "⭐"
|
| 59 |
+
case db.RoleAdmin:
|
| 60 |
+
roleEmoji = "👑"
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
// Build stats message
|
| 64 |
+
statsText := fmt.Sprintf(`📊 **Your Statistics**
|
| 65 |
+
|
| 66 |
+
%s **Role:** %s
|
| 67 |
+
📅 **Member since:** %s
|
| 68 |
+
|
| 69 |
+
📁 **Files uploaded:** %d
|
| 70 |
+
💾 **Total storage used:** %s
|
| 71 |
+
📥 **Total downloads:** %d
|
| 72 |
+
🌐 **Bandwidth used:** %s
|
| 73 |
+
|
| 74 |
+
**Today's Usage:**
|
| 75 |
+
📁 Files: %d
|
| 76 |
+
🌐 Bandwidth: %s
|
| 77 |
+
|
| 78 |
+
**Limits:**`,
|
| 79 |
+
roleEmoji, stats.Role,
|
| 80 |
+
stats.CreatedAt.Format("Jan 2, 2006"),
|
| 81 |
+
stats.FilesUploaded,
|
| 82 |
+
totalSizeFormatted,
|
| 83 |
+
stats.TotalDownloads,
|
| 84 |
+
bandwidthFormatted,
|
| 85 |
+
dailyUsage.FilesCount,
|
| 86 |
+
dailyBandwidthFormatted,
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
// Add limits info
|
| 90 |
+
if stats.Role == db.RoleAdmin {
|
| 91 |
+
statsText += "\n🚀 **Unlimited access**"
|
| 92 |
+
} else {
|
| 93 |
+
dailyRemaining := stats.DailyLimit - int64(dailyUsage.FilesCount)
|
| 94 |
+
if dailyRemaining < 0 {
|
| 95 |
+
dailyRemaining = 0
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
statsText += fmt.Sprintf(`
|
| 99 |
+
📅 Daily: %d/%d (remaining: %d)
|
| 100 |
+
📆 Monthly: %d files`,
|
| 101 |
+
dailyUsage.FilesCount, stats.DailyLimit, dailyRemaining,
|
| 102 |
+
stats.MonthlyLimit,
|
| 103 |
+
)
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
statsText += fmt.Sprintf(`
|
| 107 |
+
|
| 108 |
+
🕒 **Last active:** %s`, stats.LastActive.Format("Jan 2, 15:04"))
|
| 109 |
+
|
| 110 |
+
// Create styled message
|
| 111 |
+
styledMessage := []styling.StyledTextOption{
|
| 112 |
+
styling.Plain(statsText),
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
ctx.Reply(u, styledMessage, nil)
|
| 116 |
+
return dispatcher.EndGroups
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// formatBytes converts bytes to human readable format
|
| 120 |
+
func formatBytes(bytes int64) string {
|
| 121 |
+
const unit = 1024
|
| 122 |
+
if bytes < unit {
|
| 123 |
+
return fmt.Sprintf("%d B", bytes)
|
| 124 |
+
}
|
| 125 |
+
div, exp := int64(unit), 0
|
| 126 |
+
for n := bytes / unit; n >= unit; n /= unit {
|
| 127 |
+
div *= unit
|
| 128 |
+
exp++
|
| 129 |
+
}
|
| 130 |
+
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
| 131 |
+
}
|
internal/commands/stream.go
CHANGED
|
@@ -3,10 +3,13 @@
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
-
"
|
| 9 |
-
"
|
|
|
|
|
|
|
|
|
|
| 10 |
|
| 11 |
"github.com/celestix/gotgproto/dispatcher"
|
| 12 |
"github.com/celestix/gotgproto/dispatcher/handlers"
|
|
@@ -52,6 +55,19 @@ func sendLink(ctx *ext.Context, u *ext.Update) error {
|
|
| 52 |
userID := u.EffectiveUser().ID
|
| 53 |
chatId := u.EffectiveChat().GetID()
|
| 54 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
// --- THIS IS THE NEW STATEFUL LOGIC ---
|
| 56 |
if state.GlobalStateManager.IsUserInBatch(userID) {
|
| 57 |
// User is in batch mode, so we just add the file and confirm.
|
|
|
|
| 3 |
package commands
|
| 4 |
|
| 5 |
import (
|
| 6 |
+
"TelegramCloud/tgf/config"
|
| 7 |
+
"TelegramCloud/tgf/internal/bot"
|
| 8 |
+
"TelegramCloud/tgf/internal/db"
|
| 9 |
+
"TelegramCloud/tgf/internal/state" // <--- ADD THIS IMPORT
|
| 10 |
+
"TelegramCloud/tgf/internal/utils"
|
| 11 |
+
"context"
|
| 12 |
+
"fmt"
|
| 13 |
|
| 14 |
"github.com/celestix/gotgproto/dispatcher"
|
| 15 |
"github.com/celestix/gotgproto/dispatcher/handlers"
|
|
|
|
| 55 |
userID := u.EffectiveUser().ID
|
| 56 |
chatId := u.EffectiveChat().GetID()
|
| 57 |
|
| 58 |
+
// --- USER LIMITS CHECK ---
|
| 59 |
+
if db.UserMgr != nil {
|
| 60 |
+
canUpload, limitMsg, err := db.UserMgr.CheckUserLimits(context.Background(), userID)
|
| 61 |
+
if err != nil {
|
| 62 |
+
utils.Logger.Error("Failed to check user limits", zap.Error(err))
|
| 63 |
+
// Continue processing even if limit check fails
|
| 64 |
+
} else if !canUpload {
|
| 65 |
+
ctx.Reply(u, fmt.Sprintf("❌ %s\n\nUse `/stats` to see your usage or contact admin for upgrade.", limitMsg), nil)
|
| 66 |
+
return dispatcher.EndGroups
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
// --- END USER LIMITS CHECK ---
|
| 70 |
+
|
| 71 |
// --- THIS IS THE NEW STATEFUL LOGIC ---
|
| 72 |
if state.GlobalStateManager.IsUserInBatch(userID) {
|
| 73 |
// User is in batch mode, so we just add the file and confirm.
|
internal/db/database.go
CHANGED
|
@@ -2,7 +2,7 @@
|
|
| 2 |
package db
|
| 3 |
|
| 4 |
import (
|
| 5 |
-
"
|
| 6 |
"context"
|
| 7 |
"time"
|
| 8 |
|
|
@@ -17,6 +17,8 @@ var (
|
|
| 17 |
Users *mongo.Collection
|
| 18 |
OTPs *mongo.Collection
|
| 19 |
Files *mongo.Collection
|
|
|
|
|
|
|
| 20 |
)
|
| 21 |
|
| 22 |
// InitDatabase initializes the MongoDB connection.
|
|
@@ -49,6 +51,8 @@ func InitDatabase(log *zap.Logger) error {
|
|
| 49 |
OTPs = database.Collection("otps")
|
| 50 |
Files = database.Collection("files")
|
| 51 |
|
|
|
|
|
|
|
| 52 |
|
| 53 |
log.Info("MongoDB connection established successfully.")
|
| 54 |
return nil
|
|
|
|
| 2 |
package db
|
| 3 |
|
| 4 |
import (
|
| 5 |
+
"TelegramCloud/tgf/config"
|
| 6 |
"context"
|
| 7 |
"time"
|
| 8 |
|
|
|
|
| 17 |
Users *mongo.Collection
|
| 18 |
OTPs *mongo.Collection
|
| 19 |
Files *mongo.Collection
|
| 20 |
+
// User management instance
|
| 21 |
+
UserMgr *UserManager
|
| 22 |
)
|
| 23 |
|
| 24 |
// InitDatabase initializes the MongoDB connection.
|
|
|
|
| 51 |
OTPs = database.Collection("otps")
|
| 52 |
Files = database.Collection("files")
|
| 53 |
|
| 54 |
+
// Initialize UserManager
|
| 55 |
+
UserMgr = NewUserManager(database, log)
|
| 56 |
|
| 57 |
log.Info("MongoDB connection established successfully.")
|
| 58 |
return nil
|
internal/db/models.go
CHANGED
|
@@ -56,4 +56,77 @@ type FileRecord struct {
|
|
| 56 |
FullHash string `bson:"fullHash"`
|
| 57 |
CreatedAt time.Time `bson:"createdAt"`
|
| 58 |
UpdatedAt time.Time `bson:"updatedAt"`
|
| 59 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
FullHash string `bson:"fullHash"`
|
| 57 |
CreatedAt time.Time `bson:"createdAt"`
|
| 58 |
UpdatedAt time.Time `bson:"updatedAt"`
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
// UserStats represents user usage statistics
|
| 62 |
+
type UserStats struct {
|
| 63 |
+
ID primitive.ObjectID `bson:"_id,omitempty"`
|
| 64 |
+
UserID int64 `bson:"user_id,unique"`
|
| 65 |
+
FilesUploaded int64 `bson:"files_uploaded"`
|
| 66 |
+
TotalSize int64 `bson:"total_size"` // in bytes
|
| 67 |
+
TotalDownloads int64 `bson:"total_downloads"`
|
| 68 |
+
BandwidthUsed int64 `bson:"bandwidth_used"` // in bytes
|
| 69 |
+
LastActive time.Time `bson:"last_active"`
|
| 70 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 71 |
+
UpdatedAt time.Time `bson:"updated_at"`
|
| 72 |
+
// Usage limits
|
| 73 |
+
DailyLimit int64 `bson:"daily_limit"` // files per day
|
| 74 |
+
MonthlyLimit int64 `bson:"monthly_limit"` // files per month
|
| 75 |
+
Role string `bson:"role"` // "basic", "premium", "admin"
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
// DailyUsage tracks daily usage per user
|
| 79 |
+
type DailyUsage struct {
|
| 80 |
+
ID primitive.ObjectID `bson:"_id,omitempty"`
|
| 81 |
+
UserID int64 `bson:"user_id"`
|
| 82 |
+
Date string `bson:"date"` // YYYY-MM-DD format
|
| 83 |
+
FilesCount int `bson:"files_count"`
|
| 84 |
+
BandwidthUsed int64 `bson:"bandwidth_used"`
|
| 85 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
// UserSettings represents user preferences
|
| 89 |
+
type UserSettings struct {
|
| 90 |
+
ID primitive.ObjectID `bson:"_id,omitempty"`
|
| 91 |
+
UserID int64 `bson:"user_id,unique"`
|
| 92 |
+
DefaultExpiration int `bson:"default_expiration"` // hours, 0 = never
|
| 93 |
+
EnableNotifications bool `bson:"enable_notifications"`
|
| 94 |
+
Theme string `bson:"theme"` // "light", "dark"
|
| 95 |
+
Language string `bson:"language"`
|
| 96 |
+
AutoDelete bool `bson:"auto_delete"` // auto delete after expiration
|
| 97 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 98 |
+
UpdatedAt time.Time `bson:"updated_at"`
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
// FileHistory tracks user's file operations for history/search
|
| 102 |
+
type FileHistory struct {
|
| 103 |
+
ID primitive.ObjectID `bson:"_id,omitempty"`
|
| 104 |
+
UserID int64 `bson:"user_id"`
|
| 105 |
+
MessageID int `bson:"message_id"`
|
| 106 |
+
FileName string `bson:"file_name"`
|
| 107 |
+
FileSize int64 `bson:"file_size"`
|
| 108 |
+
MimeType string `bson:"mime_type"`
|
| 109 |
+
ShortHash string `bson:"short_hash"`
|
| 110 |
+
FullHash string `bson:"full_hash"`
|
| 111 |
+
DownloadCount int `bson:"download_count"`
|
| 112 |
+
LastAccessed time.Time `bson:"last_accessed"`
|
| 113 |
+
CreatedAt time.Time `bson:"created_at"`
|
| 114 |
+
ExpiresAt *time.Time `bson:"expires_at,omitempty"` // optional expiration
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
// UserRole constants
|
| 118 |
+
const (
|
| 119 |
+
RoleBasic = "basic"
|
| 120 |
+
RolePremium = "premium"
|
| 121 |
+
RoleAdmin = "admin"
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
// Default limits per role
|
| 125 |
+
const (
|
| 126 |
+
BasicDailyLimit = 50
|
| 127 |
+
BasicMonthlyLimit = 1000
|
| 128 |
+
PremiumDailyLimit = 200
|
| 129 |
+
PremiumMonthlyLimit = 5000
|
| 130 |
+
AdminDailyLimit = -1 // unlimited
|
| 131 |
+
AdminMonthlyLimit = -1 // unlimited
|
| 132 |
+
)
|
internal/db/user_manager.go
ADDED
|
@@ -0,0 +1,417 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
package db
|
| 2 |
+
|
| 3 |
+
import (
|
| 4 |
+
"context"
|
| 5 |
+
"fmt"
|
| 6 |
+
"time"
|
| 7 |
+
|
| 8 |
+
"go.mongodb.org/mongo-driver/bson"
|
| 9 |
+
"go.mongodb.org/mongo-driver/mongo"
|
| 10 |
+
"go.mongodb.org/mongo-driver/mongo/options"
|
| 11 |
+
"go.uber.org/zap"
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
// UserManager handles user-related database operations
|
| 15 |
+
type UserManager struct {
|
| 16 |
+
statsCollection *mongo.Collection
|
| 17 |
+
usageCollection *mongo.Collection
|
| 18 |
+
settingsCollection *mongo.Collection
|
| 19 |
+
historyCollection *mongo.Collection
|
| 20 |
+
log *zap.Logger
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
// NewUserManager creates a new UserManager instance
|
| 24 |
+
func NewUserManager(database *mongo.Database, log *zap.Logger) *UserManager {
|
| 25 |
+
return &UserManager{
|
| 26 |
+
statsCollection: database.Collection("user_stats"),
|
| 27 |
+
usageCollection: database.Collection("daily_usage"),
|
| 28 |
+
settingsCollection: database.Collection("user_settings"),
|
| 29 |
+
historyCollection: database.Collection("file_history"),
|
| 30 |
+
log: log.Named("UserManager"),
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
// GetOrCreateUserStats gets user stats or creates default ones
|
| 35 |
+
func (um *UserManager) GetOrCreateUserStats(ctx context.Context, userID int64) (*UserStats, error) {
|
| 36 |
+
var stats UserStats
|
| 37 |
+
|
| 38 |
+
err := um.statsCollection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&stats)
|
| 39 |
+
if err != nil {
|
| 40 |
+
if err == mongo.ErrNoDocuments {
|
| 41 |
+
// Create default stats for new user
|
| 42 |
+
stats = UserStats{
|
| 43 |
+
UserID: userID,
|
| 44 |
+
FilesUploaded: 0,
|
| 45 |
+
TotalSize: 0,
|
| 46 |
+
TotalDownloads: 0,
|
| 47 |
+
BandwidthUsed: 0,
|
| 48 |
+
LastActive: time.Now(),
|
| 49 |
+
CreatedAt: time.Now(),
|
| 50 |
+
UpdatedAt: time.Now(),
|
| 51 |
+
DailyLimit: BasicDailyLimit,
|
| 52 |
+
MonthlyLimit: BasicMonthlyLimit,
|
| 53 |
+
Role: RoleBasic,
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
_, err = um.statsCollection.InsertOne(ctx, stats)
|
| 57 |
+
if err != nil {
|
| 58 |
+
return nil, fmt.Errorf("failed to create user stats: %w", err)
|
| 59 |
+
}
|
| 60 |
+
um.log.Info("Created new user stats", zap.Int64("userID", userID))
|
| 61 |
+
} else {
|
| 62 |
+
return nil, fmt.Errorf("failed to get user stats: %w", err)
|
| 63 |
+
}
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
return &stats, nil
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// UpdateUserStats updates user statistics
|
| 70 |
+
func (um *UserManager) UpdateUserStats(ctx context.Context, userID int64, fileSize int64) error {
|
| 71 |
+
update := bson.M{
|
| 72 |
+
"$inc": bson.M{
|
| 73 |
+
"files_uploaded": 1,
|
| 74 |
+
"total_size": fileSize,
|
| 75 |
+
},
|
| 76 |
+
"$set": bson.M{
|
| 77 |
+
"last_active": time.Now(),
|
| 78 |
+
"updated_at": time.Now(),
|
| 79 |
+
},
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
_, err := um.statsCollection.UpdateOne(
|
| 83 |
+
ctx,
|
| 84 |
+
bson.M{"user_id": userID},
|
| 85 |
+
update,
|
| 86 |
+
)
|
| 87 |
+
if err != nil {
|
| 88 |
+
return fmt.Errorf("failed to update user stats: %w", err)
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
return nil
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// UpdateDownloadStats updates download statistics
|
| 95 |
+
func (um *UserManager) UpdateDownloadStats(ctx context.Context, userID int64, bandwidth int64) error {
|
| 96 |
+
update := bson.M{
|
| 97 |
+
"$inc": bson.M{
|
| 98 |
+
"total_downloads": 1,
|
| 99 |
+
"bandwidth_used": bandwidth,
|
| 100 |
+
},
|
| 101 |
+
"$set": bson.M{
|
| 102 |
+
"last_active": time.Now(),
|
| 103 |
+
"updated_at": time.Now(),
|
| 104 |
+
},
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
_, err := um.statsCollection.UpdateOne(
|
| 108 |
+
ctx,
|
| 109 |
+
bson.M{"user_id": userID},
|
| 110 |
+
update,
|
| 111 |
+
)
|
| 112 |
+
if err != nil {
|
| 113 |
+
return fmt.Errorf("failed to update download stats: %w", err)
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
return nil
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
// GetDailyUsage gets user's daily usage
|
| 120 |
+
func (um *UserManager) GetDailyUsage(ctx context.Context, userID int64, date string) (*DailyUsage, error) {
|
| 121 |
+
var usage DailyUsage
|
| 122 |
+
|
| 123 |
+
err := um.usageCollection.FindOne(ctx, bson.M{
|
| 124 |
+
"user_id": userID,
|
| 125 |
+
"date": date,
|
| 126 |
+
}).Decode(&usage)
|
| 127 |
+
|
| 128 |
+
if err != nil {
|
| 129 |
+
if err == mongo.ErrNoDocuments {
|
| 130 |
+
// Create new daily usage record
|
| 131 |
+
usage = DailyUsage{
|
| 132 |
+
UserID: userID,
|
| 133 |
+
Date: date,
|
| 134 |
+
FilesCount: 0,
|
| 135 |
+
BandwidthUsed: 0,
|
| 136 |
+
CreatedAt: time.Now(),
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
_, err = um.usageCollection.InsertOne(ctx, usage)
|
| 140 |
+
if err != nil {
|
| 141 |
+
return nil, fmt.Errorf("failed to create daily usage: %w", err)
|
| 142 |
+
}
|
| 143 |
+
} else {
|
| 144 |
+
return nil, fmt.Errorf("failed to get daily usage: %w", err)
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
return &usage, nil
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
// UpdateDailyUsage updates daily usage statistics
|
| 152 |
+
func (um *UserManager) UpdateDailyUsage(ctx context.Context, userID int64, bandwidth int64) error {
|
| 153 |
+
today := time.Now().Format("2006-01-02")
|
| 154 |
+
|
| 155 |
+
update := bson.M{
|
| 156 |
+
"$inc": bson.M{
|
| 157 |
+
"files_count": 1,
|
| 158 |
+
"bandwidth_used": bandwidth,
|
| 159 |
+
},
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
_, err := um.usageCollection.UpdateOne(
|
| 163 |
+
ctx,
|
| 164 |
+
bson.M{"user_id": userID, "date": today},
|
| 165 |
+
update,
|
| 166 |
+
options.Update().SetUpsert(true),
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
if err != nil {
|
| 170 |
+
return fmt.Errorf("failed to update daily usage: %w", err)
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
return nil
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
// CheckUserLimits checks if user has exceeded daily/monthly limits
|
| 177 |
+
func (um *UserManager) CheckUserLimits(ctx context.Context, userID int64) (bool, string, error) {
|
| 178 |
+
stats, err := um.GetOrCreateUserStats(ctx, userID)
|
| 179 |
+
if err != nil {
|
| 180 |
+
return false, "", err
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
// Admin users have unlimited access
|
| 184 |
+
if stats.Role == RoleAdmin {
|
| 185 |
+
return true, "", nil
|
| 186 |
+
}
|
| 187 |
+
|
| 188 |
+
// Check daily limit
|
| 189 |
+
today := time.Now().Format("2006-01-02")
|
| 190 |
+
dailyUsage, err := um.GetDailyUsage(ctx, userID, today)
|
| 191 |
+
if err != nil {
|
| 192 |
+
return false, "", err
|
| 193 |
+
}
|
| 194 |
+
|
| 195 |
+
if stats.DailyLimit > 0 && dailyUsage.FilesCount >= int(stats.DailyLimit) {
|
| 196 |
+
return false, fmt.Sprintf("Daily limit exceeded (%d files). Limit resets at midnight.", stats.DailyLimit), nil
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
// Check monthly limit (simplified - checking last 30 days)
|
| 200 |
+
thirtyDaysAgo := time.Now().AddDate(0, 0, -30).Format("2006-01-02")
|
| 201 |
+
pipeline := []bson.M{
|
| 202 |
+
{"$match": bson.M{
|
| 203 |
+
"user_id": userID,
|
| 204 |
+
"date": bson.M{"$gte": thirtyDaysAgo},
|
| 205 |
+
}},
|
| 206 |
+
{"$group": bson.M{
|
| 207 |
+
"_id": nil,
|
| 208 |
+
"total_files": bson.M{"$sum": "$files_count"},
|
| 209 |
+
}},
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
cursor, err := um.usageCollection.Aggregate(ctx, pipeline)
|
| 213 |
+
if err != nil {
|
| 214 |
+
return false, "", err
|
| 215 |
+
}
|
| 216 |
+
defer cursor.Close(ctx)
|
| 217 |
+
|
| 218 |
+
var result struct {
|
| 219 |
+
TotalFiles int `bson:"total_files"`
|
| 220 |
+
}
|
| 221 |
+
|
| 222 |
+
if cursor.Next(ctx) {
|
| 223 |
+
if err := cursor.Decode(&result); err != nil {
|
| 224 |
+
return false, "", err
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
if stats.MonthlyLimit > 0 && result.TotalFiles >= int(stats.MonthlyLimit) {
|
| 228 |
+
return false, fmt.Sprintf("Monthly limit exceeded (%d files). Upgrade to premium for higher limits.", stats.MonthlyLimit), nil
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
return true, "", nil
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
// GetUserSettings gets user settings or creates default ones
|
| 236 |
+
func (um *UserManager) GetUserSettings(ctx context.Context, userID int64) (*UserSettings, error) {
|
| 237 |
+
var settings UserSettings
|
| 238 |
+
|
| 239 |
+
err := um.settingsCollection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&settings)
|
| 240 |
+
if err != nil {
|
| 241 |
+
if err == mongo.ErrNoDocuments {
|
| 242 |
+
// Create default settings
|
| 243 |
+
settings = UserSettings{
|
| 244 |
+
UserID: userID,
|
| 245 |
+
DefaultExpiration: 0, // never expire
|
| 246 |
+
EnableNotifications: true,
|
| 247 |
+
Theme: "light",
|
| 248 |
+
Language: "en",
|
| 249 |
+
AutoDelete: false,
|
| 250 |
+
CreatedAt: time.Now(),
|
| 251 |
+
UpdatedAt: time.Now(),
|
| 252 |
+
}
|
| 253 |
+
|
| 254 |
+
_, err = um.settingsCollection.InsertOne(ctx, settings)
|
| 255 |
+
if err != nil {
|
| 256 |
+
return nil, fmt.Errorf("failed to create user settings: %w", err)
|
| 257 |
+
}
|
| 258 |
+
} else {
|
| 259 |
+
return nil, fmt.Errorf("failed to get user settings: %w", err)
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
|
| 263 |
+
return &settings, nil
|
| 264 |
+
}
|
| 265 |
+
|
| 266 |
+
// UpdateUserSettings updates user settings
|
| 267 |
+
func (um *UserManager) UpdateUserSettings(ctx context.Context, userID int64, settings *UserSettings) error {
|
| 268 |
+
settings.UpdatedAt = time.Now()
|
| 269 |
+
|
| 270 |
+
_, err := um.settingsCollection.UpdateOne(
|
| 271 |
+
ctx,
|
| 272 |
+
bson.M{"user_id": userID},
|
| 273 |
+
bson.M{"$set": settings},
|
| 274 |
+
options.Update().SetUpsert(true),
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
if err != nil {
|
| 278 |
+
return fmt.Errorf("failed to update user settings: %w", err)
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
return nil
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
// AddFileToHistory adds a file to user's history
|
| 285 |
+
func (um *UserManager) AddFileToHistory(ctx context.Context, userID int64, messageID int, fileName string, fileSize int64, mimeType, shortHash, fullHash string) error {
|
| 286 |
+
history := FileHistory{
|
| 287 |
+
UserID: userID,
|
| 288 |
+
MessageID: messageID,
|
| 289 |
+
FileName: fileName,
|
| 290 |
+
FileSize: fileSize,
|
| 291 |
+
MimeType: mimeType,
|
| 292 |
+
ShortHash: shortHash,
|
| 293 |
+
FullHash: fullHash,
|
| 294 |
+
DownloadCount: 0,
|
| 295 |
+
LastAccessed: time.Now(),
|
| 296 |
+
CreatedAt: time.Now(),
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
// Set expiration if user has default expiration set
|
| 300 |
+
settings, err := um.GetUserSettings(ctx, userID)
|
| 301 |
+
if err == nil && settings.DefaultExpiration > 0 {
|
| 302 |
+
expiresAt := time.Now().Add(time.Duration(settings.DefaultExpiration) * time.Hour)
|
| 303 |
+
history.ExpiresAt = &expiresAt
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
_, err = um.historyCollection.InsertOne(ctx, history)
|
| 307 |
+
if err != nil {
|
| 308 |
+
return fmt.Errorf("failed to add file to history: %w", err)
|
| 309 |
+
}
|
| 310 |
+
|
| 311 |
+
return nil
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
// GetUserHistory gets user's file history with pagination
|
| 315 |
+
func (um *UserManager) GetUserHistory(ctx context.Context, userID int64, limit int, offset int) ([]FileHistory, error) {
|
| 316 |
+
opts := options.Find().
|
| 317 |
+
SetSort(bson.D{{"created_at", -1}}).
|
| 318 |
+
SetLimit(int64(limit)).
|
| 319 |
+
SetSkip(int64(offset))
|
| 320 |
+
|
| 321 |
+
cursor, err := um.historyCollection.Find(ctx, bson.M{"user_id": userID}, opts)
|
| 322 |
+
if err != nil {
|
| 323 |
+
return nil, fmt.Errorf("failed to get user history: %w", err)
|
| 324 |
+
}
|
| 325 |
+
defer cursor.Close(ctx)
|
| 326 |
+
|
| 327 |
+
var history []FileHistory
|
| 328 |
+
if err := cursor.All(ctx, &history); err != nil {
|
| 329 |
+
return nil, fmt.Errorf("failed to decode user history: %w", err)
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
return history, nil
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
// SearchUserFiles searches user's files by name
|
| 336 |
+
func (um *UserManager) SearchUserFiles(ctx context.Context, userID int64, query string, limit int) ([]FileHistory, error) {
|
| 337 |
+
filter := bson.M{
|
| 338 |
+
"user_id": userID,
|
| 339 |
+
"file_name": bson.M{"$regex": query, "$options": "i"},
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
opts := options.Find().
|
| 343 |
+
SetSort(bson.D{{"created_at", -1}}).
|
| 344 |
+
SetLimit(int64(limit))
|
| 345 |
+
|
| 346 |
+
cursor, err := um.historyCollection.Find(ctx, filter, opts)
|
| 347 |
+
if err != nil {
|
| 348 |
+
return nil, fmt.Errorf("failed to search user files: %w", err)
|
| 349 |
+
}
|
| 350 |
+
defer cursor.Close(ctx)
|
| 351 |
+
|
| 352 |
+
var files []FileHistory
|
| 353 |
+
if err := cursor.All(ctx, &files); err != nil {
|
| 354 |
+
return nil, fmt.Errorf("failed to decode search results: %w", err)
|
| 355 |
+
}
|
| 356 |
+
|
| 357 |
+
return files, nil
|
| 358 |
+
}
|
| 359 |
+
|
| 360 |
+
// UpdateFileDownloadCount increments download count for a file
|
| 361 |
+
func (um *UserManager) UpdateFileDownloadCount(ctx context.Context, userID int64, shortHash string) error {
|
| 362 |
+
update := bson.M{
|
| 363 |
+
"$inc": bson.M{"download_count": 1},
|
| 364 |
+
"$set": bson.M{"last_accessed": time.Now()},
|
| 365 |
+
}
|
| 366 |
+
|
| 367 |
+
_, err := um.historyCollection.UpdateOne(
|
| 368 |
+
ctx,
|
| 369 |
+
bson.M{"user_id": userID, "short_hash": shortHash},
|
| 370 |
+
update,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
if err != nil {
|
| 374 |
+
return fmt.Errorf("failed to update download count: %w", err)
|
| 375 |
+
}
|
| 376 |
+
|
| 377 |
+
return nil
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
// PromoteUser promotes a user to premium or admin
|
| 381 |
+
func (um *UserManager) PromoteUser(ctx context.Context, userID int64, role string) error {
|
| 382 |
+
var dailyLimit, monthlyLimit int64
|
| 383 |
+
|
| 384 |
+
switch role {
|
| 385 |
+
case RolePremium:
|
| 386 |
+
dailyLimit = PremiumDailyLimit
|
| 387 |
+
monthlyLimit = PremiumMonthlyLimit
|
| 388 |
+
case RoleAdmin:
|
| 389 |
+
dailyLimit = AdminDailyLimit
|
| 390 |
+
monthlyLimit = AdminMonthlyLimit
|
| 391 |
+
default:
|
| 392 |
+
dailyLimit = BasicDailyLimit
|
| 393 |
+
monthlyLimit = BasicMonthlyLimit
|
| 394 |
+
role = RoleBasic
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
update := bson.M{
|
| 398 |
+
"$set": bson.M{
|
| 399 |
+
"role": role,
|
| 400 |
+
"daily_limit": dailyLimit,
|
| 401 |
+
"monthly_limit": monthlyLimit,
|
| 402 |
+
"updated_at": time.Now(),
|
| 403 |
+
},
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
_, err := um.statsCollection.UpdateOne(
|
| 407 |
+
ctx,
|
| 408 |
+
bson.M{"user_id": userID},
|
| 409 |
+
update,
|
| 410 |
+
)
|
| 411 |
+
|
| 412 |
+
if err != nil {
|
| 413 |
+
return fmt.Errorf("failed to promote user: %w", err)
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
return nil
|
| 417 |
+
}
|
internal/routes/internal_api.go
CHANGED
|
@@ -2,8 +2,8 @@
|
|
| 2 |
package routes
|
| 3 |
|
| 4 |
import (
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
"net/http"
|
| 8 |
"strconv"
|
| 9 |
|
|
|
|
| 2 |
package routes
|
| 3 |
|
| 4 |
import (
|
| 5 |
+
"TelegramCloud/tgf/config"
|
| 6 |
+
"TelegramCloud/tgf/internal/bot"
|
| 7 |
"net/http"
|
| 8 |
"strconv"
|
| 9 |
|
internal/routes/stream.go
CHANGED
|
@@ -1,8 +1,9 @@
|
|
| 1 |
package routes
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
-
"
|
|
|
|
| 6 |
"fmt"
|
| 7 |
"io"
|
| 8 |
"net/http"
|
|
@@ -122,6 +123,27 @@ func getStreamRoute(ctx *gin.Context) {
|
|
| 122 |
|
| 123 |
ctx.Header("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, file.FileName))
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
if r.Method != "HEAD" {
|
| 126 |
lr, _ := utils.NewTelegramReader(ctx, worker.Client, file.Location, start, end, contentLength, messageID)
|
| 127 |
if _, err := io.CopyN(w, lr, contentLength); err != nil {
|
|
|
|
| 1 |
package routes
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/internal/bot"
|
| 5 |
+
"TelegramCloud/tgf/internal/db"
|
| 6 |
+
"TelegramCloud/tgf/internal/utils"
|
| 7 |
"fmt"
|
| 8 |
"io"
|
| 9 |
"net/http"
|
|
|
|
| 123 |
|
| 124 |
ctx.Header("Content-Disposition", fmt.Sprintf("%s; filename=\"%s\"", disposition, file.FileName))
|
| 125 |
|
| 126 |
+
// Track download in user analytics (if database is available)
|
| 127 |
+
if db.UserMgr != nil {
|
| 128 |
+
go func() {
|
| 129 |
+
// Try to get user ID from file history (we don't have direct user context here)
|
| 130 |
+
// This is a simplified approach - in a real system you might want to include user ID in the URL
|
| 131 |
+
shortHash := utils.GetShortHash(expectedHash)
|
| 132 |
+
|
| 133 |
+
// Update download count for the file
|
| 134 |
+
// Note: We can't easily determine the user ID from just the hash, so this is a limitation
|
| 135 |
+
// You might want to modify the URL structure to include user identification
|
| 136 |
+
|
| 137 |
+
// For now, we'll just log the download
|
| 138 |
+
log.Info("File downloaded",
|
| 139 |
+
zap.String("filename", file.FileName),
|
| 140 |
+
zap.Int64("size", contentLength),
|
| 141 |
+
zap.String("hash", shortHash),
|
| 142 |
+
zap.String("ip", ctx.ClientIP()),
|
| 143 |
+
)
|
| 144 |
+
}()
|
| 145 |
+
}
|
| 146 |
+
|
| 147 |
if r.Method != "HEAD" {
|
| 148 |
lr, _ := utils.NewTelegramReader(ctx, worker.Client, file.Location, start, end, contentLength, messageID)
|
| 149 |
if _, err := io.CopyN(w, lr, contentLength); err != nil {
|
internal/utils/hashing.go
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
package utils
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
)
|
| 7 |
|
| 8 |
func PackFile(fileName string, fileSize int64, mimeType string, fileID int64) string {
|
|
|
|
| 1 |
package utils
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
+
"TelegramCloud/tgf/internal/types"
|
| 6 |
)
|
| 7 |
|
| 8 |
func PackFile(fileName string, fileSize int64, mimeType string, fileID int64) string {
|
internal/utils/helpers.go
CHANGED
|
@@ -1,10 +1,10 @@
|
|
| 1 |
package utils
|
| 2 |
|
| 3 |
import (
|
| 4 |
-
"
|
| 5 |
-
"
|
| 6 |
-
"
|
| 7 |
-
"
|
| 8 |
"context"
|
| 9 |
"errors"
|
| 10 |
"fmt"
|
|
@@ -266,7 +266,47 @@ func GenerateStreamLink(ctx *ext.Context, u *ext.Update, messageID int) (string,
|
|
| 266 |
hash := GetShortHash(fullHash)
|
| 267 |
link := fmt.Sprintf("%s/stream/%d?hash=%s", config.ValueOf.Host, newMsgID, hash)
|
| 268 |
|
| 269 |
-
// *** NEW LOGIC:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
if db.Files != nil && db.Users != nil {
|
| 271 |
go func() {
|
| 272 |
dbCtx := context.Background()
|
|
|
|
| 1 |
package utils
|
| 2 |
|
| 3 |
import (
|
| 4 |
+
"TelegramCloud/tgf/config"
|
| 5 |
+
"TelegramCloud/tgf/internal/cache"
|
| 6 |
+
"TelegramCloud/tgf/internal/db"
|
| 7 |
+
"TelegramCloud/tgf/internal/types"
|
| 8 |
"context"
|
| 9 |
"errors"
|
| 10 |
"fmt"
|
|
|
|
| 266 |
hash := GetShortHash(fullHash)
|
| 267 |
link := fmt.Sprintf("%s/stream/%d?hash=%s", config.ValueOf.Host, newMsgID, hash)
|
| 268 |
|
| 269 |
+
// *** NEW LOGIC: USER MANAGEMENT & ANALYTICS TRACKING ***
|
| 270 |
+
userID := u.EffectiveUser().ID
|
| 271 |
+
|
| 272 |
+
// Check user limits before processing (if database is available)
|
| 273 |
+
if db.UserMgr != nil {
|
| 274 |
+
go func() {
|
| 275 |
+
dbCtx := context.Background()
|
| 276 |
+
|
| 277 |
+
// Check if user has exceeded limits
|
| 278 |
+
canUpload, limitMsg, err := db.UserMgr.CheckUserLimits(dbCtx, userID)
|
| 279 |
+
if err != nil {
|
| 280 |
+
log.Error("Failed to check user limits", zap.Error(err))
|
| 281 |
+
} else if !canUpload {
|
| 282 |
+
log.Warn("User exceeded limits", zap.Int64("userID", userID), zap.String("reason", limitMsg))
|
| 283 |
+
// Note: At this point the file is already processed, but we log the violation
|
| 284 |
+
// You might want to move this check earlier in the process
|
| 285 |
+
}
|
| 286 |
+
|
| 287 |
+
// Update user statistics
|
| 288 |
+
err = db.UserMgr.UpdateUserStats(dbCtx, userID, file.FileSize)
|
| 289 |
+
if err != nil {
|
| 290 |
+
log.Error("Failed to update user stats", zap.Error(err))
|
| 291 |
+
}
|
| 292 |
+
|
| 293 |
+
// Update daily usage
|
| 294 |
+
err = db.UserMgr.UpdateDailyUsage(dbCtx, userID, 0) // 0 bandwidth for upload
|
| 295 |
+
if err != nil {
|
| 296 |
+
log.Error("Failed to update daily usage", zap.Error(err))
|
| 297 |
+
}
|
| 298 |
+
|
| 299 |
+
// Add to user's file history
|
| 300 |
+
err = db.UserMgr.AddFileToHistory(dbCtx, userID, newMsgID, file.FileName, file.FileSize, file.MimeType, hash, fullHash)
|
| 301 |
+
if err != nil {
|
| 302 |
+
log.Error("Failed to add file to user history", zap.Error(err))
|
| 303 |
+
} else {
|
| 304 |
+
log.Info("Successfully tracked user activity", zap.Int64("userID", userID), zap.String("fileName", file.FileName))
|
| 305 |
+
}
|
| 306 |
+
}()
|
| 307 |
+
}
|
| 308 |
+
|
| 309 |
+
// *** EXISTING DASHBOARD LOGIC (keep this as is) ***
|
| 310 |
if db.Files != nil && db.Users != nil {
|
| 311 |
go func() {
|
| 312 |
dbCtx := context.Background()
|