Initial commit: docker compose config
Release Docker multi arch / docker (push) Has been cancelled
Test Install Script / Test Script Syntax (push) Has been cancelled
Test Install Script / Test on almalinux-10 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-10 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-8 (root) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (default) (push) Has been cancelled
Test Install Script / Test on almalinux-9 (root) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (default) (push) Has been cancelled
Test Install Script / Test on amazonlinux-2 (root) (push) Has been cancelled
Test Install Script / Test on debian-11 (default) (push) Has been cancelled
Test Install Script / Test on debian-11 (root) (push) Has been cancelled
Test Install Script / Test on debian-12 (default) (push) Has been cancelled
Test Install Script / Test on debian-12 (root) (push) Has been cancelled
Test Install Script / Test on debian-13 (default) (push) Has been cancelled
Test Install Script / Test on debian-13 (root) (push) Has been cancelled
Test Install Script / Test on fedora-latest (default) (push) Has been cancelled
Test Install Script / Test on fedora-latest (root) (push) Has been cancelled
Test Install Script / Test on rocky-10 (default) (push) Has been cancelled
Test Install Script / Test on rocky-10 (root) (push) Has been cancelled
Test Install Script / Test on rocky-8 (default) (push) Has been cancelled
Test Install Script / Test on rocky-8 (root) (push) Has been cancelled
Test Install Script / Test on rocky-9 (default) (push) Has been cancelled
Test Install Script / Test on rocky-9 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-22.04 (root) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (default) (push) Has been cancelled
Test Install Script / Test on ubuntu-24.04 (root) (push) Has been cancelled

