feat 初始化
Some checks failed
Build image / build (push) Failing after 3s

This commit is contained in:
timerzz 2024-07-01 17:22:31 +08:00
commit c00090712d
10 changed files with 510 additions and 0 deletions

View File

@ -0,0 +1,20 @@
name: Build image
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: https://git.timerzz.com:20443/timerzz/setup-go@v4
env:
HTTPS_PROXY: http://192.168.31.55:10809
with:
go-version: '1.22.x'
- uses: https://git.timerzz.com:20443/timerzz/checkout@v4
- uses: https://git.timerzz.com:20443/timerzz/setup-ko@v0.6
with:
version: v0.15.4
env:
KO_DOCKER_REPO: 192.168.31.55:5000/paas/bilibili
- run: ko build --bare ./subtitles
env:
KO_DOCKER_REPO: 192.168.31.55:5000/paas/bilibili

28
go.mod Normal file
View File

@ -0,0 +1,28 @@
module gitea.timerzz.com/paas/bilibili
go 1.22.2
require (
github.com/go-playground/validator/v10 v10.22.0
github.com/gofiber/fiber/v3 v3.0.0-beta.2
)
require (
github.com/andybalholm/brotli v1.1.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/gofiber/utils/v2 v2.0.0-beta.4 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.17.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.52.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect
)

51
go.sum Normal file
View File

@ -0,0 +1,51 @@
github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao=
github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/gofiber/fiber/v3 v3.0.0-beta.2 h1:mVVgt8PTaHGup3NGl/+7U7nEoZaXJ5OComV4E+HpAao=
github.com/gofiber/fiber/v3 v3.0.0-beta.2/go.mod h1:w7sdfTY0okjZ1oVH6rSOGvuACUIt0By1iK0HKUb3uqM=
github.com/gofiber/utils/v2 v2.0.0-beta.4 h1:1gjbVFFwVwUb9arPcqiB6iEjHBwo7cHsyS41NeIW3co=
github.com/gofiber/utils/v2 v2.0.0-beta.4/go.mod h1:sdRsPU1FXX6YiDGGxd+q2aPJRMzpsxdzCXo9dz+xtOY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.52.0 h1:wqBQpxH71XW0e2g+Og4dzQM8pk34aFYlA1Ga8db7gU0=
github.com/valyala/fasthttp v1.52.0/go.mod h1:hf5C4QnVMkNXMspnsUlfM3WitlgYflyhHYoKol/szxQ=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

129
subtitles/client.go Normal file
View File

