refactor and add videos

This commit is contained in:
yyasha 2024-03-27 18:42:44 +03:00
parent 4568ea3913
commit 2c44abde3c
10 changed files with 2818 additions and 173 deletions

View file

@ -6,7 +6,7 @@ ADD go.mod .
COPY . .
RUN go mod download
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" -o main main.go
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" -o main ./cmd/bridge/main.go
FROM alpine

39
cmd/bridge/main.go Normal file
View file

@ -0,0 +1,39 @@
package main
import (
"bridge/internal/config"
"bridge/internal/telegram"
"bridge/internal/vk"
"log/slog"
"os"
prettyLogger "github.com/charmbracelet/log"
)
func main() {
// get config
cfg := config.MustLoad()
logger := setupLogger()
// create chan
video_links := make(chan string, cfg.Workers)
// vk
vk, err := vk.New(logger, cfg.VkAPIKey, video_links)
if err != nil {
panic(err)
}
// tg
tg, err := telegram.New(logger, cfg.TgAPIKey, cfg.TgChatID, video_links)
if err != nil {
panic(err)
}
// run
tg.Run(cfg.Workers)
vk.MustRun()
}
func setupLogger() *slog.Logger {
var log *slog.Logger
handler := prettyLogger.NewWithOptions(os.Stdout, prettyLogger.Options{Level: prettyLogger.DebugLevel})
log = slog.New(handler)
return log
}

21
go.mod
View file

@ -3,13 +3,28 @@ module bridge
go 1.20
require (
github.com/SevereCloud/vksdk/v2 v2.16.1 // indirect
github.com/SevereCloud/vksdk/v2 v2.16.1
github.com/charmbracelet/log v0.4.0
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
github.com/s32x/httpclient v0.0.0-20220217184346-6df4d4d51c14
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.1.2 // indirect
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect
github.com/charmbracelet/lipgloss v0.10.0 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/klauspost/compress v1.16.7 // indirect
github.com/s32x/httpclient v0.0.0-20220217184346-6df4d4d51c14 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/muesli/reflow v0.3.0 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.0 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
h12.io/socks v1.0.3 // indirect
)

35
go.sum
View file

@ -1,20 +1,55 @@
github.com/SevereCloud/vksdk/v2 v2.16.1 h1:UiazL3vTy7lMm33oIXRMxXg8S5I8bQuqEdLtbmOSpG4=
github.com/SevereCloud/vksdk/v2 v2.16.1/go.mod h1:UfVcBt8qh5+gIflQO6L+CWwrXcpwhOl5hKvKf8sXUd8=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/cenkalti/backoff/v4 v4.1.2 h1:6Yo7N8UP2K6LWZnW94DLVSSrbobcWdVzAYOisuDPIFo=
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s=
github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE=
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364 h1:5XxdakFhqd9dnXoAZy1Mb2R/DZ6D1e+0bGC/JhucGYI=
github.com/h12w/go-socks5 v0.0.0-20200522160539-76189e178364/go.mod h1:eDJQioIyy4Yn3MVivT7rv/39gAJTrA7lgmYr8EW950c=
github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I=
github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 h1:JhzVVoYvbOACxoUmOs6V/G4D5nPVUW73rKvXxP4XUJc=
github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/s32x/httpclient v0.0.0-20220217184346-6df4d4d51c14 h1:7X+d8s674m+oOrb2YbbBrtA1Ey1puN9mBvHp2SWfkig=
github.com/s32x/httpclient v0.0.0-20220217184346-6df4d4d51c14/go.mod h1:FqlhGa3u28mSvrE2aDhpKc1h1WpEcjmrXFkN/e2qnxQ=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/vmihailenco/msgpack/v5 v5.4.0 h1:hRM0digJwyR6vll33NNAwCFguy5JuBD6jxDmQP3l608=
github.com/vmihailenco/msgpack/v5 v5.4.0/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
h12.io/socks v1.0.3 h1:Ka3qaQewws4j4/eDQnOdpr4wXsC//dXtWvftlIcCQUo=
h12.io/socks v1.0.3/go.mod h1:AIhxy1jOId/XCz9BO+EIgNL2rQiPTBNnOfnVnQ+3Eck=

30
internal/config/config.go Normal file
View file

@ -0,0 +1,30 @@
package config
import (
"os"
"strconv"
)
type Config struct {
Workers int
TgAPIKey string
TgChatID int64
VkAPIKey string
}
// MustLoad loads the config.
func MustLoad() *Config {
var Conf Config
var err error
Conf.Workers, err = strconv.Atoi(os.Getenv("Workers"))
if err != nil {
panic(err)
}
Conf.TgAPIKey = os.Getenv("TgAPIKey")
Conf.VkAPIKey = os.Getenv("VkAPIKey")
Conf.TgChatID, err = strconv.ParseInt(os.Getenv("TgChatID"), 10, 64)
if err != nil {
panic(err)
}
return &Conf
}

