This commit is contained in:
stefanodvx 2025-04-14 13:05:43 +02:00
parent 264c97183e
commit 3faede7b1c
74 changed files with 6228 additions and 1 deletions

44
bot/handlers/ext.go Normal file
View file

@ -0,0 +1,44 @@
package handlers
import (
extractors "govd/ext"
"strings"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
func ExtractorsHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
ctx.CallbackQuery.Answer(bot, nil)
messageText := "available extractors:\n"
extractorNames := make([]string, 0, len(extractors.List))
for _, extractor := range extractors.List {
if extractor.IsRedirect {
continue
}
extractorNames = append(extractorNames, extractor.Name)
}
messageText += strings.Join(extractorNames, ", ")
ctx.EffectiveMessage.EditText(
bot,
messageText,
&gotgbot.EditMessageTextOpts{
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{
IsDisabled: true,
},
ReplyMarkup: gotgbot.InlineKeyboardMarkup{
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
{
{
Text: "back",
CallbackData: "start",
},
},
},
},
},
)
return nil
}

45
bot/handlers/help.go Normal file
View file

@ -0,0 +1,45 @@
package handlers
import (
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
var helpMessage = "usage:\n" +
"- you can add the bot to a group " +
"to start catching sent links\n" +
"- you can send a link to the bot privately " +
"to download the media too\n\n" +
"group commands:\n" +
"- /settings = show current settings\n" +
"- /captions (true|false) = enable/disable descriptions\n" +
"- /nsfw (true|false) = enable/disable nsfw content\n" +
"- /limit (int) = set max items in media groups\n\n" +
"note: the bot is still in beta, " +
"so expect some bugs and missing features.\n"
var helpKeyboard = gotgbot.InlineKeyboardMarkup{
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
{
{
Text: "back",
CallbackData: "start",
},
},
},
}
func HelpHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
ctx.CallbackQuery.Answer(bot, nil)
ctx.EffectiveMessage.EditText(
bot,
helpMessage,
&gotgbot.EditMessageTextOpts{
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{
IsDisabled: true,
},
ReplyMarkup: helpKeyboard,
},
)
return nil
}

91
bot/handlers/inline.go Normal file
View file