This commit is contained in:
2026-05-30 12:07:11 +00:00
commit 616c6b1c62
381 changed files with 55145 additions and 0 deletions
+229
View File
@@ -0,0 +1,229 @@
package tgbot
import (
"fmt"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/dustin/go-humanize"
tele "gopkg.in/telebot.v4"
"server/log"
sets "server/settings"
"server/torr"
)
// Telegram limits the serialized reply_markup size; many file rows with long
// labels/URLs would exceed it (e.g. "reply markup is too long").
const filesPageSize = 5
// Inline button text is limited to 64 characters in the Bot API.
func truncateBtnText(s string) string {
const max = 64
r := []rune(s)
if len(r) <= max {
return s
}
if max <= 1 {
return string(r[:max])
}
return string(r[:max-1]) + "…"
}
func files(c tele.Context) error {
args := c.Args()
if len(args) < 2 {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
hash := args[1]
if !isHash(hash) {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
msg, err := c.Bot().Send(c.Sender(), tr(c.Sender().ID, "connecting"))
t := torr.GetTorrent(hash)
if t == nil {
if err == nil {
_, _ = c.Bot().Edit(msg, tr(c.Sender().ID, "torrent_not_found")+":\n<code>"+hash+"</code>")
}
return nil
}
if err == nil {
api := c.Bot()
recipient := c.Sender()
uid := c.Sender().ID
go sendFilesList(api, recipient, msg, hash, uid, 0)
}
return err
}
// sendFilesList shows one page of per-file actions; fitems / fifresh change the page in-place.
func sendFilesList(api tele.API, recipient tele.Recipient, statusMsg *tele.Message, hash string, uid int64, page int) {
t := torr.GetTorrent(hash)
for t != nil && !t.WaitInfo() {
time.Sleep(time.Second)
t = torr.GetTorrent(hash)
}
_ = api.Delete(statusMsg)
t = torr.GetTorrent(hash)
if t == nil {
return
}
ti := t.Status()
if ti == nil {
return
}
host := getHost()
txt, kbd := buildFilesListView(t, host, uid, page)
if kbd == nil {
return
}
if _, err := api.Send(recipient, txt, kbd, tele.ModeHTML); err != nil {
log.TLogln("tg files send err", err)
}
}
func buildFilesListView(t *torr.Torrent, host string, uid int64, page int) (string, *tele.ReplyMarkup) {
ti := t.Status()
if ti == nil {
return "", nil
}
hex := t.Hash().HexString()
n := len(ti.FileStats)
if n == 0 {
return "", nil
}
totalPages := (n + filesPageSize - 1) / filesPageSize
if page < 0 {
page = 0
}
if page >= totalPages {
page = totalPages - 1
}
start := page * filesPageSize
end := start + filesPageSize
if end > n {
end = n
}
pageFiles := ti.FileStats[start:end]
viewedSet := make(map[int]struct{})
for _, v := range sets.ListViewed(ti.Hash) {
viewedSet[v.FileIndex] = struct{}{}
}
txt := "📁 <b>" + escapeHtml(ti.Title) + "</b> " +
"<i>" + humanize.IBytes(uint64(ti.TorrentSize)) + "</i>\n\n" +
"<code>" + ti.Hash + "</code>"
if totalPages > 1 {
txt += "\n\n" + tr(uid, "page") + " " + strconv.Itoa(page+1) + "/" + strconv.Itoa(totalPages)
}
if n > 1 {
txt += "\n\n" + fmt.Sprintf(tr(uid, "files_range_hint"), n)
}
m := &tele.ReplyMarkup{}
var rows []tele.Row
for _, f := range pageFiles {
viewedMark := ""
if _, ok := viewedSet[f.Id]; ok {
viewedMark = "✓ "
}
baseName := filepath.Base(f.Path)
mline := viewedMark + "#" + strconv.Itoa(f.Id) + ": " + humanize.IBytes(uint64(f.Length)) + " — " + baseName
fileLabel := truncateBtnText(mline)
idStr := strconv.Itoa(f.Id)
streamURL := host + "/stream/" + filepath.Base(f.Path) + "?link=" + hex + "&index=" + idStr + "&play"
rows = append(rows, m.Row(
m.Data(fileLabel, "upload", ti.Hash, idStr),
m.URL(tr(uid, "files_link"), streamURL),
m.Data("⏳", "fpreload", ti.Hash, idStr),
))
}
if totalPages > 1 {
var nav []tele.Btn
if page > 0 {
nav = append(nav, m.Data("◀️", "fitems", strconv.Itoa(page-1), ti.Hash))
}
nav = append(nav, m.Data(strconv.Itoa(page+1)+"/"+strconv.Itoa(totalPages), "fnop"))
if page < totalPages-1 {
nav = append(nav, m.Data("▶️", "fitems", strconv.Itoa(page+1), ti.Hash))
}
nav = append(nav, m.Data("🔄", "fifresh", strconv.Itoa(page), ti.Hash))
rows = append(rows, m.Row(nav...))
} else {
rows = append(rows, m.Row(m.Data("🔄", "fifresh", strconv.Itoa(page), ti.Hash)))
}
if n > 1 {
rows = append(rows, m.Row(m.Data(tr(uid, "files_download_all"), "fall", "all", ti.Hash)))
}
m.Inline(rows...)
return txt, m
}
func callbackFileListPage(c tele.Context, pageStr, hash string) error {
page, err := strconv.Atoi(pageStr)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
if !isHash(hash) {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
_ = c.Respond(&tele.CallbackResponse{})
return editFilesListMessage(c, hash, c.Sender().ID, page)
}
func callbackFileListRefresh(c tele.Context, pageStr, hash string) error {
if !isHash(hash) {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
page, err := strconv.Atoi(pageStr)
if err != nil {
return c.Respond(&tele.CallbackResponse{Text: tr(c.Sender().ID, "callback_unknown")})
}
_ = c.Respond(&tele.CallbackResponse{Text: "🔄"})
return editFilesListMessage(c, hash, c.Sender().ID, page)
}
func editFilesListMessage(c tele.Context, hash string, uid int64, page int) error {
t := torr.GetTorrent(hash)
if t == nil {
_ = c.Send(tr(uid, "torrent_not_found") + ":\n<code>" + hash + "</code>")
return nil
}
for t != nil && !t.WaitInfo() {
time.Sleep(time.Second)
t = torr.GetTorrent(hash)
}
t = torr.GetTorrent(hash)
if t == nil {
_ = c.Send(tr(uid, "torrent_not_found") + ":\n<code>" + hash + "</code>")
return nil
}
host := getHost()
txt, kbd := buildFilesListView(t, host, uid, page)
if kbd == nil {
log.TLogln("tg files: empty kbd for hash", logSafeStr(hash, 20))
return nil
}
if c.Callback() == nil || c.Callback().Message == nil {
_, err := c.Bot().Send(c.Sender(), txt, kbd, tele.ModeHTML)
return err
}
_, err := c.Bot().Edit(c.Callback().Message, txt, kbd, tele.ModeHTML)
if err != nil {
if strings.Contains(err.Error(), "message is not modified") {
return nil
}
log.TLogln("tg files edit err", err)
_, _ = c.Bot().Send(c.Sender(), tr(uid, "error")+":\n"+escapeHtml(err.Error()), tele.ModeHTML)
return err
}
return nil
}