View file

@ -0,0 +1,79 @@
package telegram
import (
"fmt"
"log/slog"
"strings"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/s32x/httpclient"
)
type TG struct {
api *tgbotapi.BotAPI
video_links chan string
logger *slog.Logger
chat_id int64
}
func New(logger *slog.Logger, api_key string, chat_id int64, video_links chan string) (*TG, error) {
const op = "telegram.New"
bot, err := tgbotapi.NewBotAPI(api_key)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
bot.Debug = false
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
return &TG{
api: bot,
video_links: video_links,
logger: logger,
chat_id: chat_id,
}, nil
}
func (tg *TG) Run(count int) {
for i := 0; i < count; i++ {
go tg.new_tg_worker(i + 1)
}
}
const PCUserAgent string = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
const MobileUserAgent string = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36"
func (tg *TG) new_tg_worker(id int) {
for link := range tg.video_links {
tg.logger.Info(fmt.Sprintf("[%d]", id), "Получена ссылка, загружаю:", link)
// get video by link
var client *httpclient.Client
if strings.Contains(link, "VK_ANDROID") {
// create mobile client
client = httpclient.New().WithHeader("User-Agent", MobileUserAgent)
} else {
// create Desktop client
client = httpclient.New().WithHeader("User-Agent", PCUserAgent)
}
resp, err := client.Get(link).Do()
if err != nil {
tg.logger.Error("Error with get media", "err", err)
continue
}
body, err := resp.Bytes()
if err != nil {
tg.logger.Error("Error with get body", "err", err)
continue
}
msg := tgbotapi.NewVideo(tg.chat_id, tgbotapi.FileBytes{Name: "story", Bytes: body})
tg.logger.Info(fmt.Sprintf("[%d] Отправляю видео в телеграм...", id))
_, err = tg.api.Send(msg)
if err != nil {
tg.logger.Error("Error with send message", "err", err)
continue
}
}
}

1
internal/vk/story.go Normal file
View file

@ -0,0 +1 @@
package vk

2525
internal/vk/video.go Normal file

File diff suppressed because one or more lines are too long

90
internal/vk/vk.go Normal file
View file

@ -0,0 +1,90 @@
package vk
import (
"context"
"fmt"
"log/slog"
"github.com/SevereCloud/vksdk/v2/api"
"github.com/SevereCloud/vksdk/v2/events"
"github.com/SevereCloud/vksdk/v2/longpoll-bot"
)
type VK struct {
api *api.VK
lp *longpoll.LongPoll
}
func New(logger *slog.Logger, api_key string, video_links chan string) (*VK, error) {
const op = "vk.New"
vk := api.NewVK(api_key)
// get information about the group
group, err := vk.GroupsGetByID(nil)
if err != nil {
return nil, fmt.Errorf("cannot get group info: %s: %w", op, err)
}
// Initializing Long Poll
lp, err := longpoll.NewLongPoll(vk, group[0].ID)
if err != nil {
return nil, fmt.Errorf("%s: %w", op, err)
}
// New message event
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
logger.Info("Message", "peer", obj.Message.PeerID, "text", obj.Message.Text)
for _, obj := range obj.Message.Attachments {
objectsw:
switch obj.Type {
case "story":
// fmt.Printf("Story %+v\n", obj.Story.Video.Files)
// get video link from struct
var videolink string
switch {
case obj.Story.Video.Files.Mp4_720 != "":
videolink = obj.Story.Video.Files.Mp4_720
case obj.Story.Video.Files.Mp4_480 != "":
videolink = obj.Story.Video.Files.Mp4_480
case obj.Story.Video.Files.Mp4_360 != "":
videolink = obj.Story.Video.Files.Mp4_360
case obj.Story.Video.Files.Mp4_240 != "":
videolink = obj.Story.Video.Files.Mp4_240
default:
logger.Error("cannot find video link")
break objectsw
}
// fmt.Println("Story:", videolink)
if err != nil {
logger.Error("vk error", "err", err)
}
// send video to tg
video_links <- videolink
case "video":
// logger.Info("Video", "owner", obj.Video.OwnerID, "id", obj.Video.ID)
owner := obj.Video.OwnerID
video_id := obj.Video.ID
videolink, err := fetchVideoURL(owner, video_id)
if err != nil {
logger.Error("cannot get video link", "err", err)
}
video_links <- videolink
default:
logger.Debug("Message type not found", "type", obj.Type, "obj", fmt.Sprintf("%+v", obj))
}
}
})
return &VK{
api: vk,
lp: lp,
}, nil
}
// Run starts message listening.
func (vk *VK) MustRun() {
// Run Bots Long Poll
if err := vk.lp.Run(); err != nil {
panic(err)
}
}