@ -0,0 +1,91 @@
package handlers
import (
"context"
"govd/bot/core"
"govd/models"
"govd/util"
"strings"
"time"
extractors "govd/ext"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
func InlineDownloadHandler(
bot *gotgbot.Bot,
ctx *ext.Context,
) error {
url := strings.TrimSpace(ctx.InlineQuery.Query)
if url == "" {
ctx.InlineQuery.Answer(bot, []gotgbot.InlineQueryResult{}, &gotgbot.AnswerInlineQueryOpts{
CacheTime: 1,
IsPersonal: true,
})
return nil
}
dlCtx, err := extractors.CtxByURL(url)
if err != nil || dlCtx == nil || dlCtx.Extractor == nil {
ctx.InlineQuery.Answer(bot, []gotgbot.InlineQueryResult{}, &gotgbot.AnswerInlineQueryOpts{
CacheTime: 1,
IsPersonal: true,
})
return nil
}
return core.HandleInline(bot, ctx, dlCtx)
}
func InlineDownloadResultHandler(
bot *gotgbot.Bot,
ctx *ext.Context,
) error {
dlCtx, ok := core.InlineTasks[ctx.ChosenInlineResult.ResultId]
if !ok {
return nil
}
defer delete(core.InlineTasks, ctx.ChosenInlineResult.ResultId)
mediaChan := make(chan *models.Media, 1)
errChan := make(chan error, 1)
timeout, cancel := context.WithTimeout(
context.Background(),
5*time.Minute,
)
defer cancel()
go core.GetInlineFormat(
bot, ctx, dlCtx,
mediaChan, errChan,
)
select {
case media := <-mediaChan:
err := core.HandleInlineCachedResult(
bot, ctx,
dlCtx, media,
)
if err != nil {
core.HandleErrorMessage(bot, ctx, err)
return nil
}
case err := <-errChan:
core.HandleErrorMessage(bot, ctx, err)
return nil
case <-timeout.Done():
core.HandleErrorMessage(bot, ctx, util.ErrTimeout)
return nil
}
return nil
}
func InlineLoadingHandler(
bot *gotgbot.Bot,
ctx *ext.Context,
) error {
ctx.CallbackQuery.Answer(bot, &gotgbot.AnswerCallbackQueryOpts{
Text: "wait !",
ShowAlert: true,
})
return nil
}

72
bot/handlers/instances.go Normal file
View file

@ -0,0 +1,72 @@
package handlers
import (
"fmt"
"os"
"runtime"
"strings"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
var buildHash = "unknown"
var branchName = "unknown"
func getInstanceMessage() string {
return "current instance\n" +
"go version: %s\n" +
"build: <a href='%s'>%s</a>\n" +
"branch: <a href='%s'>%s</a>\n\n" +
"public instances\n" +
"- @govd_bot | main official instance\n" +
"\nwant to add your own instance? reach us on @govdsupport"
}
func InstancesHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
var commitURL string
var branchURL string
repoURL := os.Getenv("REPO_URL")
if repoURL != "" {
commitURL = fmt.Sprintf(
"%s/tree/%s",
repoURL,
buildHash,
)
branchURL = fmt.Sprintf(
"%s/tree/%s",
repoURL,
branchName,
)
}
messageText := fmt.Sprintf(
getInstanceMessage(),
strings.TrimPrefix(runtime.Version(), "go"),
commitURL,
buildHash,
branchURL,
branchName,
)
ctx.CallbackQuery.Answer(bot, nil)
ctx.EffectiveMessage.EditText(
bot,
messageText,
&gotgbot.EditMessageTextOpts{
LinkPreviewOptions: &gotgbot.LinkPreviewOptions{
IsDisabled: true,
},
ReplyMarkup: gotgbot.InlineKeyboardMarkup{
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
{
{
Text: "back",
CallbackData: "start",
},
},
},
},
},
)
return nil
}

213
bot/handlers/settings.go Normal file
View file