@ -0,0 +1,129 @@
package main
import (
"fmt"
"github.com/gofiber/fiber/v3/client"
"net/url"
"regexp"
"strconv"
"strings"
)
var headers = map[string][]string{
"Accept": {"application/json"},
"Content-Type": {"application/json"},
"User-Agent": {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"},
"Host": {"api.bilibili.com"},
}
type Client struct {
cli *client.Client
}
func NewClient(sessData string) *Client {
return &Client{
cli: client.New().AddHeaders(headers).SetCookie("SESSDATA", sessData),
}
}
func (c *Client) GetSubtitles(url string) (string, error) {
vid, page, err := parseBilibiliUrl(url)
if err != nil {
return "", err
}
data, err := c.callView(vid)
if err != nil {
return "", err
}
var aid, cid = data.Data.Stat.Aid, 0
for _, p := range data.Data.Pages {
if p.Page == page {
cid = p.Cid
}
}
player, err := c.callPlayer(aid, cid)
if err != nil {
return "", err
}
subTitlesUrl := ""
for _, sub := range player.Data.Subtitle.Subtitles {
if sub.SubtitleUrl != "" {
subTitlesUrl = fmt.Sprintf("https:%s", sub.SubtitleUrl)
break
}
}
subtitles, err := c.callSubtitles(subTitlesUrl)
if err != nil {
return "", err
}
var subtitlesList = make([]string, 0, len(subtitles.Body))
for _, sub := range subtitles.Body {
subtitlesList = append(subtitlesList, sub.Content)
}
return strings.Join(subtitlesList, "\n"), nil
}
func (c *Client) callView(vid string) (data Response[ViewResponseData], err error) {
var r = c.cli.R()
if strings.HasPrefix(vid, "av") {
r.AddParam("aid", vid[2:])
} else {
r.AddParam("bvid", vid)
}
resp, err := r.Get("https://api.bilibili.com/x/web-interface/view")
if err != nil {
return data, fmt.Errorf("请求 %s 出错:%v", r.URL(), err)
}
err = resp.JSON(&data)
return data, err
}
func (c *Client) callPlayer(aid, cid int) (data Response[PlayerResponseData], err error) {
var r = c.cli.R()
r.AddParam("aid", strconv.Itoa(aid))
r.AddParam("cid", strconv.Itoa(cid))
resp, err := r.Get("https://api.bilibili.com/x/player/v2")
if err != nil {
return data, fmt.Errorf("请求 %s 出错:%v", r.URL(), err)
}
err = resp.JSON(&data)
return data, err
}
func (c *Client) callSubtitles(url string) (data SubtitlesResponse, err error) {
var r = c.cli.R()
resp, err := r.Get(url)
if err != nil {
return data, fmt.Errorf("请求 %s 出错:%v", r.URL(), err)
}
err = resp.JSON(&data)
return data, err
}
func parseBilibiliUrl(bu string) (vid string, page int, err error) {
u, err := url.Parse(bu)
if err != nil {
return "", 0, err
}
if u.Host != "www.bilibili.com" && u.Host != "m.bilibili.com" {
return "", 0, fmt.Errorf("链接似乎不是bilibili的视频链接")
}
re := regexp.MustCompile("/video/(?P<vid>[a-zA-Z0-9]+)")
parts := re.FindStringSubmatch(u.Path)
names := re.SubexpNames()
for _, name := range names {
if name == "vid" {
vid = parts[re.SubexpIndex(name)]
}
}
if vid == "" {
return "", 0, fmt.Errorf("链接似乎不是bilibili的视频链接未获取到视频id")
}
query := u.Query()
if query.Get("p") != "" {
pageStr := query.Get("p")
page, _ = strconv.Atoi(pageStr)
}
return vid, page, nil
}

61
subtitles/data.go Normal file
View File

@ -0,0 +1,61 @@
package main
type ResponseData interface {
ViewResponseData | PlayerResponseData
}
type Response[T ResponseData] struct {
Code int `json:"code"`
Message string `json:"message"`
Ttl int `json:"ttl"`
Data T `json:"data"`
}
type ViewResponseData struct {
Bvid string `json:"bvid"`
Title string `json:"title"`
Duration int `json:"duration"`
Stat struct {
Aid int `json:"aid"`
} `json:"stat"`
Pages []struct {
Cid int `json:"cid"`
Page int `json:"page"`
From string `json:"from"`
Part string `json:"part"`
Duration int `json:"duration"`
Vid string `json:"vid"`
Weblink string `json:"weblink"`
} `json:"pages"`
}
type PlayerResponseData struct {
Aid int `json:"aid"`
Bvid string `json:"bvid"`
Cid int `json:"cid"`
PageNo int `json:"page_no"`
Subtitle struct {
Subtitles []struct {
Id int64 `json:"id"`
Lan string `json:"lan"`
LanDoc string `json:"lan_doc"`
IsLock bool `json:"is_lock"`
SubtitleUrl string `json:"subtitle_url"`
Type int `json:"type"`
IdStr string `json:"id_str"`
AiType int `json:"ai_type"`
AiStatus int `json:"ai_status"`
} `json:"subtitles"`
} `json:"subtitle"`
}
type SubtitlesResponse struct {
Body []struct {
From float64 `json:"from"`
To float64 `json:"to"`
Sid int `json:"sid"`
Location int `json:"location"`
Content string `json:"content"`
Music int `json:"music"`
} `json:"body"`
}

62
subtitles/main.go Normal file
View File

@ -0,0 +1,62 @@
package main
import (
"github.com/go-playground/validator/v10"
"github.com/gofiber/fiber/v3"
"github.com/gofiber/fiber/v3/middleware/cors"
"github.com/gofiber/fiber/v3/middleware/healthcheck"
"github.com/gofiber/fiber/v3/middleware/recover"
"os"
)
type Request struct {
Url string `json:"url" validate:"required"`
}
func main() {
sessData := os.Getenv("SESSDATA")
if sessData == "" {
panic("SESSDATA is required")
}
cli := NewClient(sessData)
s := &Service{cli: cli}
app := fiber.New(fiber.Config{
StructValidator: &structValidator{validate: validator.New()},
})
app.Use(recover.New(), cors.New())
app.Get(healthcheck.DefaultLivenessEndpoint, healthcheck.NewHealthChecker())
// Provide a minimal config for readiness check
app.Get(healthcheck.DefaultReadinessEndpoint, healthcheck.NewHealthChecker())
app.Post("/api/v1/bilibili/subtitles", s.GetSubtitles)
_ = app.Listen(":80")
}
type Service struct {
cli *Client
}
// GetSubtitles @Summary 获取字幕
// @Description 获取字幕
// @Accept application/json
// @Produce application/json
// @Param json body Request true "请求参数"
// @Router /api/v1/bilibili/subtitles [post]
func (s *Service) GetSubtitles(ctx fiber.Ctx) error {
var req Request
if err := ctx.Bind().JSON(&req); err != nil {
return err
}
data, err := s.cli.GetSubtitles(req.Url)
if err != nil {
return err
}
return ctx.JSON(fiber.Map{
"data": data,
"code": 0,
"msg": "ok",
})
}

73
subtitles/swag/docs.go Normal file
View File

@ -0,0 +1,73 @@
// Package swag Code generated by swaggo/swag. DO NOT EDIT
package swag
import "github.com/swaggo/swag"
const docTemplate = `{
"schemes": {{ marshal .Schemes }},
"swagger": "2.0",
"info": {
"description": "{{escape .Description}}",
"title": "{{.Title}}",
"contact": {},
"version": "{{.Version}}"
},
"host": "{{.Host}}",
"basePath": "{{.BasePath}}",
"paths": {
"/api/v1/bilibili/subtitles": {
"post": {
"description": "获取字幕",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"description": "请求参数",
"name": "json",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.Request"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.Request": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string"
}
}
}
}
}`
// SwaggerInfo holds exported Swagger Info so clients can modify it
var SwaggerInfo = &swag.Spec{
Version: "",
Host: "",
BasePath: "",
Schemes: []string{},
Title: "",
Description: "",
InfoInstanceName: "swagger",
SwaggerTemplate: docTemplate,
LeftDelim: "{{",
RightDelim: "}}",
}
func init() {
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
}

View File

@ -0,0 +1,44 @@
{
"swagger": "2.0",
"info": {
"contact": {}
},
"paths": {
"/api/v1/bilibili/subtitles": {
"post": {
"description": "获取字幕",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"parameters": [
{
"description": "请求参数",
"name": "json",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/main.Request"
}
}
],
"responses": {}
}
}
},
"definitions": {
"main.Request": {
"type": "object",
"required": [
"url"
],
"properties": {
"url": {
"type": "string"
}
}
}
}
}

View File

@ -0,0 +1,27 @@
definitions:
main.Request:
properties:
url:
type: string
required:
- url
type: object
info:
contact: {}
paths:
/api/v1/bilibili/subtitles:
post:
consumes:
- application/json
description: 获取字幕
parameters:
- description: 请求参数
in: body
name: json
required: true
schema:
$ref: '#/definitions/main.Request'
produces:
- application/json
responses: {}
swagger: "2.0"

15
subtitles/validator.go Normal file
View File

@ -0,0 +1,15 @@
package main
import "github.com/go-playground/validator/v10"
type structValidator struct {
validate *validator.Validate
}
func (v *structValidator) Engine() any {
return ""
}
func (v *structValidator) ValidateStruct(out any) error {
return v.validate.Struct(out)
}