打造视频下载Webhook

前言

由于 B 站的番出现圣骑和被剪的情况越来越多,今年补番已经使用了别的方式,加之 B 站的客户端越更越烂,已经卡到在骁龙 636+原生上都不能流畅看视频了,用旧版本又看不了 1080p,于是我越来越想从手机里删掉这垃圾了。然而有些喜欢的 up 主只在 B 站传视频,那么有什么解决途径呢?

今年初我开始自建 RSSHub 订阅信息流,从手机里删掉了一大堆科技网站客户端,当然 B 站的 up 主更新也使用 RSSHub 订阅了,不过看还是得跳去客户端,前段时间看到 RSSHub 的作者搞了个用订阅触发 webhook 下载,然后用 you-get 下视频的小工具,然而是 nodejs 写的,youget 则是 python 写的,我的便宜小鸡跑个 RSSHub 和其他一些杂七杂八的东西已经把内存吃的差不多了,再跑个 node 怕是要炸,于是就没继续折腾,不过前两天突发奇想,要是使用 go rust 这些编译型语言写的工具实现同样的功能,应该没什么大问题吧!于是便有了本文。

webhook

首先是搞定 webhook 部分,稍作搜索,果然有 go 写的 webhook 工具,而且提供了 Docker 镜像,我打算使用一堆小工具组合实现功能,但是不想创建多个 container,毕竟跨 container 要共享 volume 啥的也挺麻烦的。

所以先把他的镜像作为中间层引入,然后在一个新镜像里跑起来试试看。

FROM almir/webhook as builder

FROM alpine:latest
EXPOSE 9000
VOLUME /etc/webhook
COPY --from=builder /usr/local/bin/webhook /usr/bin/webhook
RUN chmod +x /usr/bin/webhook
ENTRYPOINT ["/usr/bin/webhook"]
CMD ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]
dockerfile
[
	{
		"id": "down",
		"execute-command": "/usr/bin/sh",
		"command-working-directory": "/etc/webhook",
		"pass-arguments-to-command": [
			{
				"source": "string",
				"name": "echo"
			},
			{
				"source": "url",
				"name": "url"
			}
		]
	}
]
json

Caddy 反代 9000 端口出去,请求一下/hooks/down?url=目标链接可以看到容器日志里会打印出链接。

视频下载

接下来要找个可以下载视频的东西,一番寻找之后找到了annie

功能很多,支持一堆下载网站,不过我只需要能下载 B 站的视频页就可以了,加入一个 builder 中间层,编译一波,然后塞镜像里:

FROM almir/webhook as webhook

FROM golang:1.12.4 as builder
RUN apk add --no-cache git
RUN go get -v github.com/iawia002/annie

FROM alpine:latest
EXPOSE 9000
VOLUME /etc/webhook
COPY --from=webhook /usr/local/bin/webhook /usr/bin/webhook
COPY --from=builder /go/bin/annie /usr/bin/annie
RUN chmod +x /usr/bin/webhook /usr/bin/annie
ENTRYPOINT ["/usr/bin/webhook"]
CMD ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]
dockerfile

annie 的下载进度是直接打印在控制台输出上的,想要看到进度会很麻烦,不过 annie 还支持调用 aria2 下载。

先从官方文档抄一个 aria2 的配置文件,省的在命令行上写一大堆参数:

dir=/etc/webhook/downloads
input-file=/etc/aria2.session
save-session=/etc/aria2.session

file-allocation=falloc
log-level=warn
enable-http-pipelining=true

max-concurrent-downloads=3
max-connection-per-server=10
min-split-size=10M
split=10
continue=true
max-overall-download-limit=0
max-overall-upload-limit=1K

enable-rpc=true
rpc-listen-all=true
rpc-allow-origin-all=true
rpc-listen-port=6800

disable-ipv6=true
ini

然后继续修改 dockerfile,引入 aria2:

FROM almir/webhook as webhook

FROM golang:1.12.4 as builder
RUN apk add --no-cache git
RUN go get -v github.com/iawia002/annie

FROM alpine:latest
EXPOSE 9000 6800
VOLUME /etc/webhook
COPY --from=webhook /usr/local/bin/webhook /usr/bin/webhook
COPY --from=builder /go/bin/annie /usr/bin/annie
COPY ./aria2.conf /etc/aria2.conf
RUN apk add --no-cache aria2 tzdata && \
    chmod +x /usr/bin/webhook /usr/bin/annie && \
	touch /etc/aria2.session
ENTRYPOINT ["/usr/bin/webhook"]
CMD ["-verbose", "-hooks=/etc/webhook/hooks.json", "-hotreload"]
dockerfile

这时候已经需要同时运行两个程序了,写个 sh 跑不太稳定,而且只有最后一个运行的程序才能直接用docker logs看到日志,那就搜索下有什么比较好用的进程管理器吧。

试用了一堆后,选用了foreman的一个 go 移植版本goreman,这是一个管理基于Procfile启动的程序的管理器。

先写个 Procfile:

webhook:/usr/bin/webhook -verbose -hooks=/etc/webhook/hooks.json -template -hotreload
aria2c:/usr/bin/aria2c --conf-path=/etc/aria2.conf --log=/etc/webhook/logs.txt --rpc-secret=$RPC_SECRET
ini

然后继续修改 dockerfile,编译 goreman 并引入:

FROM almir/webhook as webhook

FROM golang:1.12-alpine as builder
RUN apk add --no-cache git
RUN go get -v github.com/iawia002/annie
RUN go get -v github.com/mattn/goreman