@ -0,0 +1,213 @@
package handlers
import (
"fmt"
"govd/database"
"govd/util"
"strconv"
"strings"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
func SettingsHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type == "private" {
ctx.EffectiveMessage.Reply(
bot,
"use this command in group chats only",
nil,
)
return nil
}
settings, err := database.GetGroupSettings(ctx.EffectiveMessage.Chat.Id)
if err != nil {
return err
}
ctx.EffectiveMessage.Reply(
bot,
fmt.Sprintf(
"settings for this group\n\ncaptions: %s\nnsfw: %s\nmedia group limit: %d",
strconv.FormatBool(*settings.Captions),
strconv.FormatBool(*settings.NSFW),
settings.MediaGroupLimit,
),
nil,
)
return nil
}
func CaptionsHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type == "private" {
return nil
}
chatID := ctx.EffectiveMessage.Chat.Id
userID := ctx.EffectiveMessage.From.Id
args := ctx.Args()
if len(args) != 2 {
ctx.EffectiveMessage.Reply(
bot,
"usage: /captions (true|false)",
nil,
)
return nil
}
if !util.IsUserAdmin(bot, chatID, userID) {
ctx.EffectiveMessage.Reply(
bot,
"you don't have permission to change settings",
nil,
)
return nil
}
userInput := strings.ToLower(args[1])
value, err := strconv.ParseBool(userInput)
if err != nil {
ctx.EffectiveMessage.Reply(
bot,
fmt.Sprintf("invalid value (%s), use true or false", userInput),
nil,
)
return nil
}
settings, err := database.GetGroupSettings(chatID)
if err != nil {
return err
}
settings.Captions = &value
err = database.UpdateGroupSettings(chatID, settings)
if err != nil {
return err
}
var message string
if value {
message = "captions enabled"
} else {
message = "captions disabled"
}
ctx.EffectiveMessage.Reply(
bot,
message,
nil,
)
return nil
}
func NSFWHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type == "private" {
return nil
}
chatID := ctx.EffectiveMessage.Chat.Id
userID := ctx.EffectiveMessage.From.Id
args := ctx.Args()
if len(args) != 2 {
ctx.EffectiveMessage.Reply(
bot,
"usage: /nsfw (true|false)",
nil,
)
return nil
}
if !util.IsUserAdmin(bot, chatID, userID) {
ctx.EffectiveMessage.Reply(
bot,
"you don't have permission to change settings",
nil,
)
return nil
}
userInput := strings.ToLower(args[1])
value, err := strconv.ParseBool(userInput)
if err != nil {
ctx.EffectiveMessage.Reply(
bot,
fmt.Sprintf("invalid value (%s), use true or false", userInput),
nil,
)
return nil
}
settings, err := database.GetGroupSettings(chatID)
if err != nil {
return err
}
settings.NSFW = &value
err = database.UpdateGroupSettings(chatID, settings)
if err != nil {
return err
}
var message string
if value {
message = "nsfw enabled"
} else {
message = "nsfw disabled"
}
ctx.EffectiveMessage.Reply(
bot,
message,
nil,
)
return nil
}
func MediaGroupLimitHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type == "private" {
return nil
}
chatID := ctx.EffectiveMessage.Chat.Id
userID := ctx.EffectiveMessage.From.Id
args := ctx.Args()
if len(args) != 2 {
ctx.EffectiveMessage.Reply(
bot,
"usage: /limit (int)",
nil,
)
return nil
}
if !util.IsUserAdmin(bot, chatID, userID) {
ctx.EffectiveMessage.Reply(
bot,
"you don't have permission to change settings",
nil,
)
return nil
}
value, err := strconv.Atoi(args[1])
if err != nil {
ctx.EffectiveMessage.Reply(
bot,
fmt.Sprintf("invalid value (%s), use a number", args[1]),
nil,
)
return nil
}
if value < 1 || value > 20 {
ctx.EffectiveMessage.Reply(
bot,
"media group limit must be between 1 and 20",
nil,
)
return nil
}
settings, err := database.GetGroupSettings(chatID)
if err != nil {
return err
}
settings.MediaGroupLimit = value
err = database.UpdateGroupSettings(chatID, settings)
if err != nil {
return err
}
ctx.EffectiveMessage.Reply(
bot,
fmt.Sprintf("media group limit set to %d", value),
nil,
)
return nil
}

88
bot/handlers/start.go Normal file
View file

@ -0,0 +1,88 @@
package handlers
import (
"fmt"
"os"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
var startMessage = "govd is an open-source telegram bot " +
"that allows you to download medias from " +
"various platforms. the project born after " +
"the discontinuation of an " +
"highly popular bot, known as UVD."
func getStartKeyboard(bot *gotgbot.Bot) gotgbot.InlineKeyboardMarkup {
return gotgbot.InlineKeyboardMarkup{
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
{
{
Text: "add to group",
Url: fmt.Sprintf(
"https://t.me/%s?startgroup=true",
bot.Username,
),
},
},
{
{
Text: "usage",
CallbackData: "help",
},
{
Text: "stats",
CallbackData: "stats",
},
},
{
{
Text: "extractors",
CallbackData: "extractors",
},
{
Text: "support",
Url: "https://t.me/govdsupport",
},
},
{
{
Text: "instances",
CallbackData: "instances",
},
{
Text: "github",
Url: os.Getenv("REPO_URL"),
},
},
},
}
}
func StartHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type != "private" {
return nil
}
keyboard := getStartKeyboard(bot)
if ctx.Update.Message != nil {
ctx.EffectiveMessage.Reply(
bot,
startMessage,
&gotgbot.SendMessageOpts{
ReplyMarkup: &keyboard,
},
)
} else if ctx.Update.CallbackQuery != nil {
ctx.CallbackQuery.Answer(bot, nil)
ctx.EffectiveMessage.EditText(
bot,
startMessage,
&gotgbot.EditMessageTextOpts{
ReplyMarkup: keyboard,
},
)
}
return nil
}

