From ac70cca4a266bbb9d1e1a580993f928eedf170b6 Mon Sep 17 00:00:00 2001 From: ApplePine Date: Sat, 21 Jun 2025 03:29:14 +0800 Subject: [PATCH] release(project): publish project --- .gitignore | 18 + .prettierignore | 4 + .prettierrc.json | 60 +++ README.md | 12 + eslint.config.mjs | 15 + free-video-backend/.gitignore | 12 + free-video-backend/.prettierrc.json | 60 +++ free-video-backend/package.json | 15 + free-video-backend/src/index.ts | 72 ++++ free-video-backend/tsconfig.json | 11 + package.json | 32 ++ rsbuild.config.mjs | 17 + src/App.vue | 7 + src/index.js | 16 + src/router/index.js | 36 ++ src/store/index.js | 82 ++++ src/styles/global.css | 20 + src/views/VideoPlayer.vue | 426 +++++++++++++++++++ src/views/VideoSearch.vue | 639 ++++++++++++++++++++++++++++ 19 files changed, 1554 insertions(+) create mode 100644 .gitignore create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 README.md create mode 100644 eslint.config.mjs create mode 100644 free-video-backend/.gitignore create mode 100644 free-video-backend/.prettierrc.json create mode 100644 free-video-backend/package.json create mode 100644 free-video-backend/src/index.ts create mode 100644 free-video-backend/tsconfig.json create mode 100644 package.json create mode 100644 rsbuild.config.mjs create mode 100644 src/App.vue create mode 100644 src/index.js create mode 100644 src/router/index.js create mode 100644 src/store/index.js create mode 100644 src/styles/global.css create mode 100644 src/views/VideoPlayer.vue create mode 100644 src/views/VideoSearch.vue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d1ecbab --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +# Local +.DS_Store +*.local +*.log* + +# Dist +node_modules +dist/ + +# Profile +.rspack-profile-*/ + +# IDE +.vscode/* +.vscode/ +!.vscode/extensions.json +.idea +bun.lock diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..ac66857 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,4 @@ +# Lock files +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..063e288 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,60 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": true, + "semi": false, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf", + "proseWrap": "always", + "htmlWhitespaceSensitivity": "css", + "bracketSameLine": false, + "vueIndentScriptAndStyle": false, + "embeddedLanguageFormatting": "auto", + "jsxSingleQuote": true, + "overrides": [ + { + "files": "*.{json,json5}", + "options": { + "printWidth": 100, + "singleQuote": false, + "trailingComma": "none" + } + }, + { + "files": "*.{yaml,yml}", + "options": { + "singleQuote": false, + "tabWidth": 2 + } + }, + { + "files": "*.html", + "options": { + "printWidth": 120, + "htmlWhitespaceSensitivity": "ignore" + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "always", + "printWidth": 100 + } + }, + { + "files": "*.sql", + "options": { + "printWidth": 100, + "keywordCase": "upper", + "identifierCase": "lower", + "linesBetweenQueries": 1, + "useTabs": true, + "tabWidth": 4 + } + } + ] +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c677fd --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +## 免责声明 + +本项目仅供学习和技术研究使用,不得用于商业目的。使用本工具时,请遵守以下规定: + +1. 本工具仅提供技术演示,不提供任何视频内容本身,不提供任何视频接口 +2. 使用者应当遵守当地法律法规,不得利用本工具从事任何违法活动 +3. 本工具不存储、复制或分发任何受版权保护的内容 +4. 对于使用本工具可能引发的法律问题,使用者须自行承担全部责任 +5. 开发者保留对本项目进行修改、更新或终止的权利,且不承担任何责任 +6. 使用本工具即表示您已阅读并同意本免责声明的全部内容 + +请尊重知识产权,支持正版内容。若权利人认为本项目侵犯了您的权益,请联系开发者进行处理。 \ No newline at end of file diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..04c0b47 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,15 @@ +import js from '@eslint/js'; +import pluginVue from 'eslint-plugin-vue'; +import { defineConfig, globalIgnores } from 'eslint/config'; +import globals from 'globals'; + +export default defineConfig([ + { + name: 'app/files-to-lint', + files: ['**/*.{js,mjs,jsx,vue}'], + }, + globalIgnores(['**/dist/**', '**/dist-ssr/**', '**/coverage/**']), + { languageOptions: { globals: globals.browser } }, + js.configs.recommended, + ...pluginVue.configs['flat/essential'], +]); diff --git a/free-video-backend/.gitignore b/free-video-backend/.gitignore new file mode 100644 index 0000000..05e61ec --- /dev/null +++ b/free-video-backend/.gitignore @@ -0,0 +1,12 @@ +# deps +node_modules/ + +# ide +.vscode/ +.idea/ +.zed/ + +# lock file +bun.lock +package-lock.json + diff --git a/free-video-backend/.prettierrc.json b/free-video-backend/.prettierrc.json new file mode 100644 index 0000000..063e288 --- /dev/null +++ b/free-video-backend/.prettierrc.json @@ -0,0 +1,60 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "useTabs": true, + "semi": false, + "singleQuote": true, + "quoteProps": "as-needed", + "trailingComma": "all", + "bracketSpacing": true, + "arrowParens": "always", + "endOfLine": "lf", + "proseWrap": "always", + "htmlWhitespaceSensitivity": "css", + "bracketSameLine": false, + "vueIndentScriptAndStyle": false, + "embeddedLanguageFormatting": "auto", + "jsxSingleQuote": true, + "overrides": [ + { + "files": "*.{json,json5}", + "options": { + "printWidth": 100, + "singleQuote": false, + "trailingComma": "none" + } + }, + { + "files": "*.{yaml,yml}", + "options": { + "singleQuote": false, + "tabWidth": 2 + } + }, + { + "files": "*.html", + "options": { + "printWidth": 120, + "htmlWhitespaceSensitivity": "ignore" + } + }, + { + "files": "*.md", + "options": { + "proseWrap": "always", + "printWidth": 100 + } + }, + { + "files": "*.sql", + "options": { + "printWidth": 100, + "keywordCase": "upper", + "identifierCase": "lower", + "linesBetweenQueries": 1, + "useTabs": true, + "tabWidth": 4 + } + } + ] +} diff --git a/free-video-backend/package.json b/free-video-backend/package.json new file mode 100644 index 0000000..2fb7d89 --- /dev/null +++ b/free-video-backend/package.json @@ -0,0 +1,15 @@ +{ + "name": "free_backend", + "scripts": { + "dev": "bun run --hot src/index.ts" + }, + "dependencies": { + "axios": "^1.10.0", + "hono": "^4.8.1" + }, + "devDependencies": { + "@types/bun": "latest", + "eslint": "^9.29.0", + "prettier": "^3.5.3" + } +} \ No newline at end of file diff --git a/free-video-backend/src/index.ts b/free-video-backend/src/index.ts new file mode 100644 index 0000000..e0ab116 --- /dev/null +++ b/free-video-backend/src/index.ts @@ -0,0 +1,72 @@ +import { Hono } from 'hono' +import { cors } from 'hono/cors' +import axios from 'axios' + +const app = new Hono() + +app.use('*', cors()) + +app.get('/api/searchvideo', async (c) => { + const url_prefix = 'https://api.so.360kan.com/index?force_v=1&kw=' + const keyword = c.req.query('keyword') || '' + const url = url_prefix + keyword + try { + const response = await axios.get(url, { + headers: { + 'User-Agent': + 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/137.0.0.0 Mobile Safari/537.36', + }, + }) + const rows = response.data.data.longData.rows || [] + if (rows.length === 0) { + return c.json({ error: 'No results found' }, 404) + } + const promises = rows.map(async (item: any) => { + let videoLinks = [] + if (!item.seriesPlaylinks || item.seriesPlaylinks.length === 0) { + const videoResponse = await axios.get( + `https://api.so.360kan.com/episodesv2?v_ap=1&s=[{"cat_id":"${item.cat_id}","ent_id":"${item.en_id}","site":"${item.vipSite[0]}"}]`, + ) + if (videoResponse.data.data.length === 0) { + return + } + videoLinks = videoResponse.data.data[0].seriesHTML.seriesPlaylinks.map((video: any) => { + return video.url.split('?')[0] + }) + } else { + videoLinks = item.seriesPlaylinks.map((video: any) => { + if (typeof video.url === 'string') { + return video.url.split('?')[0] + } + return video + }) + } + const videoItem = { + video_id: item.id, + video_id_en: item.en_id, + cat_id: item.cat_id, + cat_name: item.cat_name, + cover: item.cover, + title: item.titleTxt, + year: item.year, + description: item.description, + areas: item.area, + tags: item.tag, + actList: item.actList, + dirList: item.dirList, + isVip: item.vip, + vipSites: item.vipSite, + videoLinks: videoLinks, + episodeCount: videoLinks.length, + } + return videoItem + }) + const res = (await Promise.all(promises)).filter(Boolean) + return c.json(res) + } catch (e) { + console.error(e) + return c.json({ error: 'Failed to fetch data' }, 500) + } +}) + +export default app diff --git a/free-video-backend/tsconfig.json b/free-video-backend/tsconfig.json new file mode 100644 index 0000000..3fe4a2f --- /dev/null +++ b/free-video-backend/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "nodenext", + "moduleResolution": "nodenext", + "strict": true, + "jsx": "react-jsx", + "jsxImportSource": "hono/jsx", + "baseUrl": "." + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..860474e --- /dev/null +++ b/package.json @@ -0,0 +1,32 @@ +{ + "name": "free-video-app", + "version": "1.0.0", + "description": "视频解析工具,支持多种视频网站", + "main": "electron/main.js", + "author": "Jianlong Deng", + "private": true, + "type": "module", + "scripts": { + "build": "rsbuild build", + "dev": "rsbuild dev --open", + "format": "prettier --write .", + "lint": "eslint .", + "preview": "rsbuild preview" + }, + "dependencies": { + "axios": "^1.10.0", + "element-plus": "^2.10.2", + "pinia": "^3.0.3", + "vue": "^3.5.16", + "vue-router": "4" + }, + "devDependencies": { + "@eslint/js": "^9.25.1", + "@rsbuild/core": "^1.3.22", + "@rsbuild/plugin-vue": "^1.0.7", + "eslint": "^9.25.1", + "eslint-plugin-vue": "^10.1.0", + "globals": "^16.0.0", + "prettier": "^3.5.3" + } +} diff --git a/rsbuild.config.mjs b/rsbuild.config.mjs new file mode 100644 index 0000000..aa477e7 --- /dev/null +++ b/rsbuild.config.mjs @@ -0,0 +1,17 @@ +import { defineConfig } from '@rsbuild/core' +import { pluginVue } from '@rsbuild/plugin-vue' + +export default defineConfig({ + plugins: [pluginVue()], + server: { + port: 8080, + open: true, + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + secure: false, + }, + }, + }, +}) diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..0f29977 --- /dev/null +++ b/src/App.vue @@ -0,0 +1,7 @@ + + + + + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..e14080f --- /dev/null +++ b/src/index.js @@ -0,0 +1,16 @@ +import { createApp } from 'vue' +import { createPinia } from 'pinia' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import './styles/global.css' +import App from './App.vue' + +const app = createApp(App) +const pinia = createPinia() +app.use(pinia) + +import router from './router/index.js' +app.use(ElementPlus, { zIndex: 1000 }) +app.use(router) + +app.mount('#root') diff --git a/src/router/index.js b/src/router/index.js new file mode 100644 index 0000000..f4514c2 --- /dev/null +++ b/src/router/index.js @@ -0,0 +1,36 @@ +import { createRouter, createWebHistory } from 'vue-router' +import VideoSearch from '../views/VideoSearch.vue' +import VideoPlayer from '../views/VideoPlayer.vue' + +const routes = [ + { + path: '/', + name: 'Home', + component: VideoSearch, + meta: { + title: '免费视频解析 - 支持全网视频解析,免费观看VIP视频', + }, + }, + { + path: '/video_player', + name: 'VideoPlayer', + component: VideoPlayer, + meta: { + title: '免费视频解析 - 视频播放器', + }, + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +// 全局前置守卫 +router.beforeEach((to, _from, next) => { + const title = to.meta.title || '免费视频解析' + document.title = title + next() +}) + +export default router diff --git a/src/store/index.js b/src/store/index.js new file mode 100644 index 0000000..358c6fc --- /dev/null +++ b/src/store/index.js @@ -0,0 +1,82 @@ +import { defineStore } from 'pinia' + +export const useStore = defineStore('main', { + state: () => { + return { + prefix_urls: [''], + search_results: [], + current_video: { + title: '', // 视频标题 + episodeIdx: 0, // 当前集数索引 + episodeUrl: '', // 当前播放地址 + totalEpisodes: 0, // 总集数 + coverUrl: '', // 封面 + description: '', // 描述 + videoLinks: [], // 所有剧集的链接 + }, + supportedPlatforms: ['优酷', '爱奇艺', '腾讯视频'], + supportedFormats: ['MP4', 'M3U8', 'FLV', '...'], + apiConfig: { + timeout: 10000, // 请求超时时间(毫秒) + retryTimes: 3, // 重试次数 + }, + appConfig: { + title: '免费视频解析', + description: '支持全网视频解析,免费观看VIP视频', + version: '1.0.0', + site_begin_time: new Date('2025-06-20 18:00:00'), + }, + } + }, + actions: { + // 更新搜索结果 + updateSearchResults(results) { + this.search_results = results + }, + + // 更新当前视频信息 - 综合方法,处理不同情况 + updateCurrentVideo(videoData, episodeIdx = 0, episodeUrl = '') { + // 处理直接传入URL字符串的情况 + if (typeof videoData === 'string') { + this.current_video = { + title: '外部链接', + episodeIdx: 0, + episodeUrl: videoData, + totalEpisodes: 1, + coverUrl: '', + description: '', + videoLinks: [videoData], + } + return + } + + // 处理传入视频对象的情况 + const videoLinks = videoData.videoLinks || [] + + this.current_video = { + title: videoData.title || '未知标题', + episodeIdx: episodeIdx, + episodeUrl: episodeUrl || videoLinks[episodeIdx] || '', + totalEpisodes: videoLinks.length || 0, + coverUrl: videoData.cover || '', + description: videoData.description || '', + videoLinks: videoLinks, + } + }, + + // 仅更新当前播放URL + updateEpisodeUrl(url) { + if (!url) return + this.current_video.episodeUrl = url + }, + + // 更新当前播放集数 + updateEpisodeIdx(idx) { + if (idx < 0 || !this.current_video.videoLinks || idx >= this.current_video.videoLinks.length) + return + + this.current_video.episodeIdx = idx + this.current_video.episodeUrl = this.current_video.videoLinks[idx] + }, + }, +}) diff --git a/src/styles/global.css b/src/styles/global.css new file mode 100644 index 0000000..a451e95 --- /dev/null +++ b/src/styles/global.css @@ -0,0 +1,20 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; + font-family: 'Arial', sans-serif; + background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%); + overflow-x: hidden; + height: 100%; +} + +#root { + width: 100%; + height: 100vh; +} diff --git a/src/views/VideoPlayer.vue b/src/views/VideoPlayer.vue new file mode 100644 index 0000000..5c00b15 --- /dev/null +++ b/src/views/VideoPlayer.vue @@ -0,0 +1,426 @@ + + + + + diff --git a/src/views/VideoSearch.vue b/src/views/VideoSearch.vue new file mode 100644 index 0000000..4d85771 --- /dev/null +++ b/src/views/VideoSearch.vue @@ -0,0 +1,639 @@ + + + + +