FROM alpine:latest
EXPOSE 9000 6800
VOLUME /etc/webhook
ENV RPC_SECRET=""
COPY --from=webhook /usr/local/bin/webhook /usr/bin/webhook
COPY --from=builder /go/bin/annie /usr/bin/annie
COPY --from=builder /go/bin/goreman /usr/bin/goreman
COPY ./Procfile /etc/Procfile
COPY ./aria2.conf /etc/aria2.conf
RUN apk add --no-cache aria2 tzdata && \
    chmod +x /usr/bin/webhook /usr/bin/annie /usr/bin/goreman && \
	touch /etc/aria2.session
WORKDIR /etc
ENTRYPOINT ["/usr/bin/goreman"]
CMD ["start"]
dockerfile

最后我还需要一个 aria2 的网页端控制台,aria2-ng是使用人群中比较流行的客户端,界面挺不错。

这个控制台不需要后端,直接调用 aria2 jsonrpc,这个前面已经启用了。虽然网上有不少人利用 github pages 之类的静态页面服务提供在线版本,不过处理跨域也是比较麻烦,所以我打算直接在本地搭一个。

我在外部使用 caddy 将 10+ 容器服务反代到一个域名下,虽然 ariang 是一个纯 html 文件,可以直接把文件放到外面让 caddy 进行代理,但是这会让容器的重新部署变得麻烦,集成到容器里显然是更好选择。

darkhttpd是一个十分轻量级的静态 http 服务器,我直接使用它提供 http 服务,之后再在外部的 caddy 容器中进行反代。

基于上面的信息,继续修改 dockerfile:

FROM almir/webhook as webhook

FROM golang:1.12-alpine as builder
RUN apk add --no-cache curl git jq unzip
RUN go get -v github.com/iawia002/annie
RUN go get -v github.com/mattn/goreman
RUN mkdir -p /aria2-ng
RUN curl -o /aria2-ng.zip -L $(curl -sX GET "https://api.github.com/repos/mayswind/AriaNg/releases/latest" | jq -r '.assets[0].browser_download_url')
RUN unzip /aria2-ng.zip -d /aria2-ng
RUN sed -i 's/6800/443/g' /aria2-ng/index.html

FROM alpine:latest
EXPOSE 9000 6800 80
VOLUME /etc/webhook
ENV RPC_SECRET=""
COPY --from=webhook /usr/local/bin/webhook /usr/bin/webhook
COPY --from=builder /go/bin/annie /usr/bin/annie
COPY --from=builder /go/bin/goreman /usr/bin/goreman
COPY --from=builder /aria2-ng /etc/aria2-ng
COPY ./webhook.proc /etc/Procfile
COPY ./aria2.conf /etc/aria2.conf
RUN apk add --no-cache aria2 darkhttpd tzdata && \
    chmod +x /usr/bin/webhook /usr/bin/annie /usr/bin/goreman && \
	touch /etc/aria2.session
WORKDIR /etc
ENTRYPOINT ["/usr/bin/goreman"]
CMD ["start"]
dockerfile

然后修改 Procfile,顺便启用 rpc_secret:

webhook:/usr/bin/webhook -verbose -hooks=/etc/webhook/hooks.json -template -hotreload
darkhttpd:/usr/bin/darkhttpd /etc/aria2-ng --port 80
aria2c:/usr/bin/aria2c --conf-path=/etc/aria2.conf --log=/etc/webhook/logs.txt --rpc-secret=$RPC_SECRET
ini

启用 rpc_secret 后,annie 的下载请求也要带上,这时候修改下 webhook 的请求参数:

[
    {
      "id": "down",
      "execute-command": "/usr/bin/annie",
      "command-working-directory": "/etc/webhook",
      "pass-arguments-to-command":
      [
        {
            "source": "string",
            "name": "-aria2"
        },
        {
            "source": "string",
            "name": "-aria2token"
        },
        {
            "source": "string",
            "name": "{{ getenv "RPC_SECRET" }}"
        },
        {
          "source": "url",
          "name": "url"
        }
      ]
    }
  ]
json

至此已经搞得差不多,再使用 caddy 反代一下就可以用了(tls 配置自行处理,否则把 https 改成 http):

https://your.domain {
  proxy /aria2 http://webhook:80 {
    without /aria2
  }
  proxy /jsonrpc http://webhook:6800 {
    websocket
  }
  proxy /hooks http://webhook:9000
}
ini

现在可以使用/aria2 进入管理页面,aria2 的 rpc-secret 使用环境变量 RPC_SECRET 设置。 下载视频则直接 get 请求 /hooks/down?url=视频 url。

自动下载

最后的自动下载依然是使用 ifttt,订阅到新 rss 后直接把链接请求一下就好:

ifttt
ifttt

Docker 镜像

最后做了一下整理,publish 到了 Docker Hub 上。

直接运行:

docker run -d --name webhook -p 9000:9000 -p 6800:6800 -p 80:80 -e RPC_SECRET=<YOUR_RPC_SECRET> -e URL_PREFIX=<YOUR_WEBHOOK_URL_PREFIX> darkskydocker/down_webhook
sh

docker-compose:

version: '2.3'
services:
    webhook:
        image: darkskydocker/down_webhook:latest
        environment:
            - RPC_SECRET=<YOUR_RPC_SECRET>
            - URL_PREFIX=<YOUR_WEBHOOK_URL_PREFIX>
        volumes:
            - ./webhook:/etc/webhook
        logging:
            options:
                max-size: '500k'
        restart: always
yml