89
bot/handlers/stats.go Normal file
View file

@ -0,0 +1,89 @@
package handlers
import (
"fmt"
"govd/database"
"time"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
)
type Stats struct {
TotalUsers int64
TotalGroups int64
TotalDailyUsers int64
TotalMedia int64
UpdatedAt time.Time
}
var lastSavedStats *Stats
var statsMessage = "users: %d\nusers today: %d\ngroups: %d\ndownloads: %d\n\nupdates every 10 minutes"
func StatsHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
if ctx.EffectiveMessage.Chat.Type != "private" {
return nil
}
ctx.CallbackQuery.Answer(bot, nil)
stats := GetStats()
ctx.EffectiveMessage.EditText(
bot,
fmt.Sprintf(
statsMessage,
stats.TotalUsers,
stats.TotalDailyUsers,
stats.TotalGroups,
stats.TotalMedia,
),
&gotgbot.EditMessageTextOpts{
ReplyMarkup: gotgbot.InlineKeyboardMarkup{
InlineKeyboard: [][]gotgbot.InlineKeyboardButton{
{
{
Text: "back",
CallbackData: "start",
},
},
},
},
},
)
return nil
}
func UpdateStats() {
totalUsers, err := database.GetUsersCount()
if err != nil {
return
}
totalGroups, err := database.GetGroupsCount()
if err != nil {
return
}
totalDailyUsers, err := database.GetDailyUserCount()
if err != nil {
return
}
totalMedia, err := database.GetMediaCount()
if err != nil {
return
}
lastSavedStats = &Stats{
TotalUsers: totalUsers,
TotalGroups: totalGroups,
TotalDailyUsers: totalDailyUsers,
TotalMedia: totalMedia,
UpdatedAt: time.Now(),
}
}
func GetStats() *Stats {
if lastSavedStats == nil {
UpdateStats()
}
if lastSavedStats.UpdatedAt.Add(10 * time.Minute).Before(time.Now()) {
UpdateStats()
}
return lastSavedStats
}

65
bot/handlers/url.go Normal file
View file

@ -0,0 +1,65 @@
package handlers
import (
"govd/bot/core"
"govd/database"
extractors "govd/ext"
"github.com/PaulSonOfLars/gotgbot/v2"
"github.com/PaulSonOfLars/gotgbot/v2/ext"
"github.com/PaulSonOfLars/gotgbot/v2/ext/handlers/filters/message"
)
func URLHandler(bot *gotgbot.Bot, ctx *ext.Context) error {
messageURL := getMessageURL(ctx.EffectiveMessage)
if messageURL == "" {
return nil
}
dlCtx, err := extractors.CtxByURL(messageURL)
if err != nil {
core.HandleErrorMessage(
bot, ctx, err)
return nil
}
if dlCtx == nil || dlCtx.Extractor == nil {
return nil
}
userID := ctx.EffectiveMessage.From.Id
if ctx.EffectiveMessage.Chat.Type != "private" {
settings, err := database.GetGroupSettings(ctx.EffectiveMessage.Chat.Id)
if err != nil {
return err
}
dlCtx.GroupSettings = settings
}
if userID != 1087968824 {
// groupAnonymousBot
_, err = database.GetUser(userID)
if err != nil {
return err
}
}
err = core.HandleDownloadRequest(bot, ctx, dlCtx)
if err != nil {
core.HandleErrorMessage(
bot, ctx, err)
}
return nil
}
func URLFilter(msg *gotgbot.Message) bool {
return message.Text(msg) && !message.Command(msg) && containsURL(msg)
}
func containsURL(msg *gotgbot.Message) bool {
return message.Entity("url")(msg)
}
func getMessageURL(msg *gotgbot.Message) string {
for _, entity := range msg.Entities {
if entity.Type == "url" {
return msg.Text[entity.Offset : entity.Offset+entity.Length]
}
}
return ""
}