Debian10 上安装部分应用,速度几乎为0,至少需要Debian11以上,512M内存足够。

安装 Docker

1.首先,更新现有的软件包列表:

1
sudo apt update -y

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 软件包来进行升级更新。

1
sudo apt update -y

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.要查看已下载到计算机的镜像:

1
docker images

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.要查看所有的容器对象,请使用:

1
docker ps -a

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(后台)

1
docker-compose up -d

4.要查看正在运行的 docker 容器,请使用以下命令

1
docker-compose ps

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
# cat /etc/systemd/system/docker-compose-app.service

[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
vi 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/decohererk/decotv:latest
container_name: decotv-core
restart: always # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred

networks:
decotv-network:
driver: bridge

volumes:
kvrocks-data:

执行命令

tv 后端程序将运行在 http://localhost:端口号

1
docker-compose up -d

配置域名访问

参考:域名访问

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;

# Redirect all HTTP traffic to HTTPS
return 301 https://$host$request_uri;
}

server {
listen 443 ssl http2;
server_name tv.bore.vip;
root /data/wwwroot/tv.bore.vip;

# SSL setting
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";

# proxy to 3000
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;
# cache
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

  1. 在任何页面的右上角,单击个人资料照片,然后单击 “设置”。

  2. 在左侧边栏中,单击 “开发人员设置”。

  3. 请在左侧边栏的 “Personal access token” 下,单击 “细粒度令牌” 。

  4. 单击 “生成新令牌”。

  5. 在 “令牌名称” 下,输入令牌的名称。

  6. 在 “过期时间” 下,选择令牌的过期时间(永不过期)。

  7. 然后权限要开启 repoworkflowwrite:packages以及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
vi 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 # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred

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

【想要的最终效果】(越详细越好,按优先级列点):

  1. 电影/电视剧/综艺页面默认分类是“全部”,默认排序是“近期热度”(sort = ‘U’),胶囊显示“近期热度”
  2. 动漫页面默认显示“番剧”分类,“番剧”分类和“剧场版”分类默认排序是“近期热度”(sort = ‘U’),胶囊显示“近期热度”
  3. 切换到“综合排序”时,胶囊按钮显示“排序”(灰色)
  4. 选择“近期热度”或其他非综合排序时,胶囊显示具体名称(绿色)
  5. 下拉菜单里默认高亮“近期热度”

【其他要求】(如果有):

  • 不要改变其他筛选器的行为
  • 保持现有加载更多、骨架屏等逻辑不变
  • 请给出完整文件代码,不要省略

当前代码请参考附件:[拖入或贴上最新 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
// 只要匹配 stitle 或主标题任何一个开头,就会显示
const mainTitle = (searchTitle || videoTitleRef.current || '')
.trim()
.replace(/\s+/g, '')
.toLowerCase();

修改后的逻辑:

1
2
3
4
5
// 仅使用主标题,彻底无视 stitle 的前缀干扰
const mainTitle = (videoTitleRef.current || '')
.trim()
.replace(/\s+/g, '')
.toLowerCase();

屏蔽集数超过30%的源

还是用grok提问

我正在修改 fetchSourcesData 函数里的 filter 过滤逻辑(或者评分逻辑)。 【业务场景】:目前在搜索 [某种类型,如:电视剧] 时,会出现 [某种问题,如:有很多集数不全的脏数据]。 【逻辑规则】

  1. 请帮我区分 movievod 两种类型。
  2. 如果是 vod,当 [条件,如:集数误差 > 30%] 时,执行拦截。
  3. 如果是 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)。

问题在于: 你的组件中定义的状态变量名称是 isLoadingsetIsLoading(第 82 行),但你在错误处理回调函数中却尝试调用一个不存在的 setImageLoaded 函数。


修复方案

你需要将 setImageLoaded(true) 更改为组件中实际定义的 setIsLoading(true)

修改步骤:

  1. 打开 src/components/VideoCard.tsx
  2. 找到第 571 行左右。
  3. 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 里的播放逻辑,实现‘无感知优选’。 详细逻辑:

  1. 起播策略:
    • 有指定源: 立即起播,不显示测速加载动画。
    • 无指定源: 允许显示短暂加载(如 2 秒),测速完成后自动播放评分最高的源。
  2. 后台逻辑: 无论哪种情况,起播后都要在后台持续完成所有源的并发测速。
  3. UI 排序: 测速结束后,侧边栏列表按评分(速度+分辨率)降序排列。
  4. 置顶保护: 更新列表时,当前正在播放的源必须锁定在第一位,其余源按评分排序,防止 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 比较

  1. 修改 VERSION.txt 将其改为一个远未来的时间(例如 2099 年) 。
    • 内容改为:20991231235959
  2. 修改 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
vi 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 # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred
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 # You can keep 'unless-stopped' or 'always' as preferred

networks:
yangtv-network:
driver: bridge

volumes:
kvrocks-data:

参考链接