Debian10 上安装部分应用,速度几乎为0,至少需要Debian11以上,512M内存足够。
安装 Docker 1.首先,更新现有的软件包列表:
2.接下来,安装一些必备软件包,让 apt 通过 HTTPS 使用软件包。
1 sudo apt install apt-transport-https ca-certificates curl gnupg2 software-properties-common -y
3.然后将官方 Docker hub 的 GPG key 添加到系统中。
1 curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -
4.将 Docker 版本库添加到APT源:
1 sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"
5.接下来,我们用新添加的 Docker 软件包来进行升级更新。
6.安装 Docker
1 sudo apt install docker-ce -y
7.检查 Docker 是否正在运行
1 2 docker --version sudo systemctl status docker
8.重启 docker 并设置开机自启
1 2 3 sudo systemctl enable docker sudo systemctl daemon-reload sudo systemctl restart docker
9.修改Docker配置(来自烧饼博客 )
以下配置会增加一段自定义内网 IPv6 地址,开启容器的 IPv6 功能,以及限制日志文件大小,防止 Docker 日志塞满硬盘(泪的教训)
1 2 3 4 5 6 7 8 9 10 11 12 13 cat > /etc/docker/daemon.json <<EOF { "log-driver": "json-file", "log-opts": { "max-size": "20m", "max-file": "3" }, "ipv6": true, "fixed-cidr-v6": "fd00:dead:beef:c0::/80", "experimental":true, "ip6tables":true } EOF
然后重启 Docker 服务:
1 systemctl restart docker
在 Docker 中使用镜像 1.要查看已下载到计算机的镜像:
2.删除某个docker镜像
1 docker rmi <your-image-id>
3.一次删除多张镜像
1 docker rmi <your-image-id> <your-image-id> ...
4.一次删除所有镜像
1 docker rmi $(docker images -q)
在 Docker 中使用容器 1.要查看所有 的容器对象,请使用:
docker ps -a -q 分解
docker ps 列出活动 中容器。
-a 这个选项用于列出所有 容器,包括停止运行的。如果没有这个选项,则默认只列出在运行的容器。
-q 这个选项列出容器的数字 ID,而不是容器的所有信息。
2.要启动已停止的容器,请使用docker start命令+容器ID或容器名
停止所有容器运行:docker stop $(docker ps -a -q)
3.通过docker rm命令来删除不用的容器。
先使用docker ps -a命令查找相关镜像关联的容器的容器ID或名称 ,然后通过docker rm命令来删除其删除。
删除所有停止运行的容器:docker rm $(docker ps -a -q)
Docker 容器开机自启 1.在使用docker run启动容器时,使用–restart参数来设置:
1 docker run -m 512m --memory-swap 1G -it -p 58080:8080 --restart=always
2.如果创建时未指定 –restart=always ,可通过update 命令设置
1 docker update --restart=always 容器ID或名称
安装 Docker Compose 1.安装
1 2 3 4 export LATEST_VERSION=$(wget -qO- -t1 -T2 "https://api.github.com/repos/docker/compose/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g' )sudo curl -L https://github.com/docker/compose/releases/download/$LATEST_VERSION /docker-compose-linux-`uname -m` > ./docker-compose sudo chmod +x ./docker-compose sudo mv ./docker-compose /usr/local/bin/docker-compose
2.查看版本
1 docker-compose --version
3.使用 -d 选项以分离模式启动 Compose(后台)
4.要查看正在运行的 docker 容器,请使用以下命令
5.删除容器
1 2 3 4 5 cd /root/docker/tv docker-compose stop docker-compose down cd .. rm -rf /root/docker/tv
6.一些 Docker Compose 常用命令:
1 2 3 4 5 docker-compose restart # 重启容器 docker-compose stop # 暂停容器 docker-compose down # 删除容器 docker-compose pull # 更新镜像 docker-compose exec artalk bash # 进入容器
7.Docker Compose升级
拉取最新镜像,然后重新创建容器即可。
1 2 3 docker-compose pull docker-compose up -d docker image prune
8.使用 Watchtower 自动更新
Watchtower 可自动检测并更新 Docker 容器到最新镜像。
1 2 3 4 5 6 7 8 services: watchtower: image: containrrr/watchtower container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 86400 --cleanup restart: always
开机自动启动应用容器 1.方法一、通过 Docker Restart Policy 方法
在 Docker 中,支持 –restart 选项,来控制容器自动启动。在 Docker Compose 中,应该使用 restart 属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 version: '2' services: database: build: ./mysql/ command: mysqld --user=root --verbose + restart: always environment: MYSQL_DATABASE: "web_level3_sqli" MYSQL_USER: "web_level3_sqli" MYSQL_PASSWORD: "thisisasecurepassword123" MYSQL_ROOT_PASSWORD: "root" MYSQL_ALLOW_EMPTY_PASSWORD: "yes" web: build: ./www/ + restart: always ports: - "12000:80" volumes: - ./www/src:/var/www/html links: - database
注意事项: 1)Docker 并不知道这些服务的依赖关系及启动顺序,需要我们精心编排 docker-compose.yaml 文件; 2)Docker Compose 不支持 deploy:restart_policy 属性,该属性只能用于 a swarm with docker stack deploy 环境;
2.方法二、通过进程管理服务(推荐)
该方法本质上还是在执行 docker-compose 命令。
使用 systemd 管理 如下示例,可以根据需要进行设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [Unit] Description=Docker Compose Application Service Requires=docker.service After=docker.service [Service] Type=oneshot RemainAfterExit=yes WorkingDirectory=/srv/docker/app/ ExecStart=/usr/local/bin/docker-compose up -d ExecStop=/usr/local/bin/docker-compose down TimeoutStartSec=0 [Install] WantedBy=multi-user.target
7.卸载 Docker Compose
1 sudo rm /usr/local/bin/docker-compose
Docker Compose 部署tv 参考:Memos Docker-Compose部署
创建 tv 工作目录 1 2 3 mkdir -p /root/docker/tv cd /root/docker/tv vi docker-compose.yml
编写 docker-compose.yml 文件: 注意更改 - PASSWORD=你的密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 services: decotv-core: image: ghcr.io/decohererk/decotv:latest container_name: decotv-core restart: always ports: - '3000:3000' environment: - USERNAME=admin - PASSWORD=你的密码 - NEXT_PUBLIC_STORAGE_TYPE=kvrocks - KVROCKS_URL=redis://decotv-kvrocks:6666 - NEXT_PUBLIC_DISABLE_YELLOW_FILTER=false networks: - decotv-network depends_on: - decotv-kvrocks decotv-kvrocks: image: apache/kvrocks container_name: decotv-kvrocks restart: always volumes: - kvrocks-data:/var/lib/kvrocks networks: - decotv-network watchtower: image: containrrr/watchtower container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 86400 --cleanup restart: always networks: decotv-network: driver: bridge volumes: kvrocks-data:
执行命令 tv 后端程序将运行在 http://localhost:端口号
配置域名访问 参考:域名访问
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 server { listen 80 ; server_name tv.bore.vip; return 301 https://$host$request_uri; } server { listen 443 ssl http2; server_name tv.bore.vip; root /data/wwwroot/tv.bore.vip; ssl_certificate /etc/letsencrypt/live/tv.bore.vip/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/tv.bore.vip/privkey.pem; ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_timeout 10m; add_header Strict-Transport-Security "max-age=31536000" ; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header REMOTE-HOST $remote_addr; add_header X-Cache $upstream_cache_status; add_header Cache-Control no -cache; expires 12h; } location ^~ /.well-known/acme-challenge/ { default_type "text/plain" ; allow all; root /data/wwwroot/tv.bore.vip/; } }
视频源配置
精简版见:LunaTV-config
更新镜像 注意:更新前最好在后台先备份数据
1 2 3 4 cd /root/docker/tv docker-compose pull docker-compose up -d docker image prune -f
PS:是否需要 docker-compose down?(问chatgpt)
一般 不需要 ,除非:
改过网络、volume、端口等会冲突的配置
某些服务需要完全重建
想彻底清理旧容器
如果你只是更新镜像 → 重启服务,那么 不加 down 是正确的 。
自定义tv fork仓库 这一步简单,fork后可克隆到本地修改代码,修改完成后提交,注意先添加本地SSH公钥 到仓库,参考:配置 SSH 公钥
克隆:
1 git clone https://github.com/iwyang/DecoTV.git
修改后提交:
1 2 3 git add . git commit -m "更新" git push origin main
github退回旧版本
如果你想彻底回退到 ba82571f837d9e0a7abf1d667c9354543e87c130,并删除该提交之后的所有历史记录,使用以下命令:
1 git reset --hard ba82571f837d9e0a7abf1d667c9354543e87c130
注意 :这会丢失所有在目标提交之后的更改,包括工作区和暂存区的内容。
1 git push origin main --force
服务器拉取最新镜像:
1 2 3 4 cd /root/docker/tv docker-compose pull docker-compose up -d docker image prune -f
创建 github 访问 token
在任何页面的右上角,单击个人资料照片,然后单击 “设置”。
在左侧边栏中,单击 “开发人员设置”。
请在左侧边栏的 “Personal access token” 下,单击 “细粒度令牌” 。
单击 “生成新令牌”。
在 “令牌名称” 下,输入令牌的名称。
在 “过期时间” 下,选择令牌的过期时间(永不过期)。
然后权限要开启 repo 和 workflow 和write:package s以及read:packages 的权限
添加环境变量 secret 在 settings/secrets(Secrets and variables)/actions 里把 Github 的 Token 设置上,比如 workflow 写的是secrets.GHCR_TOKEN,所以添加的时候 Name 填写 GHCR_TOKEN,Secret 里填写上一步创建 Token 内容。
修改docker-image.yml 把.github/workflows/docker-image.yml中的secrets.GITHUB_TOKEN 改成secrets.GHCR_TOKEN,大概有三处要改。GitHub actions成功运行后就可以创建自己的镜像。
修改docker-compose.yml 文件 注意修改自己的密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 services: decotv-core: image: ghcr.io/iwyang/decotv:latest container_name: decotv-core restart: always ports: - '3000:3000' environment: - USERNAME=admin - PASSWORD=你的密码 - NEXT_PUBLIC_STORAGE_TYPE=kvrocks - KVROCKS_URL=redis://decotv-kvrocks:6666 - NEXT_PUBLIC_DISABLE_YELLOW_FILTER=false networks: - decotv-network depends_on: - decotv-kvrocks decotv-kvrocks: image: apache/kvrocks container_name: decotv-kvrocks restart: always volumes: - kvrocks-data:/var/lib/kvrocks networks: - decotv-network watchtower: image: containrrr/watchtower container_name: watchtower volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 86400 --cleanup restart: always networks: decotv-network: driver: bridge volumes: kvrocks-data:
修改TV logo和PWA启动图片
logo在根目录public文件夹下,favicon.ico和logo.png
pwa启动图片在根目录public\icons文件夹下(根目录scripts\generate-manifest.js文件可查看)
douban页面隐藏数据源选择器 1.提问grok (附件添加src /app /douban /page.tsx):
怎样修改,才能隐藏 :数据源(30) 聚合🎬-爱qiyi……
2.接着追问:能给我修改好的完整文件吗(grok发的代码经常不完整,导致部署出错)
3.如果部署报错,下载日志文件给它分析:**点击Explain error右边的小齿轮选择Download logs arichive**,下载日志文件,找到失败那一步,复制错误日志给ai分析
修改完成效果:
“数据源(30)” 那一整行完全消失
不再加载任何第三方 CMS 源
所有与 useSourceFilter、sourceData、filteredSourceCategories 等相关代码已删除
页面只显示豆瓣原生内容或 Bangumi 每日放送,体验更纯粹、加载更快
直接替换原来的 page.tsx 即可。祝使用愉快!
隐藏源指定分类 方法如上,还是用grok ,提问:
默认此页面会显示源的所有分类,帮我修改代码,实现以下过滤规则:
如果源名称包含 “艾旦影视” → 完全隐藏所有分类(不显示任何分类按钮)
其他所有源 → 自动隐藏以下敏感分类,如福利、伦理、里番动画等
给我完整代码,不要省略代码
注意:会省略代码,要反复确认是否是完整代码,有没有省略,尤其是部署失败的时候 ,也可下载日志文件,找到失败那一步,复制错误日志给ai分析
搜索屏蔽指定违禁词 方法如上,还是用grok ,上传/src/app/api/search下面的5个路由文件,提问 :
怎样修改才能实现: 1.搜索违禁词时,直接跳过搜索,返回空结果。
2.当搜索结果含有违禁词时,屏蔽显示该结果
3.新建一个 filter.ts,专门设置违禁词,其他 4 个文件引用就行
给我完整代码,不要省略代码,文件开头注释修改的是哪个文件。
注意:会省略代码,要反复确认是否是完整代码,有没有省略,尤其是部署失败的时候 ,也可下载日志文件,找到失败那一步,复制错误日志给ai分析
电影、动漫等页面排序 还是用grok ,上传\src\app\douban\page.tsx和\src\components\MultiLevelSelector.tsx2个文件,提问 :
我想修改代码达到以下最终效果,请帮我给出完整代码(不要省略),并解释关键改动:
【页面/功能】:Douban页面 / 排序筛选器 / 动漫分类等
【当前文件】:page.tsx / MultiLevelSelector.tsx
【想要的最终效果】(越详细越好,按优先级列点):
电影/电视剧/综艺页面默认分类是“全部”,默认排序是“近期热度”(sort = ‘U’),胶囊显示“近期热度”
动漫页面默认显示“番剧”分类,“番剧”分类和“剧场版”分类默认排序是“近期热度”(sort = ‘U’),胶囊显示“近期热度”
切换到“综合排序”时,胶囊按钮显示“排序”(灰色)
选择“近期热度”或其他非综合排序时,胶囊显示具体名称(绿色)
下拉菜单里默认高亮“近期热度”
【其他要求】(如果有):
不要改变其他筛选器的行为
保持现有加载更多、骨架屏等逻辑不变
请给出完整文件代码,不要省略
当前代码请参考附件:[拖入或贴上最新 page.tsx / MultiLevelSelector.tsx 代码]
播放源只显示标题开头的源 还是用grok ,上传yangtv‘下\src\app\play\page.tsx文件,提问 :(得到最终需要的版本后,问grok:以后要修改代码,达到这种效果的话,要怎样向你提问 )
我希望播放源过滤规则变成: 核心条件:
result.title(忽略空格、大小写)必须以主标题(videoTitle 或 searchTitle)开头
年份要匹配(如果有 year 参数)
类型处理:
如果 searchType 是 movie,允许 episodes.length <= 5 的源通过
如果 searchType 是 tv,还是要求 episodes.length > 1
如果没有 searchType,就不限制类型
请直接给出修改后的完整 filter 代码块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 const results = data.results.filter((result: SearchResult) => { if (!result.title) return false ; // 主标题处理(忽略大小写、去掉所有空格) const mainTitle = (searchTitle || videoTitleRef.current || '' ) .trim() .replace(/\s+/g , '' ) // 推荐用这个正则,更彻底去空格 .toLowerCase(); // 源标题同样处理 const sourceTitleClean = result.title .trim() .replace(/\s+/g , '' ) .toLowerCase(); // ★ 最核心条件:必须以主标题开头(最能保证相关性) const isPrefixMatch = sourceTitleClean.startsWith(mainTitle); // 年份匹配(建议保留) const yearMatch = videoYearRef.current ? (result.year || '' ).toLowerCase() === videoYearRef.current.toLowerCase() : true ; // 类型判断放宽(解决电影被标成 tv 的常见问题) const episodeCount = result.episodes?.length ?? 0 ; const isLikelyMovie = episodeCount <= 5 ; // 允许 1 ~5 集(处理分上下部、误标、长片分段等) const isLikelyTv = episodeCount > 1 ; let typeMatch = true ; if (searchType) { if (searchType === 'movie' ) { typeMatch = isLikelyMovie; // 电影要求:看起来像电影即可 } else if (searchType === 'tv' ) { typeMatch = isLikelyTv; // 电视剧还是要求多集 } } // 最终返回条件:标题开头是必须的,类型已放宽 return isPrefixMatch && yearMatch && typeMatch; });
播放源屏蔽子标题的源 还是用grok ,提问:“请修改过滤逻辑,将 mainTitle 的来源由 stitle 改为仅限 title 参数,确保源列表只显示以主标题开头的资源。”
修改前的逻辑(第 552 行左右):
1 2 3 4 5 const mainTitle = (searchTitle || videoTitleRef.current || '' ) .trim () .replace (/\s+/g , '' ) .toLowerCase ();
修改后的逻辑:
1 2 3 4 5 const mainTitle = (videoTitleRef.current || '' ) .trim () .replace (/\s+/g , '' ) .toLowerCase ();
屏蔽集数超过30%的源 还是用grok ,提问 :
我正在修改 fetchSourcesData 函数里的 filter 过滤逻辑(或者评分逻辑)。 【业务场景】 :目前在搜索 [某种类型,如:电视剧] 时,会出现 [某种问题,如:有很多集数不全的脏数据]。 【逻辑规则】 :
请帮我区分 movie 和 vod 两种类型。
如果是 vod,当 [条件,如:集数误差 > 30%] 时,执行拦截。
如果是 movie,执行 [条件,如:集数 > 5 就拦截]。 【动作目标】 :请直接在 filter 中返回 false 直接屏蔽 ,不要只在评分里降分。 【当前代码】 :[粘贴你相关的代码片段]
最终效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 总结一下,这次修改通过将“拦截逻辑”前置到搜索阶段,实现了播放源列表的**“自动精简化”**。以下是最终实现的效果: 1. 电视剧:自动过滤“残血源”与“杂质源” 众数定标: 系统会自动分析搜索结果,找出大家公认的集数(如 40 集)。 30% 强力拦截: * 屏蔽残缺源: 如果某个源只有 20 集(低于 28 集),它会从列表里直接消失。 屏蔽灌水源: 如果某个源因为带了太多花絮导致有 60 集(高于 52 集),也会被直接干掉。 结果: 你看到的电视剧源列表将非常整齐,基本都是集数达标的正片源。 2. 电影:彻底杜绝“剧版”干扰 强制 1 集基准: 电影模式下不再统计众数,默认就是 1 集。 硬拦截(> 5集): 搜电影时,凡是集数超过 5 集的(通常是同名的 30-40 集电视剧)会被瞬间过滤。 结果: 解决了“搜电影出来一堆电视剧”的痛点,列表极其干净。 3. 评分系统:回归纯粹性能比拼 纯净打分: calculateSourceScore 不再背负“惩罚误差源”的包袱,也不再需要处理业务逻辑参数。 比拼硬实力: 能够进入评分环节的都是“集数合格”的源,它们现在只纯粹地比拼 画质是否清晰(4K/1080P)、加载速度快慢 以及 网络延迟低秒。 4. 系统表现:更轻、更快、更省 节省测速资源: 那些不合格的源在第一步就被 filter 掉了,系统不会再去请求它们的 API 进行画质和速度测试。 加载提速: 减少了无效源的并行测试,整体搜索结果渲染出来的速度会更快,也节省了服务器和客户端的流量。 一句话总结: 现在的系统像加了一个智能安检门,电视剧只放行“足量”的,电影只放行“本体”,最后剩下的都是精品,你只需要在其中挑一个最快的就行。
设置暂无海报 搜索lunatv源码,查找需要修改的文件和代码位置(搜索暂无海报),\src\components\VideoCard.tsx,如果直接复制代码到yangtv相应位置,部署会报错,下载错误日志,上传VideoCard.tsx和错误日志给grok分析错误原因,没有给出正确修改方法,上传VideoCard.tsx和错误日志问gemini 得到了正确修改方法(先问grok,不行再问Gemini)。
问题在于: 你的组件中定义的状态变量名称是 isLoading 和 setIsLoading(第 82 行),但你在错误处理回调函数中却尝试调用一个不存在的 setImageLoaded 函数。
修复方案
你需要将 setImageLoaded(true) 更改为组件中实际定义的 setIsLoading(true)。
修改步骤:
打开 src/components/VideoCard.tsx。
找到第 571 行左右。
将 setImageLoaded(true); 替换为 setIsLoading(true);。
1 2 3 4 5 } else { // 重试失败,使用通用占位图 img.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="200" height="300" viewBox="0 0 200 300"%3E%3Crect fill="%23374151" width="200" height="300"/%3E%3Cg fill="%239CA3AF"%3E%3Cpath d="M100 80 L100 120 M80 100 L120 100" stroke="%239CA3AF" stroke-width="8" stroke-linecap="round"/%3E%3Crect x="60" y="140" width="80" height="100" rx="5" fill="none" stroke="%239CA3AF" stroke-width="4"/%3E%3Cpath d="M70 160 L90 180 L130 140" stroke="%239CA3AF" stroke-width="4" stroke-linecap="round" stroke-linejoin="round" fill="none"/%3E%3C/g%3E%3Ctext x="100" y="270" font-family="Arial" font-size="12" fill="%239CA3AF" text-anchor="middle"%3E暂无海报%3C/text%3E%3C/svg%3E' ; setIsLoading(true); }
为豆瓣添加2026年的筛选选项 src/components/MultiLevelSelector.tsx
1 2 3 4 5 6 7 options: [ { label: '全部', value: 'all' }, { label: '2020年代', value: '2020s' }, + { label: '2026', value: '2026' }, { label: '2025', value: '2025' }, { label: '2024', value: '2024' },
播放源排序 问gemini :
“我想修改 page.tsx 里的播放逻辑,实现‘无感知优选’。 详细逻辑:
起播策略:
有指定源: 立即起播,不显示测速加载动画。
无指定源: 允许显示短暂加载(如 2 秒),测速完成后自动播放评分最高的源。
后台逻辑: 无论哪种情况,起播后都要在后台持续完成所有源的并发测速。
UI 排序: 测速结束后,侧边栏列表按评分(速度+分辨率)降序排列。
置顶保护: 更新列表时,当前正在播放的源必须锁定在第一位,其余源按评分排序,防止 UI 闪烁或错位。 文件: [上传 page.tsx]”
禁止访问指定网页 项目根目录新建middleware.ts 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 // middleware.ts (放在项目根目录) import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; // 禁止关键词列表(支持多个) const BANNED_KEYWORDS = [ 'AAA', 'BBB', // '另一个禁止标题', ]; export function middleware(request: NextRequest) { // 直接获取 title 参数(Next.js 已自动解码汉字) const title = request.nextUrl.searchParams.get('title'); // 检查 title 是否包含禁止关键词(不区分大小写) if (title) { const lowerTitle = title.toLowerCase(); const isBanned = BANNED_KEYWORDS.some(keyword => lowerTitle.includes(keyword.toLowerCase()) ); if (isBanned) { // 返回 404(推荐使用自定义页面) return NextResponse.rewrite(new URL('/404', request.url)); // 或使用内置:return new Response('Not Found', { status: 404 }); } } // 正常通过 return NextResponse.next(); } // 确保对所有路径生效(特别是 /play) export const config = { matcher: '/:path*', };
创建自定义 404 页面(app/404.tsx) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 // app/404.tsx import Link from 'next/link'; export default function Custom404() { return ( <div className="flex flex-col items-center justify-center min-h-screen bg-gray-50 dark:bg-gray-900"> <h1 className="text-6xl font-bold text-red-600 mb-6">404</h1> <p className="text-2xl mb-8">页面不存在或已被禁止访问</p> <Link href="/" className="px-8 py-4 bg-blue-600 text-white rounded-lg hover:bg-blue-700"> 返回首页 </Link> </div> ); }
这样,当访问包含 title=AAA(无论是否编码)的链接时,都会直接显示 404 页面,且真实返回 404 状态码。
移除版本检测 问Gemini :怎样修改才能使目标仓库无论何时更新,v0.8都是最新版本
答:方案二:锁定本地时间戳为“未来时间”
如果您不想改动函数逻辑,可以通过设置一个极大 的时间戳来欺骗系统。版本检测模块通过 YYYYMMDDHHMMSS 格式进行 BigInt 比较
修改 VERSION.txt: 将其改为一个远未来的时间(例如 2099 年) 。
修改 version.ts 中的 BUILD_TIMESTAMP: 确保回退值也足够大。
修改为:export const BUILD_TIMESTAMP = '20991231235959';
固定V0.9版本 V0.9版本最快
1.新建仓库,如:https://github.com/iwyang/yangtv
2.创建 github 访问 token,并添加到仓库环境变量,名称为YANGTV(方法见上)
3.下载V0.9源码到本地
4.修改 docker-image.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 name: Build & Push Docker Image on: workflow_dispatch: inputs: tag: description: 'Docker 标签' required: false default: 'latest' type: string push: branches: [main , master ] pull_request: branches: [main , master ] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true permissions: contents: write packages: write actions: write jobs: build: strategy: matrix: include: - platform: linux/amd64 os: ubuntu-latest - platform: linux/arm64 os: ubuntu-24.04-arm runs-on: ${{ matrix.os }} steps: - name: Prepare platform identifier id: platform run: | PLATFORM_ID=$(echo "${{ matrix.platform }}" | sed 's|/|-|g') echo "id=${PLATFORM_ID}" >> $GITHUB_OUTPUT - name: Checkout source code uses: actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.YANGTV }} - name: Set lowercase repository owner id: lowercase run: echo "owner=$(echo '${{ github.repository_owner }} ' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - name: Extract version from package.json id: version run: | VERSION=$(node -p "require('./package.json').version") echo "version=v${VERSION}" >> "$GITHUB_OUTPUT" echo "Detected version: v${VERSION}" - name: Extract metadata id: meta uses: docker/metadata-action@v4 with: images: ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv tags: | type=raw,value=${{ github.event.inputs.tag || 'latest' }},enable={{is_default_branch}} type=raw,value=${{ steps.version.outputs.version }},enable={{is_default_branch}} - name: Build and push by digest id: build uses: docker/build-push-action@v4 with: context: . file: ./Dockerfile platforms: ${{ matrix.platform }} labels: ${{ steps.meta.outputs.labels }} tags: | ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv:${{ github.event.inputs.tag || 'latest' }} ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv:${{ steps.version.outputs.version }} outputs: type=image,name=ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv,name-canonical=true,push=true - name: Export digest run: | mkdir -p /tmp/digests digest="${{ steps.build.outputs.digest }}" touch "/tmp/digests/${digest#sha256:}" - name: Upload digest uses: actions/upload-artifact@v4 with: name: digests-${{ steps.platform.outputs.id }} path: /tmp/digests/* if-no-files-found: error retention-days: 1 merge: runs-on: ubuntu-latest needs: - build steps: - name: Download digests uses: actions/download-artifact@v4 with: path: /tmp/digests pattern: digests-* merge-multiple: true - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.YANGTV }} - name: Set lowercase repository owner id: lowercase run: echo "owner=$(echo '${{ github.repository_owner }} ' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_OUTPUT" - name: Download package.json uses: actions/checkout@v4 with: sparse-checkout: | package.json sparse-checkout-cone-mode: false - name: Extract version from package.json id: version run: | VERSION=$(node -p "require('./package.json').version") echo "version=v${VERSION}" >> "$GITHUB_OUTPUT" echo "Detected version: v${VERSION}" - name: Create manifest list and push working-directory: /tmp/digests run: | docker buildx imagetools create -t ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv:${{ github.event.inputs.tag || 'latest' }} \ $(printf 'ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv@sha256:%s ' *) docker buildx imagetools create -t ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv:${{ steps.version.outputs.version }} \ $(printf 'ghcr.io/${{ steps.lowercase.outputs.owner }}/yangtv@sha256:%s ' *) cleanup-refresh: runs-on: ubuntu-latest needs: - merge if: always() steps: - name: Delete workflow runs uses: Mattraks/delete-workflow-runs@main with: token: ${{ secrets.YANGTV }} repository: ${{ github.repository }} retain_days: 0 keep_minimum_runs: 2
6.本地进行其他修改,见上
7.提交源码到第一步新建的仓库
1 2 3 4 5 6 git init git checkout -b main git remote add origin git@github.com:iwyang/yangtv.git git add . git commit -m "首次提交" git push --force origin main
8.修改 docker-compose.yml 文件
注意修改自己的密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 services: yangtv-core: image: ghcr.io/iwyang/yangtv:latest container_name: yangtv-core restart: always ports: - '3000:3000' environment: - USERNAME=admin - PASSWORD=你的密码 - NEXT_PUBLIC_STORAGE_TYPE=kvrocks - KVROCKS_URL=redis://yangtv-kvrocks:6666 - NEXT_PUBLIC_DISABLE_YELLOW_FILTER=false networks: - yangtv-network depends_on: - yangtv-kvrocks yangtv-kvrocks: image: apache/kvrocks container_name: yangtv-kvrocks restart: always volumes: - kvrocks-data:/var/lib/kvrocks networks: - yangtv-network watchtower: image: containrrr/watchtower container_name: watchtower-yangtv volumes: - /var/run/docker.sock:/var/run/docker.sock command: --interval 86400 --cleanup restart: always networks: yangtv-network: driver: bridge volumes: kvrocks-data:
参考链接