Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

嵌入B站视频支持av/bv号 #287

Merged
merged 3 commits into from
Feb 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func main() {
}

migrateTo3(tx, myLogger)
migrateIframeVideoUrl(tx, myLogger)

e.HideBanner = true
err = e.Start(fmt.Sprintf(":%d", cfg.Port))
Expand Down
88 changes: 87 additions & 1 deletion backend/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import (
"encoding/json"
"errors"
"fmt"
"regexp"
"strings"

"github.com/kingwrcy/moments/db"
"github.com/kingwrcy/moments/handler"
"github.com/kingwrcy/moments/vo"
"github.com/rs/zerolog"
"github.com/tidwall/gjson"
"gorm.io/gorm"
"strings"
)

func migrateTo3(tx *gorm.DB, log zerolog.Logger) {
Expand Down Expand Up @@ -173,3 +175,87 @@ WHERE
((createdAt NOT LIKE '%-%' AND length(createdAt) = 13) OR
(updatedAt NOT LIKE '%-%' AND length(updatedAt) = 13))`)
}

func migrateIframeVideoUrl(tx *gorm.DB, log zerolog.Logger) {
var memos []db.Memo
tx.Find(&memos)

bilibiliUrlReg := regexp.MustCompile(`src=['"](?:https?:)?(?:\/)*([^'"]+)['"]`)
youtubeUrlRegList := []*regexp.Regexp{
regexp.MustCompile(`v=([^&#]+)`),
regexp.MustCompile(`youtu\.be\/([^\/\?]+)`),
}

for _, memo := range memos {
var ext vo.MemoExt
err := json.Unmarshal([]byte(memo.Ext), &ext)
if err != nil {
log.Warn().Msgf("memo id: %d 的 ext 不是标准的 json 格式 => %s", memo.Id, memo.Ext)
continue
}

// 测试数据开始
// if ext.Video.Value != "" {
// ext.Video.Type = "bilibili"
// ext.Video.Value = `<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=123&bvid=FDA1FAD&cid=123&p=1" scrolling></iframe>`
// ext.Video.Value = `//player.bilibili.com/player.html?isOutside=true&aid=123&bvid=FDA1FAD&cid=123&p=1`
// ext.Video.Value = `https://player.bilibili.com/player.html?isOutside=true&aid=123&bvid=FDA1FAD&cid=123&p=1`
// }

// if ext.Video.Value != "" {
// ext.Video.Type = "youtube"
// ext.Video.Value = "https://www.youtube.com/watch?v=hacdT_G2Ara&q=123"
// ext.Video.Value = "https://youtu.be/hacdT_G2Ara?si=aa_a_a_aaa"
// ext.Video.Value = "https://youtu.be/hacdT_G2Ara"
// ext.Video.Value = "//www.youtube.com/embed/hacdT_G2Ara"
// ext.Video.Value = "https://www.youtube.com/embed/hacdT_G2Ara"
// }
// 测试数据结束

if ext.Video.Value == "" ||
strings.HasPrefix(ext.Video.Value, "https://player.bilibili.com/player.html") ||
strings.HasPrefix(ext.Video.Value, "https://www.youtube.com/embed") {
continue
}

log.Info().Msgf("开始迁移 memo id: %d 的 %s url: %s", memo.Id, ext.Video.Type, ext.Video.Value)

if strings.HasPrefix(ext.Video.Value, "//") {
ext.Video.Value = fmt.Sprintf("https:%s", ext.Video.Value)
} else if strings.HasPrefix(ext.Video.Value, "http://") {
ext.Video.Value = strings.Replace(ext.Video.Value, "http://", "https://", 1)
} else if ext.Video.Type == "bilibili" {
matchResult := bilibiliUrlReg.FindStringSubmatch(ext.Video.Value)
if matchResult == nil {
continue
}

ext.Video.Value = fmt.Sprintf(`https://%s`, matchResult[1])
} else if ext.Video.Type == "youtube" {
for _, youtubeUrlReg := range youtubeUrlRegList {
matchResult := youtubeUrlReg.FindStringSubmatch(ext.Video.Value)
if matchResult == nil {
continue
}

ext.Video.Value = fmt.Sprintf(
`https://www.youtube.com/embed/%s`,
matchResult[1],
)
break
}
} else {
log.Info().Msgf("视频地址无需迁移")
continue
}

log.Info().Msgf("迁移后的 url: %s", ext.Video.Value)
extContent, _ := json.Marshal(ext)
memo.Ext = string(extContent)
if err = tx.Save(&memo).Error; err == nil {
log.Info().Msgf("迁移 memo id: %d 成功", memo.Id)
} else {
log.Error().Msgf("迁移 memo id: %d 失败, 原因:%v", memo.Id, err)
}
}
}
22 changes: 0 additions & 22 deletions front/components/BilibiliPreview.vue

This file was deleted.

8 changes: 2 additions & 6 deletions front/components/Memo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,8 @@
<music-preview v-if="extJSON.music && extJSON.music.id" v-bind="extJSON.music"/>
<douban-book-preview v-if="extJSON.doubanBook && extJSON.doubanBook.title" :book="extJSON.doubanBook"/>
<douban-movie-preview v-if="extJSON.doubanMovie && extJSON.doubanMovie.title" :movie="extJSON.doubanMovie"/>
<youtube-preview v-if="extJSON.video && extJSON.video.type === 'youtube' && extJSON.video.value"
:url="extJSON.video.value"/>
<bilibili-preview v-if="extJSON.video && extJSON.video.type === 'bilibili' && extJSON.video.value"
:url="extJSON.video.value"/>
<video-preview v-if="extJSON.video && extJSON.video.type === 'online' && extJSON.video.value"
:url="extJSON.video.value"/>
<video-preview-iframe v-if="extJSON.video && ['bilibili', 'youtube'].includes(extJSON.video.type) && extJSON.video.value" :url="extJSON.video.value"/>
<video-preview v-if="extJSON.video && extJSON.video.type === 'online' && extJSON.video.value" :url="extJSON.video.value"/>
</div>

<div class="text-[#576b95] font-medium dark:text-white text-xs mt-2 mb-1 select-none flex items-center gap-0.5"
Expand Down
3 changes: 1 addition & 2 deletions front/components/MemoEdit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,7 @@
v-bind="state.music"/>
<douban-book-preview :book="doubanData" v-if="doubanType === 'book' && doubanData&& doubanData.title"/>
<douban-movie-preview :movie="doubanData" v-if="doubanType === 'movie' && doubanData&& doubanData.title"/>
<youtube-preview v-if="state.video.type === 'youtube' && state.video.value" :url="state.video.value"/>
<bilibili-preview v-if="state.video.type === 'bilibili' && state.video.value" :url="state.video.value"/>
<video-preview-iframe v-if="['bilibili', 'youtube'].includes(state.video.type) && state.video.value" :url="state.video.value"/>
<video-preview v-if="state.video.type === 'online' && state.video.value" :url="state.video.value"/>
</div>
</div>
Expand Down
142 changes: 98 additions & 44 deletions front/components/UploadVideo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -51,32 +51,32 @@

<UButtonGroup>
<UButton @click="confirm(close)">确定</UButton>
<UButton color="white" @click="reset(close)">清空</UButton>
<UButton color="white" @click="reset()">清空</UButton>
</UButtonGroup>
</div>
</template>
</UPopover>
</template>

<script setup lang="ts">
import type {Video, VideoType} from "~/types";
import {toast} from "vue-sonner";
import {useUpload} from "~/utils";
import type { Video, VideoType } from "~/types";
import { toast } from "vue-sonner";
import { useUpload } from "~/utils";

const props = withDefaults(defineProps<Video>(), {
type: "youtube",
value: ""
})
const emit = defineEmits(['confirm'])
const videoType = ref<VideoType>(props.type)

const youtubeUrl = ref('')
const bilibiliUrl = ref('')
const onlineUrl = ref('')
const youtubeUrlRegs = [/v=([^&#]+)/, /youtu\.be\/(.*)\?/]
const progress = ref(0)
const filename = ref('')
const total = ref(0)
const current = ref(0)

const items = [{
slot: 'uploadVideo',
label: '本地'
Expand All @@ -85,8 +85,36 @@ const items = [{
label: '在线'
}]

const youtubeUrlTemplateList = [
{
reg: /src=['"](?:https?:)?(?:\/)*([^'"]+)['"]/,
template: 'https://@{placeholder}',
},
{
reg: /v=([^&#]+)/,
template: 'https://www.youtube.com/embed/@{placeholder}',
},
{
reg: /youtu\.be\/([^\/\?]+)/,
template: 'https://www.youtube.com/embed/@{placeholder}',
},
]
const bilibiliUrlTemplateList = [
{
reg: /src=['"](?:https?:)?(?:\/)*([^'"]+)['"]/,
template: 'https://@{placeholder}',
},
{
reg: /av(\d+)/i,
template: 'https://player.bilibili.com/player.html?aid=@{placeholder}',
},
{
reg: /(bv[\w]+)/i,
template: 'https://player.bilibili.com/player.html?bvid=@{placeholder}',
},
]

watch(props, () => {
videoType.value = props.type
if (props.type === 'youtube') {
youtubeUrl.value = props.value
} else if (props.type === 'bilibili') {
Expand All @@ -98,7 +126,7 @@ watch(props, () => {

const handleUploadVideo = async (files: FileList) => {
for (let i = 0; i < files.length; i++) {
if (files[i].type.indexOf("video") < 0){
if (files[i].type.indexOf("video") < 0) {
toast.error("只能上传视频文件");
return
}
Expand All @@ -114,69 +142,95 @@ const handleUploadVideo = async (files: FileList) => {
onlineUrl.value = result[0]
}
}

const emitUrl = (type: VideoType, value: string) => {
if (type !== 'youtube') {
youtubeUrl.value = ''
}

if (type !== 'bilibili') {
bilibiliUrl.value = ''
}

if (type !== 'online') {
onlineUrl.value = ''
}

emit('confirm', {
type,
value,
})
}

const confirm = (close: Function) => {
const match = bilibiliUrl.value.match(/src=['"]([^'"]+)['"]/)
if (bilibiliUrl.value.trim() && youtubeUrl.value.trim()) {
toast.warning("请勿同时填写两个地址")
return
}

if (bilibiliUrl.value.trim()) {
videoType.value = 'bilibili'
if (match && match.length > 1) {
emit('confirm', {
type: videoType.value,
value: bilibiliUrl.value
})
if (bilibiliUrl.value.startsWith('https://player.bilibili.com/player.html')) {
emitUrl('bilibili', bilibiliUrl.value)
close()
} else {
toast.warning("无效的B站视频地址")
return
}

for (const bilibiliUrlTemplate of bilibiliUrlTemplateList) {
const { reg, template } = bilibiliUrlTemplate
const [_, matchedValue] = bilibiliUrl.value.match(reg) || []
if (matchedValue) {
const url = template.replace('@{placeholder}', matchedValue)
emitUrl('bilibili', url)
close()
return
}
}

toast.warning("无效的B站视频地址")
return
}

if (youtubeUrl.value.trim()) {
videoType.value = 'youtube'
let success = false
for (let i = 0; i < youtubeUrlRegs.length; i++) {
const match = youtubeUrl.value.match(youtubeUrlRegs[i])
if (match && match.length > 1) {
success = true
break
}
}
if (success) {
emit('confirm', {
type: videoType.value,
value: youtubeUrl.value
})
if (youtubeUrl.value.startsWith('https://www.youtube.com/embed')) {
emitUrl('youtube', youtubeUrl.value)
close()
} else {
toast.warning("无效的Youtube视频地址")
return
}

for (const youtubeUrlTemplate of youtubeUrlTemplateList) {
const { reg, template } = youtubeUrlTemplate
const [_, matchedValue] = youtubeUrl.value.match(reg) || []
if (matchedValue) {
const url = template.replace('@{placeholder}', matchedValue)
emitUrl('youtube', url)
close()
return
}
}

toast.warning("无效的Youtube视频地址")
return
}

if (onlineUrl.value.trim()) {
videoType.value = 'online'
emit('confirm', {
type: videoType.value,
value: onlineUrl.value.trim()
})
emitUrl('online', onlineUrl.value.trim())
close()
return
}
}

const reset = (close: Function) => {
videoType.value = 'youtube'
const reset = () => {
youtubeUrl.value = ''
bilibiliUrl.value = ''
onlineUrl.value = ''

emit('confirm', {
type: videoType.value,
type: 'youtube',
value: ""
})
}


</script>

<style scoped>

</style>
<style scoped></style>
Loading