169
main.go
View file

@ -1,169 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
"runtime"
"strconv"
"strings"
"github.com/SevereCloud/vksdk/v2/api"
"github.com/SevereCloud/vksdk/v2/events"
"github.com/SevereCloud/vksdk/v2/longpoll-bot"
"github.com/s32x/httpclient"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
)
var video_links chan (string)
const PCUserAgent string = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/117.0.0.0 Safari/537.36"
const MobileUserAgent string = "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Mobile Safari/537.36"
func main() {
// get config
mustConfigLoad()
// create chan
video_links = make(chan string, Conf.Workers*2)
// run vk
go RunVK()
// run tg
go RunTG()
//
select {}
}
type Config struct {
Workers int
TgAPIKey string
TgChatID int64
VkAPIKey string
}
var Conf Config
func mustConfigLoad() {
var err error
Conf.Workers, err = strconv.Atoi(os.Getenv("Workers"))
if err != nil {
panic(err)
}
Conf.TgAPIKey = os.Getenv("TgAPIKey")
Conf.VkAPIKey = os.Getenv("VkAPIKey")
Conf.TgChatID, err = strconv.ParseInt(os.Getenv("TgChatID"), 10, 64)
if err != nil {
panic(err)
}
}
func RunVK() {
vk := api.NewVK(Conf.VkAPIKey)
// get information about the group
group, err := vk.GroupsGetByID(nil)
if err != nil {
log.Fatal(err)
}
// Initializing Long Poll
lp, err := longpoll.NewLongPoll(vk, group[0].ID)
if err != nil {
log.Fatal(err)
}
// New message event
lp.MessageNew(func(_ context.Context, obj events.MessageNewObject) {
log.Printf("%d: %s", obj.Message.PeerID, obj.Message.Text)
for _, obj := range obj.Message.Attachments {
objectsw:
switch obj.Type {
case "story":
// fmt.Printf("Story %+v\n", obj.Story.Video.Files)
// get video link from struct
var videolink string
switch {
case obj.Story.Video.Files.Mp4_720 != "":
videolink = obj.Story.Video.Files.Mp4_720
case obj.Story.Video.Files.Mp4_480 != "":
videolink = obj.Story.Video.Files.Mp4_480
case obj.Story.Video.Files.Mp4_360 != "":
videolink = obj.Story.Video.Files.Mp4_360
case obj.Story.Video.Files.Mp4_240 != "":
videolink = obj.Story.Video.Files.Mp4_240
default:
error_logging(errors.New("cannot find video link"))
break objectsw
}
// fmt.Println("Story:", videolink)
error_logging(err)
// send video to tg
video_links <- videolink
}
}
})
// Run Bots Long Poll
log.Println("[VK] Start Long Poll")
if err := lp.Run(); err != nil {
log.Fatal(err)
}
}
func RunTG() {
bot, err := tgbotapi.NewBotAPI(Conf.TgAPIKey)
if err != nil {
log.Panic(err)
}
bot.Debug = false
log.Printf("[TG] Authorized on account %s", bot.Self.UserName)
u := tgbotapi.NewUpdate(0)
u.Timeout = 60
for i := 0; i < Conf.Workers; i++ {
go tg_worker(i+1, bot)
}
}
func tg_worker(id int, bot *tgbotapi.BotAPI) {
for link := range video_links {
log.Println(fmt.Sprintf("[%d]", id), "Получена ссылка, загружаю:", link)
// get video by link
var client *httpclient.Client
if strings.Contains(link, "VK_ANDROID") {
// create mobile client
client = httpclient.New().WithHeader("User-Agent", MobileUserAgent)
} else {
// create Desktop client
client = httpclient.New().WithHeader("User-Agent", PCUserAgent)
}
resp, err := client.Get(link).Do()
error_logging(err)
body, err := resp.Bytes()
error_logging(err)
msg := tgbotapi.NewVideo(Conf.TgChatID, tgbotapi.FileBytes{Name: "story", Bytes: body})
log.Println(fmt.Sprintf("[%d]", id), "Отправляю видео в телеграм...")
_, err = bot.Send(msg)
error_logging(err)
}
}
// Log errors
func error_logging(err error) {
if err != nil {
pc := make([]uintptr, 10)
n := runtime.Callers(2, pc)
frames := runtime.CallersFrames(pc[:n])
frame, _ := frames.Next()
// fmt.Printf("%s:%d %s\n", frame.File, frame.Line, frame.Function)
log.Printf("[Parser] error on %s line %d: %s", frame.Function, frame.Line, err)
}
}