<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on H⁺ + OH⁻ → H₂O</title><link>https://naizi.ch/post/</link><description>Recent content in Posts on H⁺ + OH⁻ → H₂O</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sat, 15 Jun 2024 00:00:00 +0000</lastBuildDate><atom:link href="https://naizi.ch/post/atom.xml" rel="self" type="application/rss+xml"/><item><title>自建 Immich 相册服务同步白嫖 Google Photos</title><link>https://naizi.ch/2024/06/15/%E8%87%AA%E5%BB%BA-immich-%E7%9B%B8%E5%86%8C%E6%9C%8D%E5%8A%A1%E5%90%8C%E6%AD%A5%E7%99%BD%E5%AB%96-google-photos/</link><pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate><guid>https://naizi.ch/2024/06/15/%E8%87%AA%E5%BB%BA-immich-%E7%9B%B8%E5%86%8C%E6%9C%8D%E5%8A%A1%E5%90%8C%E6%AD%A5%E7%99%BD%E5%AB%96-google-photos/</guid><description>&lt;p>Google Photos 有着 15GB 的免费空间上限，前几个月自己搞了 NAS 之后，看上了 Immich 相册，算是最贴近 Google Photos 界面的开源自建相册服务了，自己手头也正好还留着当初买的初代 Google Pixel，能够以原画质白嫖无限相册空间，相当于能够在本地备份的同时也拥有稳定的云备份。&lt;/p>
&lt;p>考虑到稳定性，NAS 上并没有安装 APP 或者其他容器服务，单纯作为一个存储设备。Docker 服务都放在另一台小主机上运行。&lt;/p>
&lt;p>计划是 TrueNAS 存储只提供 SMB 服务挂载，小主机上通过多个 Docker 分别部署 Immich 相册，以及用来同步照片到 Pixel 的 Syncthing 服务。另外手机肯定是7*24供电的状态，为了不用频繁物理操作，考虑部署 RustDesk 服务远程控制手机，同样采用 Docker 的方式部署在小主机上。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h1 id="软硬件配置">软硬件配置&lt;/h1>
&lt;p>TrueNAS 服务器 CPU 是 N100， 16GB 内存， 16TB*2 HDD（Raid 1）&lt;/p>
&lt;p>小主机 CPU 同样是 N100，24GB 内存，256GB SSD，Debian 系统&lt;/p>
&lt;p>初代 Pixel，未 Root&lt;/p>
&lt;h2 id="truenas-配置">TrueNAS 配置&lt;/h2>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/TrueNAS_SMB.png"
loading="lazy"
alt="TrueNAS_SMB"
>&lt;/p>
&lt;p>非常简单，TrueNAS 上的配置就结束了，继续配置小主机。&lt;/p>
&lt;h2 id="服务器配置">服务器配置&lt;/h2>
&lt;p>首先为了让之后 Immich 从 NAS 上读写，需要把 SMB 开机自动挂载路径。SSH 登陆后执行：&lt;/p>
&lt;blockquote>
&lt;p>sudo apt install cifs-utils psmisc&lt;br>
nano ~/.credentials&lt;/p>
&lt;/blockquote>
&lt;p>对应修改 SMB 共享的用户名和密码&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">username=smbusername
password=smbpassword
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>sudo chown username .credentials&lt;br>
sudo chmod 600 .credentials&lt;/p>
&lt;/blockquote>
&lt;p>测试挂载&lt;/p>
&lt;blockquote>
&lt;p>sudo mkdir /mnt/Immich&lt;br>
sudo mount -t cifs -o credentials=~/.credentials //truenas.ipaddress/Immich /mnt/Immich&lt;/p>
&lt;/blockquote>
&lt;p>没有问题则暂时卸载&lt;/p>
&lt;blockquote>
&lt;p>sudo umount -t cifs /mnt/Immich&lt;/p>
&lt;/blockquote>
&lt;p>修改 &lt;code>/etc/fstab&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>sudo nano /etc/fstab&lt;/p>
&lt;/blockquote>
&lt;p>添加如下内容&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf"># Immich Storage
//truenas.ipaddress/Immich /mnt/Immich cifs credentials=/home/username/.credentials 0 0
&lt;/code>&lt;/pre>&lt;p>应用修改完成挂载&lt;/p>
&lt;blockquote>
&lt;p>sudo systemctl daemon-reload&lt;br>
sudo mount -av&lt;/p>
&lt;/blockquote>
&lt;h1 id="docker-配置">Docker 配置&lt;/h1>
&lt;h2 id="安装-dockerhttpsdocsdockercomengineinstalldebian">&lt;a class="link" href="https://docs.docker.com/engine/install/debian/" target="_blank" rel="noopener"
>安装 Docker&lt;/a>&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install ca-certificates curl
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo install -m &lt;span style="color:#ae81ff">0755&lt;/span> -d /etc/apt/keyrings
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo chmod a+r /etc/apt/keyrings/docker.asc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> &lt;span style="color:#e6db74">&amp;#34;deb [arch=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>dpkg --print-architecture&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74"> signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> &lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>. /etc/os-release &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$VERSION_CODENAME&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74"> stable&amp;#34;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> sudo tee /etc/apt/sources.list.d/docker.list &amp;gt; /dev/null
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="部署-immich">部署 Immich&lt;/h2>
&lt;p>官方推荐采用 &lt;a class="link" href="https://v1.106.4.archive.immich.app/docs/install/docker-compose" target="_blank" rel="noopener"
>Docker Compose&lt;/a> 的方式安装，跟着教程走并没有什么难度。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>mkdir ~/immich-app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd ~/immich-app
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget -O docker-compose.yml https://github.com/immich-app/immich/releases/latest/download/docker-compose.yml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget -O .env https://github.com/immich-app/immich/releases/latest/download/example.env
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget -O hwaccel.transcoding.yml https://github.com/immich-app/immich/releases/latest/download/hwaccel.transcoding.yml
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>wget -O hwaccel.ml.yml https://github.com/immich-app/immich/releases/latest/download/hwaccel.ml.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>编辑 &lt;code>.env&lt;/code> 文件，修改照片的上传路径&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">TZ=Asis/Shanghai
UPLOAD_LOCATION=/mnt/Immich
DB_DATA_LOCATION=./postgres
&lt;/code>&lt;/pre>&lt;p>数据库路径不变，之所以这样是因为数据库有频繁的读写，像 16T 容量的机械盘基本都是充气盘，会频繁的发出炒豆子的声音。&lt;br>
换言之只要不是充气盘或者距离远听不见声音就无所谓了，可以改成 &lt;code>/mnt/Immich/postgres&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>docker compose up -d&lt;/p>
&lt;/blockquote>
&lt;p>启动容器后，访问 &lt;a class="link" href="http://server.ipaddress:2283/" target="_blank" rel="noopener"
>http://server.ipaddress:2283/&lt;/a> 登录，在&lt;code>设置&lt;/code>/&lt;code>机器学习设置&lt;/code>/&lt;code>智能搜索&lt;/code>/&lt;code>CLIP模型&lt;/code>，修改为支持多语言的&lt;code>XLM-Roberta-Large-Vit-B-16Plus&lt;/code>。&lt;/p>
&lt;p>为了便于管理，启用&lt;code>设置&lt;/code>/&lt;code>存储模板&lt;/code>功能。&lt;/p>
&lt;p>在&lt;code>设置&lt;/code>/&lt;code>视频转码设置&lt;/code>中，&lt;code>支持的音频编解码器&lt;/code>勾选 AAC/MP3/Opus，&lt;code>支持的视频编解码器&lt;/code>勾选 H.264/HEVC/VP9/AV1，因为我现在的主力机都支持这些编码，没有必要转码。&lt;/p>
&lt;h2 id="部署-syncthing">部署 Syncthing&lt;/h2>
&lt;p>先建立需要的目录&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo mkdir -p /var/syncthing/Immich
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir ~/syncthing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd ~/syncthing
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>nano docker-compose.yml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>按照&lt;a class="link" href="https://github.com/syncthing/syncthing/blob/main/README-Docker.md" target="_blank" rel="noopener"
>官方指导&lt;/a>编写 Dockerfile&lt;/p>
&lt;h2 id="docker-composeyml">docker-compose.yml&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>---&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>services:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> syncthing:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> image: syncthing/syncthing&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> container_name: syncthing&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> hostname: Dorothy&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> environment:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - PUID&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - PGID&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> volumes:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - /var/syncthing:/var/syncthing&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - /mnt/Immich/library/admin:/var/syncthing/Immich&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> network_mode: host&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> restart: unless-stopped&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>docker compose up -d&lt;/p>
&lt;/blockquote>
&lt;p>启动容器后，访问 &lt;a class="link" href="http://server.ipaddress:8384/" target="_blank" rel="noopener"
>http://server.ipaddress:8384/&lt;/a> 登录，添加文件夹&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Syncthing_Immich.png"
loading="lazy"
alt="Syncthing_Immich"
>&lt;/p>
&lt;p>在&lt;code>高级&lt;/code>书签中选择&lt;code>文件夹类型&lt;/code>为&lt;code>仅发送&lt;/code>&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Syncthing_Immich_extra.png"
loading="lazy"
alt="Syncthing_Immich_extra"
>&lt;/p>
&lt;p>这样 Syncthing 的初步配置就结束了&lt;/p>
&lt;h2 id="部署-rustdesk">部署 RustDesk&lt;/h2>
&lt;p>同样按照&lt;a class="link" href="https://rustdesk.com/docs/en/self-host/rustdesk-server-oss/docker/" target="_blank" rel="noopener"
>官方指导&lt;/a>建立 Dockerfile&lt;/p>
&lt;h2 id="docker-composeyml-1">docker-compose.yml&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>---&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>services:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> hbbs:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> container_name: hbbs&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> image: rustdesk/rustdesk-server:latest&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> command: hbbs&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> volumes:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - ./data:/root&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> network_mode: &lt;span style="color:#e6db74">&amp;#34;host&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> depends_on:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - hbbr&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> restart: always&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> hbbr:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> container_name: hbbr&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> image: rustdesk/rustdesk-server:latest&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> command: hbbr&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> volumes:&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> - ./data:/root&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> network_mode: &lt;span style="color:#e6db74">&amp;#34;host&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span> restart: always&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>启动服务&lt;/p>
&lt;blockquote>
&lt;p>docker compose up -d&lt;/p>
&lt;/blockquote>
&lt;p>获取服务端 API KEY&lt;/p>
&lt;blockquote>
&lt;p>docker container logs hbbs | grep Key&lt;/p>
&lt;/blockquote>
&lt;h1 id="配置-pixel-自动任务">配置 Pixel 自动任务&lt;/h1>
&lt;p>首先安装 &lt;a class="link" href="https://f-droid.org/packages/com.nutomic.syncthingandroid/" target="_blank" rel="noopener"
>Syncthing&lt;/a> 和 &lt;a class="link" href="https://f-droid.org/packages/com.carriez.flutter_hbb/" target="_blank" rel="noopener"
>RustDesk&lt;/a> 的客户端&lt;/p>
&lt;p>然后是用来实现自动任务的 &lt;a class="link" href="https://play.google.com/store/apps/details?id=net.dinglisch.android.taskerm" target="_blank" rel="noopener"
>Tasker&lt;/a> 和 &lt;a class="link" href="https://play.google.com/store/apps/details?id=com.balda.touchtask" target="_blank" rel="noopener"
>TouchTask&lt;/a>&lt;/p>
&lt;h2 id="连接-rustdesk">连接 RustDesk&lt;/h2>
&lt;p>修改对应的服务器 IP 地址和 KEY&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/RustDesk.png"
loading="lazy"
alt="RustDesk"
>&lt;/p>
&lt;p>允许 IP 地址访问，保持后台服务，允许自启动&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/RustDesk_Setting.png"
loading="lazy"
alt="RustDesk_Setting"
>&lt;/p>
&lt;p>设置固定密码，使用固定密码&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/RustDesk_passwd.png"
loading="lazy"
alt="RustDesk_passwd"
>&lt;/p>
&lt;p>勾选全部权限后启动服务&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/RustDesk_Service.png"
loading="lazy"
alt="RustDesk_Service"
>&lt;/p>
&lt;p>在自己的主机上也装好 Rustdesk，修改对应的服务器 IP 地址和 KEY 信息后，现在就可以接上充电线放在一边，通过电脑远程操作 Pixel 了。&lt;/p>
&lt;h2 id="连接-syncthing">连接 Syncthing&lt;/h2>
&lt;p>选择 Syncthing 的&lt;code>设备&lt;/code>然后点击右上角&lt;code>+&lt;/code>，添加设备&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Syncthing_device.png"
loading="lazy"
alt="Syncthing_device"
>&lt;/p>
&lt;p>回到 &lt;a class="link" href="http://server.ipaddress:8384/" target="_blank" rel="noopener"
>http://server.ipaddress:8384/&lt;/a> 登录，点击识别的设备标识，通过手机扫码或者直接输入完整设备标识字符串进行配对&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Syncthing_id.png"
loading="lazy"
alt="Syncthing_id"
>&lt;/p>
&lt;p>然后网页端会出现配对许可的提示，同意即可。&lt;/p>
&lt;p>接着编辑之前共享的目录，共享给配对的设备。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Syncthing_share.png"
loading="lazy"
alt="Syncthing_share"
>&lt;/p>
&lt;p>回到手机，同意接收共享，将目录选为照相机的默认目录 &lt;code>/storage/emulated/0/DCIM/Camera&lt;/code>，目录种类更改为仅接收。&lt;/p>
&lt;p>这样就实现了 Immich 从 NAS 往 Pixel 的单向同步，之后在 Pixel 上清除本地缓存时也不会导致 NAS 上的文件被同步删除，Pixel 也会自动将同步收到的照片上传至 Google Photos。&lt;/p>
&lt;h2 id="配置-tasker">配置 Tasker&lt;/h2>
&lt;p>为了避免手机存储满了之后无法继续同步文件，需要定期运行相册 APP 释放空间，这里就可以使用 Tasker 来完成。&lt;/p>
&lt;p>主要的操作一共三种：&lt;/p>
&lt;ol>
&lt;li>&lt;code>+&lt;/code>/&lt;code>任务&lt;/code>/&lt;code>等待&lt;/code>&lt;/li>
&lt;li>&lt;code>+&lt;/code>/&lt;code>程序&lt;/code>/&lt;code>启动应用/结束应用&lt;/code>&lt;/li>
&lt;li>&lt;code>+&lt;/code>/&lt;code>插件&lt;/code>/&lt;code>TouchTask&lt;/code>/&lt;code>Actions&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_add_1.png"
loading="lazy"
alt="Tasker_add_1"
>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_add_2.png"
loading="lazy"
alt="Tasker_add_2"
>&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_add_3.png"
loading="lazy"
alt="Tasker_add_3"
>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_add_4.png"
loading="lazy"
alt="Tasker_add_4"
>&lt;/p>
&lt;p>屏幕点击操作需要的坐标数据可以通过打开&lt;code>开发者选项&lt;/code>/&lt;code>输入&lt;/code>/&lt;code>指针位置&lt;/code>来获取&lt;/p>
&lt;p>然后按照&lt;/p>
&lt;p>等待/结束相册/启动相册/等待/点击头像/释放空间/确定释放/等待/启动Tasker（回前台）&lt;/p>
&lt;p>的逻辑添加操作步骤即可&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker.png"
loading="lazy"
alt="Tasker"
>&lt;/p>
&lt;p>之后添加配置文件，让任务定时执行&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_config.png"
loading="lazy"
alt="Tasker_config"
>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/Tasker_timer.png"
loading="lazy"
alt="Tasker_timer"
>&lt;/p>
&lt;h2 id="fakestandby">FakeStandby&lt;/h2>
&lt;p>为了不需要 Root 权限，Pixel 设置了不锁屏，但保持亮屏一是容易造成烧屏，二是造成无意义的耗电，为此可以安装 &lt;a class="link" href="https://f-droid.org/packages/android.jonas.fakestandby/" target="_blank" rel="noopener"
>FakeStandby&lt;/a> 这款应用，通过显示黑屏实现伪锁屏待机。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2024-06-15-immich-syncthing-google-photos/FakeStandby.png"
loading="lazy"
alt="FakeStandby"
>&lt;/p>
&lt;h1 id="导入-google-photos">导入 Google Photos&lt;/h1>
&lt;p>最后，对于已经在 Google 相册中的照片，可以通过 Google 导出服务和 immich-go 工具便捷的导入 Immich。&lt;/p>
&lt;p>访问 &lt;a class="link" href="https://takeout.google.com/settings/takeout/custom/photos" target="_blank" rel="noopener"
>https://takeout.google.com/settings/takeout/custom/photos&lt;/a>&lt;/p>
&lt;p>在要包含的数据中选择 &lt;code>Google 相册&lt;/code>，继续，然后再目标位置选择&lt;code>添加到 OneDrive&lt;/code>，文件类型选择&lt;code>.zip&lt;/code>，大小选择&lt;code>50GB&lt;/code>。&lt;/p>
&lt;p>视具体网络情况，选择通过 OneDrive 可以直连下载，节约大量的代理流量。&lt;/p>
&lt;p>网页端登录 Immich，点击头像/&lt;code>账号设定&lt;/code>/&lt;code>API Keys&lt;/code>/&lt;code>新API Key&lt;/code>，复制下 Key 字符串。&lt;/p>
&lt;p>Google 导出结束后，下载&lt;a class="link" href="https://github.com/simulot/immich-go" target="_blank" rel="noopener"
>immich-go&lt;/a>，将解压的 exe 文件和 Google 导出的 zip 放在同一目录中，打开命令行 cd 到此目录并执行&lt;/p>
&lt;blockquote>
&lt;p>.\immich-go.exe -server=http://immich.ipaddress:2283 -key=API_KEY_STRING upload -google-photos takeout-*.zip&lt;/p>
&lt;/blockquote>
&lt;p>即可将过去的相册内容导入至 Immich。&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>Google 目前的政策只有初代 Pixel 能够上传原始画质享有无限空间，后代的 Pixel 也只有最早的几代能够上传省空间画质且享有无限空间。&lt;/p>
&lt;p>目前闲鱼 2~300 收一个初代 Pixel 还是合算的，不过哪天 Pixel 电池顶不住了，是换电池还是换机，亦或者直接抛弃 Google 相册，就见仁见智了。&lt;/p></description></item><item><title>树莓派搭建网络摄像头</title><link>https://naizi.ch/2024/03/15/%E6%A0%91%E8%8E%93%E6%B4%BE%E6%90%AD%E5%BB%BA%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/</link><pubDate>Fri, 15 Mar 2024 00:00:00 +0000</pubDate><guid>https://naizi.ch/2024/03/15/%E6%A0%91%E8%8E%93%E6%B4%BE%E6%90%AD%E5%BB%BA%E7%BD%91%E7%BB%9C%E6%91%84%E5%83%8F%E5%A4%B4/</guid><description>&lt;p>年前迭代了 3D 打印机，换了拓竹的 A1 mini。感慨技术迭代是真的快，Ender 3 真的是完全落后时代了，打算（已经）拆了改造成 PET 回收装置。&lt;/p>
&lt;p>对应着原本担当&lt;a class="link" href="https://naizi.ch/2021/08/28/%e5%8d%87%e7%ba%a7-3d-%e6%89%93%e5%8d%b0%e6%9c%ba%e5%9b%ba%e4%bb%b6%e5%8f%8a%e8%bf%9c%e7%a8%8b%e6%89%93%e5%8d%b0%e6%96%b9%e6%a1%88/" target="_blank" rel="noopener"
>远程控制作用的树莓派&lt;/a>又双叒叕闲置了，再次折腾点东西玩。&lt;/p>
&lt;p>树莓派是 Raspberry Pi 3B+ ，现在已经算是老东西了。摄像头放弃了专用的排线摄像头，排线的物理距离太短还容易伤，换了另一个闲置的 USB 摄像头，型号是 Logitech C920。&lt;/p>
&lt;p>主要参考了 &lt;a class="link" href="https://medium.com/@peer5/setting-up-hls-live-streaming-server-using-nginx-67f6b71758db" target="_blank" rel="noopener"
>Setting up HLS live streaming server using NGINX&lt;/a> 的设置过程。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="配置过程">配置过程&lt;/h2>
&lt;h3 id="安装软件包">安装软件包&lt;/h3>
&lt;blockquote>
&lt;p>sudo apt-get update&lt;br>
sudo apt install nginx libnginx-mod-rtmp ffmpeg&lt;/p>
&lt;/blockquote>
&lt;h3 id="nginx-配置">nginx 配置&lt;/h3>
&lt;blockquote>
&lt;p>sudo nano /etc/nginx/nginx.conf&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
}
rtmp {
server {
listen 1935;
chunk_size 4000;
application show {
live on;
hls on;
hls_path /tmp/hls/;
hls_fragment 3;
hls_playlist_length 60;
}
}
}
http {
sendfile off;
tcp_nopush on;
directio 512;
default_type application/octet-stream;
server {
listen 8080;
location / {
add_header &amp;#39;Cache-Control&amp;#39; &amp;#39;no-cache&amp;#39;;
add_header &amp;#39;Access-Control-Allow-Origin&amp;#39; &amp;#39;*&amp;#39; always;
add_header &amp;#39;Access-Control-Expose-Headers&amp;#39; &amp;#39;Content-Length&amp;#39;;
if ($request_method = &amp;#39;OPTIONS&amp;#39;) {
add_header &amp;#39;Access-Control-Allow-Origin&amp;#39; &amp;#39;*&amp;#39;;
add_header &amp;#39;Access-Control-Max-Age&amp;#39; 1728000;
add_header &amp;#39;Content-Type&amp;#39; &amp;#39;text/plain charset=UTF-8&amp;#39;;
add_header &amp;#39;Content-Length&amp;#39; 0;
return 204;
}
types {
application/dash+xml mpd;
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /tmp/;
}
}
server {
listen 80;
location / {
root /var/www/html/;
}
}
}
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>sudo systemctl enable nginx&lt;br>
sudo nginx -t&lt;br>
sudo nginx -s reload&lt;/p>
&lt;/blockquote>
&lt;h3 id="ffmpeg-推流配置">ffmpeg 推流配置&lt;/h3>
&lt;blockquote>
&lt;p>sudo nano /etc/systemd/system/ffmpeg_stream.service&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">[Unit]
Description=FFmpeg Streamer
After=network.target
[Service]
ExecStart=/bin/bash -c &amp;#39;while true; do ffmpeg -hide_banner -thread_queue_size 512 -s 1280x720 -i /dev/video0 -thread_queue_size 1024 -f lavfi -i anullsrc -codec:v h264_v4l2m2m -b:v 8096k -c:a aac -b:a 128k -r 24 -f flv rtmp://localhost/show/stream || continue; done&amp;#39;
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>sudo systemctl daemon-reload&lt;br>
sudo service ffmpeg_stream enable&lt;br>
sudo service ffmpeg_stream start&lt;/p>
&lt;/blockquote>
&lt;h3 id="html-播放器页面">html 播放器页面&lt;/h3>
&lt;blockquote>
&lt;p>sudo nano /var/www/html/index.html&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;!DOCTYPE html&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">html&lt;/span> &lt;span style="color:#a6e22e">lang&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;en&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">head&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">meta&lt;/span> &lt;span style="color:#a6e22e">charset&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;UTF-8&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">title&lt;/span>&amp;gt;Live Streaming&amp;lt;/&lt;span style="color:#f92672">title&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">link&lt;/span> &lt;span style="color:#a6e22e">href&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;//vjs.zencdn.net/8.3.0/video-js.min.css&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">rel&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;stylesheet&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">script&lt;/span> &lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;//vjs.zencdn.net/8.3.0/video.min.js&amp;#34;&lt;/span>&amp;gt;&amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">head&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">div&lt;/span> &lt;span style="color:#a6e22e">style&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;margin: auto;width: 100%;padding: 0px;&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">video&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;my-player&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">class&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;video-js vjs-default-skin&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">controls&lt;/span> &lt;span style="color:#a6e22e">preload&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;auto&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">poster&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;poster.jpg&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#a6e22e">data-setup&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;{&amp;#34;fluid&amp;#34;: true}&amp;#39;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">source&lt;/span> &lt;span style="color:#a6e22e">src&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;http://raspberry.home:8080/hls/stream.m3u8&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;application/x-mpegURL&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">source&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">p&lt;/span> &lt;span style="color:#a6e22e">class&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;vjs-no-js&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> To view this video please enable JavaScript, and consider upgrading to a
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> web browser that
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">a&lt;/span> &lt;span style="color:#a6e22e">href&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;https://videojs.com/html5-video-support/&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">target&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;_blank&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> supports HTML5 video
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">a&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">p&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">video&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">div&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">var&lt;/span> &lt;span style="color:#a6e22e">player&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#a6e22e">videojs&lt;/span>(&lt;span style="color:#e6db74">&amp;#39;my-player&amp;#39;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &amp;lt;/&lt;span style="color:#f92672">script&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">body&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">html&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>至此摄像头架设完成。&lt;/p></description></item><item><title>Git 检测到存储库可能不安全的解决方法</title><link>https://naizi.ch/2024/03/12/git-%E6%A3%80%E6%B5%8B%E5%88%B0%E5%AD%98%E5%82%A8%E5%BA%93%E5%8F%AF%E8%83%BD%E4%B8%8D%E5%AE%89%E5%85%A8%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</link><pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate><guid>https://naizi.ch/2024/03/12/git-%E6%A3%80%E6%B5%8B%E5%88%B0%E5%AD%98%E5%82%A8%E5%BA%93%E5%8F%AF%E8%83%BD%E4%B8%8D%E5%AE%89%E5%85%A8%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</guid><description>&lt;p>因为经常折腾东西导致重新安装系统，也因此经常遇到类似如下问题：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>git status
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fatal&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> detected dubious ownership &lt;span style="color:#66d9ef">in&lt;/span> repository at &lt;span style="color:#e6db74">&amp;#39;D:/Neutralization.github.io&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#39;D:/Neutralization.github.io/.git&amp;#39;&lt;/span> is owned by&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (inconvertible) (S-&lt;span style="color:#ae81ff">1&lt;/span>-&lt;span style="color:#ae81ff">5&lt;/span>-&lt;span style="color:#ae81ff">21&lt;/span>-&lt;span style="color:#ae81ff">111&lt;/span>-&lt;span style="color:#ae81ff">111&lt;/span>-&lt;span style="color:#ae81ff">111&lt;/span>-&lt;span style="color:#ae81ff">1001&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>but the current user is&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> WORKGROUP/Neutralized (S-&lt;span style="color:#ae81ff">1&lt;/span>-&lt;span style="color:#ae81ff">5&lt;/span>-&lt;span style="color:#ae81ff">21&lt;/span>-&lt;span style="color:#ae81ff">222&lt;/span>-&lt;span style="color:#ae81ff">222&lt;/span>-&lt;span style="color:#ae81ff">222&lt;/span>-&lt;span style="color:#ae81ff">1002&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>To add an exception &lt;span style="color:#66d9ef">for&lt;/span> this directory, call&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> git config --global --add safe.directory D:/Neutralization.github.io
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>网上的建议一般是按照 git 的提示照做添加 &lt;code>safe.directory&lt;/code> ，或者就是文件夹右键 属性/安全/高级/更改所有者/☑替换子容器和对象的所有者 这样的一套操作。&lt;/p>
&lt;p>其实完全可以简化为：&lt;/p>
&lt;blockquote>
&lt;p>sudo takeown /R /F .&lt;br>
sudo icacls . /reset /q /t /c&lt;/p>
&lt;/blockquote>
&lt;p>留作记录。&lt;/p></description></item><item><title>OpenWrt 使用 WireGuard 组网及局域网文件共享设置</title><link>https://naizi.ch/2023/12/12/openwrt-%E4%BD%BF%E7%94%A8-wireguard-%E7%BB%84%E7%BD%91%E5%8F%8A%E5%B1%80%E5%9F%9F%E7%BD%91%E6%96%87%E4%BB%B6%E5%85%B1%E4%BA%AB%E8%AE%BE%E7%BD%AE/</link><pubDate>Tue, 12 Dec 2023 00:00:00 +0000</pubDate><guid>https://naizi.ch/2023/12/12/openwrt-%E4%BD%BF%E7%94%A8-wireguard-%E7%BB%84%E7%BD%91%E5%8F%8A%E5%B1%80%E5%9F%9F%E7%BD%91%E6%96%87%E4%BB%B6%E5%85%B1%E4%BA%AB%E8%AE%BE%E7%BD%AE/</guid><description>&lt;p>一直以来都是依靠群友/同事的帮助来配置当甩手掌柜，最近路由升级 OpenClash 又炸了，加上之前神秘的 DNS 转发失败的问题，决定从零开始自己重新配置一遍软路由，实践出真知。&lt;/p>
&lt;h2 id="前期准备">前期准备&lt;/h2>
&lt;p>硬件上用的是两台 FriendlyARM NanoPi R2S，闲鱼现在 200 出头就可以收到，主要是体积小巧，本来的需求也只是跑个 OpenWrt + WireGuard 组网，性能完全够用。家里的网络也没有千兆，不担心跑不满带宽的问题。&lt;/p>
&lt;p>因为是要打算跟公司里的设备组网，显然公司是不会给你 IP / 端口转发的，所以家里的宽带需要申请公网 IP + 桥接。之前买了台小米的 Redmi AX6S 用作家里的软路由，但刷机不如 R2S 方便，就刷回原厂用作 AP 了。&lt;/p>
&lt;p>考虑到公网 IP 拨号可能重新分配，最好还是绑定个域名做 DDNS，便于之后 WireGuard 通信，避免意外断电重启找不到 IP。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="openwrt-配置">OpenWrt 配置&lt;/h2>
&lt;p>因为 R2S 已经在 OpenWrt 的支持列表里了，这里简单从&lt;a class="link" href="https://firmware-selector.openwrt.org/" target="_blank" rel="noopener"
>官网&lt;/a>下一个镜像，目前的版本是 23.05.2。&lt;/p>
&lt;p>镜像选择了 SQUASHFS 格式，主要是为了方便重置配置备份升级。但缺点是默认的分区大小可能有些局促，需要进行一下扩容。&lt;/p>
&lt;p>刷写完毕启动后，SSH 进入 OpenWrt，执行以下命令：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 更换为国内镜像源加快下载&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sed -i &lt;span style="color:#e6db74">&amp;#39;s/downloads.openwrt.org/mirrors.ustc.edu.cn\/openwrt/g&amp;#39;&lt;/span> /etc/opkg/distfeeds.conf
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>opkg update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>opkg install cfdisk losetup f2fs-tools
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># https://github.com/anaelorlinski/OpenWrt-NanoPi-R2S-R4S-Builds/blob/main/docs/resize-f2fs.md&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cfdisk /dev/mmcblk0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 选择第二个分区，扩容，写入，退出&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LOOP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>losetup -n -O NAME | sort | sed -n -e &lt;span style="color:#e6db74">&amp;#34;1p&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ROOT&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>losetup -n -O BACK-FILE &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span> | sed -e &lt;span style="color:#e6db74">&amp;#34;s|^|/dev|&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>OFFS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>losetup -n -O OFFSET &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LOOP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>losetup -f&lt;span style="color:#66d9ef">)&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>losetup -o &lt;span style="color:#e6db74">${&lt;/span>OFFS&lt;span style="color:#e6db74">}&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>ROOT&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fsck.f2fs -f &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mount &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span> /mnt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>umount &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>resize.f2fs &lt;span style="color:#e6db74">${&lt;/span>LOOP&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>reboot
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>重启后扩容完成，可以继续 &lt;code>opkg install&lt;/code> 需要的包了。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 因为还是有后期加上 OpenClash 的打算，先替换 dnsmasq&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>opkg remove dnsmasq &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> opkg install dnsmasq-full
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># OpenClash 的依赖包&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>opkg install coreutils-nohup bash curl ca-certificates ipset ip-full libcap libcap-bin ruby ruby-yaml kmod-tun kmod-inet-diag unzip kmod-nft-tproxy luci-compat luci luci-base
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># WireGuard + 自己需要的包&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>opkg install luci-app-wireguard nano-full bind-dig
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两边的 OpenWrt 要分配不同的网段，访问 OpenWrt 页面（默认地址 192.168.1.1）：&lt;/p>
&lt;p>Network -&amp;gt; Interfaces -&amp;gt; Lan -&amp;gt; Edit -&amp;gt; IPv4 address&lt;/p>
&lt;p>这里举例家里设备为 &lt;code>192.168.10.1/24&lt;/code> 公司为 &lt;code>192.168.20.1/24&lt;/code>。&lt;/p>
&lt;h2 id="wireguard">WireGuard&lt;/h2>
&lt;p>前述已经安装好了 WireGuard，配置时最好能够同时访问两台设备，操作都是相同的。&lt;/p>
&lt;h3 id="添加新接口">添加新接口&lt;/h3>
&lt;p>Network -&amp;gt; Interfaces -&amp;gt; Add new interface&amp;hellip;&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Name: WireGuard
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Protocol: WireGuard VPN
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="设置新接口">设置新接口&lt;/h3>
&lt;p>Network -&amp;gt; Interfaces -&amp;gt; WireGuard -&amp;gt; Edit -&amp;gt; General Settings&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Listen Port: 50251
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>IP Addresses: 192.168.192.10
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的 IP 地址是 WireGuard 识别自身设备的地址，给两个设备设置在同一个子网 (192.168.192.0) 里就行，比如另一台设备设置为 &lt;code>192.168.192.20&lt;/code>，末位数和 LAN 的地址数字相同便于自己识别。&lt;/p>
&lt;p>点击 &lt;code>Generate new key pair&lt;/code>，复制生成的 &lt;code>Public Key&lt;/code> 记录下来，之后要用。&lt;/p>
&lt;p>转到 &lt;code>Firewall Settings&lt;/code>，选择 &lt;code>LAN&lt;/code>。&lt;/p>
&lt;p>然后再转到 &lt;code>Peers&lt;/code> 点击 &lt;code>Add peer&lt;/code> 添加对端设备。&lt;/p>
&lt;p>在 &lt;code>192.168.10.1&lt;/code> 上配置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Public Key: 192.168.20.1 的 `Public Key`
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allowed IPs: 192.168.192.20/24, 192.168.20.1/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Route Allowed IPs: ☑
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Endpoint Host: 留空
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Endpoint Port: 留空
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Persistent Keep Alive: 25
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 &lt;code>192.168.20.1&lt;/code> 上配置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Public Key: 192.168.10.1 的 `Public Key`
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allowed IPs: 192.168.192.10/24, 192.168.10.1/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Route Allowed IPs: ☑
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Endpoint Host: ddns.router.home（绑定了公网 IP 的 DDNS 域名）
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Endpoint Port: 50251
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Persistent Keep Alive: 25
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>两边配置完成后重启 WireGuard 接口，如无意外应该能看到握手数据，互相 ping 对面设备的 LAN 地址，正常返回数据则 WireGuard 已经正常工作。&lt;/p>
&lt;p>最后配置 watchdog，意外断电重启公网 IP 变动后 WireGuard 可以及时重新连接：&lt;/p>
&lt;blockquote>
&lt;p>echo &amp;lsquo;* * * * * /usr/bin/wireguard_watchdog&amp;rsquo; &amp;raquo; /etc/crontabs/root&lt;/p>
&lt;/blockquote>
&lt;h3 id="允许-wireguard-通过防火墙">允许 WireGuard 通过防火墙&lt;/h3>
&lt;p>Network -&amp;gt; Firewall -&amp;gt; Traffic Rules -&amp;gt; &lt;code>Add&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Name: Allow-WireGuard
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Protocol: UDP
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Source zone: WAN
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Destination zone: Device (input)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Destination port: 50251
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Action: accept
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="配置-dns-解析和转发">配置 DNS 解析和转发&lt;/h2>
&lt;p>有了 WireGuard 后最方便的事情就是可以在家访问公司内网服务，只要通过位于内网路由的 DNS 来解析内网地址就行。&lt;/p>
&lt;p>在内网也可以访问家里的设备，两边设置不同的域即可，对应的域转发给对应的 DNS 来解析。&lt;/p>
&lt;p>Network -&amp;gt; DHCP and DNS -&amp;gt; General Settings&lt;/p>
&lt;p>在 &lt;code>192.168.10.1&lt;/code> 上配置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Local server: /home/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Local domain: home
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DNS forwardings: /office/192.168.192.20
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Rebind protection: ☐
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 &lt;code>192.168.20.1&lt;/code> 上配置&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-text" data-lang="text">&lt;span style="display:flex;">&lt;span>Local server: /office/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Local domain: office
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DNS forwardings: /home/192.168.192.10
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Rebind protection: ☐
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要在家里（&lt;code>192.168.10.1&lt;/code>）访问内网时，按两步走：&lt;/p>
&lt;ol>
&lt;li>将对应的内网域名以 &lt;code>/abc.xyz/192.168.192.20&lt;/code> 的格式添加到 &lt;code>DNS forwardings&lt;/code> 中&lt;/li>
&lt;li>将对应的解析 IP/IP段 添加到对端 Peer 的 &lt;code>Allowed IPs&lt;/code> 中&lt;/li>
&lt;li>也可以直接将内网 DNS 地址添加到 &lt;code>Allowed IPs&lt;/code> 中，&lt;code>1&lt;/code> 中的 LAN IP 直接换成内网 DNS 地址。&lt;/li>
&lt;/ol>
&lt;p>这样就能用正确的 DNS 结果（通过 WireGuard 转发给公司上游 DNS 解析），访问到内网 IP 上的服务。&lt;/p>
&lt;p>通过 dig 命令可以查看是否正确转发了 DNS 请求。&lt;/p>
&lt;p>设置完成后可以通过 hostname.domain (比如 raspberry.office, router.home) 的方式访问两边的设备，不用再记难记的 IP 地址了。&lt;/p>
&lt;h2 id="主机防火墙策略">主机防火墙策略&lt;/h2>
&lt;p>通过上述方式建立了局域网之后，理所应当就可以在局域网里共享文件进行传输了，由于两边子网不同，需要一些简单的设置。&lt;/p>
&lt;p>首先打开当前连接网络的网络配置文件，看一下网络状态是&lt;code>公用&lt;/code>还是&lt;code>专用&lt;/code>网络。然后打开：&lt;/p>
&lt;ul>
&lt;li>Windows 10：控制面板\网络和 Internet\网络和共享中心\高级共享设置&lt;/li>
&lt;li>Windows 11：设置\网络和 Internet\高级网络设置\高级共享设置&lt;/li>
&lt;/ul>
&lt;p>在对应的配置文件里启用&lt;code>网络发现&lt;/code>和&lt;code>文件和打印机共享&lt;/code>，在&lt;code>所有网络&lt;/code>的配置文件里启用&lt;code>有密码保护的共享&lt;/code>。&lt;/p>
&lt;p>打开防火墙设置：&lt;/p>
&lt;ul>
&lt;li>Windows 10：控制面板\系统和安全\Windows Defender 防火墙\高级设置&lt;/li>
&lt;li>Windows 11：设置\网络和 Internet\高级网络设置\Windows 防火墙\高级设置&lt;/li>
&lt;/ul>
&lt;p>在&lt;code>入站规则&lt;/code>中找到&lt;code>文件和打印机共享(SMB-In)&lt;/code>（有多个，选择对应网络配置的那一个），双击打开属性，选择&lt;code>作用域&lt;/code>，在&lt;code>远程IP地址&lt;/code>处选择&lt;code>任何IP地址&lt;/code>，或者添加对应子网，比如访问家里设备的共享，就添加公司的子网地址&lt;code>192.168.20.0/24&lt;/code>。&lt;/p>
&lt;h2 id="局域网文件传输">局域网文件传输&lt;/h2>
&lt;p>上述设置完成后，比如我们在家里电脑上共享一个文件夹&lt;code>Share&lt;/code>：&lt;/p>
&lt;blockquote>
&lt;p>\\computer.home\Share&lt;/p>
&lt;/blockquote>
&lt;p>我就可以直接通过这个网络地址打开共享，输入账号密码认证后像本地文件夹一样传输文件了。&lt;/p>
&lt;p>至此，我终于不用受限于公司 VPN 的各种问题，也不需要靠 WSL 做桥梁，用最习惯的方式同步文件了。&lt;/p>
&lt;p>而且此前设置的 OpenSSH 也能互相正常访问，爽到。&lt;/p>
&lt;h2 id="一些额外的折腾">一些额外的折腾&lt;/h2>
&lt;p>完全是为了用 robocopy 而用 robocopy 的折腾，记录一下。&lt;/p>
&lt;p>原因还是 SCP 不够灵活，想要类似 rsync 的体验。其实就是需要一个差异同步的功能减少传输数据量节约时间，结果还得是 robocopy，而且 robocopy 支持形如 &lt;code>\\computer.home\Share&lt;/code> 的网络地址，完美。&lt;/p>
&lt;p>考虑到 robocopy 的 &lt;code>/compass&lt;/code> 参数需要&lt;a class="link" href="https://learn.microsoft.com/zh-cn/windows-server/storage/file-server/smb-compression" target="_blank" rel="noopener"
>条件支持&lt;/a>，批量小文件传输速度本身也会比较慢，而后者的影响比较大，打算利用 robocopy 导出差异列表，然后打包小文件变成一个大文件来传输提高效率。&lt;/p>
&lt;blockquote>
&lt;p>RoboCopy [source] [dist] &lt;em>.&lt;/em> /E /Z /COPY:DAT /DCOPY:T /XD __pycache__ node_modules venv .git .github /XO /IT /XJ /R:3 /W:3 /REG /NC /NS /NDL /NJH /NJS /LOG:diff.txt&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>7z a -t7z diff.7z -i@&amp;ldquo;diff.txt&amp;rdquo; -scsWIN -spf&lt;/p>
&lt;/blockquote>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>这下折腾爽了。&lt;/p></description></item><item><title>给 OpenWrt 加上 DDNS</title><link>https://naizi.ch/2023/06/06/%E7%BB%99-openwrt-%E5%8A%A0%E4%B8%8A-ddns/</link><pubDate>Tue, 06 Jun 2023 00:00:00 +0000</pubDate><guid>https://naizi.ch/2023/06/06/%E7%BB%99-openwrt-%E5%8A%A0%E4%B8%8A-ddns/</guid><description>&lt;p>给家里的宽带申请了公网 IP，为了方便连回家绑了自己的一个域名。虽说一般也不会关掉路由器，为防万一还是简单做个 DDNS 确保 IP 地址能更新。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;p>因为用的是主机壳的域名解析服务，先从&lt;a class="link" href="https://console.hostker.net/index.html#/Account" target="_blank" rel="noopener"
>后台&lt;/a>拿到 API 密钥。&lt;/p>
&lt;p>然后去域名解析管理添加一条新的 A 记录，指向家里路由的公网 IP 地址。&lt;/p>
&lt;p>简单&lt;a class="link" href="https://gist.github.com/Neutralization/aaf154b058450bb05894291d1c53bb32" target="_blank" rel="noopener"
>写个 bash 脚本&lt;/a>，搞定。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>domain&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;example.com&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;router&amp;#34;&lt;/span> &lt;span style="color:#75715e"># router.example.com &amp;lt;-&amp;gt; public ip address&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>email&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;*************************&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>token&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;*************************&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>trueip&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>ifconfig pppoe-wan | grep -Eo &lt;span style="color:#e6db74">&amp;#39;inet (addr:)?([0-9]*\.){3}[0-9]*&amp;#39;&lt;/span> | grep -Eo &lt;span style="color:#e6db74">&amp;#39;([0-9]*\.){3}[0-9]*&amp;#39;&lt;/span> | grep -v &lt;span style="color:#e6db74">&amp;#39;127.0.0.1&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>trueip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>trueip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">$(&lt;/span>cat /root/ip&lt;span style="color:#66d9ef">)&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;pass&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> getRecord&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;action\&amp;#34;: \&amp;#34;DomainDNS.getRecords\&amp;#34;, \&amp;#34;domain\&amp;#34;: \&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>domain&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>getRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sign&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>getRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | openssl sha256 -hmac &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>token&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/^.* //&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>sign&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> curl -sL -X POST &lt;span style="color:#e6db74">&amp;#34;https://api.hostker.net/v2&amp;#34;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#39;Content-Type: application/json&amp;#39;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#34;Sign: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>sign&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#34;Email: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>email&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -d &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>getRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -o records.json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> recordip&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>jq -r &lt;span style="color:#e6db74">&amp;#39;.result.records[] | select(.header==&amp;#34;&amp;#39;&lt;/span>$header&lt;span style="color:#e6db74">&amp;#39;&amp;#34;).value&amp;#39;&lt;/span> records.json&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>recordip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>trueip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>recordip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>recordip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &amp;gt; /root/ip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> old_record&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>jq -c -r &lt;span style="color:#e6db74">&amp;#34;.result.records[] | select(.header==\&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>header&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\&amp;#34;)&amp;#34;&lt;/span> records.json&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>old_record&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new_record&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;type\&amp;#34;:\&amp;#34;A\&amp;#34;,\&amp;#34;ttl\&amp;#34;:600,\&amp;#34;header\&amp;#34;:\&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>header&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\&amp;#34;,\&amp;#34;value\&amp;#34;:\&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>trueip&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\&amp;#34;}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>new_record&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> upRecord&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;{\&amp;#34;action\&amp;#34;: \&amp;#34;DomainDNS.updateRecord\&amp;#34;, \&amp;#34;domain\&amp;#34;: \&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>domain&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\&amp;#34;, \&amp;#34;old_record\&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>old_record&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">, \&amp;#34;new_record\&amp;#34;: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>new_record&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">}&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>upRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sign&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$(&lt;/span>echo -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>upRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | openssl sha256 -hmac &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>token&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/^.* //&amp;#39;&lt;/span>&lt;span style="color:#66d9ef">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>sign&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> curl -sL -X POST &lt;span style="color:#e6db74">&amp;#34;https://api.hostker.net/v2&amp;#34;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#39;Content-Type: application/json&amp;#39;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#34;Sign: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>sign&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -H &lt;span style="color:#e6db74">&amp;#34;Email: &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>email&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -d &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>upRecord&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> -o resp.json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接着去 OpenWrt 添加一条计划任务：&lt;/p>
&lt;blockquote>
&lt;p>opkg update&lt;br>
opkg install openssl-util jq&lt;br>
chmod +x /root/hostker_ddns.sh&lt;br>
crontab -e&lt;/p>
&lt;p>*/5 * * * * /root/hostker_ddns.sh&lt;/p>
&lt;/blockquote></description></item><item><title>记录一些和 Unicode 不断作斗争的日子</title><link>https://naizi.ch/2022/08/09/%E8%AE%B0%E5%BD%95%E4%B8%80%E4%BA%9B%E5%92%8C-unicode-%E4%B8%8D%E6%96%AD%E4%BD%9C%E6%96%97%E4%BA%89%E7%9A%84%E6%97%A5%E5%AD%90/</link><pubDate>Tue, 09 Aug 2022 00:00:00 +0000</pubDate><guid>https://naizi.ch/2022/08/09/%E8%AE%B0%E5%BD%95%E4%B8%80%E4%BA%9B%E5%92%8C-unicode-%E4%B8%8D%E6%96%AD%E4%BD%9C%E6%96%97%E4%BA%89%E7%9A%84%E6%97%A5%E5%AD%90/</guid><description>&lt;p>最开始给哔哩哔哩周刊写&lt;a class="link" href="https://github.com/Neutralization/BiliBiliRankingScripts" target="_blank" rel="noopener"
>&lt;code>自动化脚本&lt;/code>&lt;/a>的时候，还不觉得生成图片能遇到什么坑。毕竟之前也用过 Pillow 库，算是比较熟悉了。而且遇到的都是些在普通不过的正常文本。&lt;/p>
&lt;p>直到躲不开的 emoji 开始出现了。&lt;/p>
&lt;h2 id="一些普通的-emoji">一些普通的 Emoji&lt;/h2>
&lt;p>☀☁☂☃☄☎☑☔☕☘&lt;/p>
&lt;p>刚开始遇到这类也还好，因为很显然，只是文本的字体没有这部分 emoji，Pillow 就全部给显示成了方框。简单框了个范围，用另一个字体来渲染就是了。&lt;/p>
&lt;p>但后来就开始遇到各种标题里的花活，什么上下标字符花体字粗斜体全来了。比如&lt;/p>
&lt;blockquote>
&lt;p>&lt;a class="link" href="https://www.bilibili.com/video/BV1xV41167qm" target="_blank" rel="noopener"
>【𝟒𝐊 𝟔𝟎𝐅𝐏𝐒】这首《𝑭𝒂𝒍𝒍𝒊𝒏𝒈 𝑨𝒈𝒂𝒊𝒏》如今治愈了多少人！！! ℳ₯㎕-沉 沦&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>自此真正打响了与 Unicode 斗争的第一枪。&lt;/p>
&lt;h2 id="一些普通的-unicode">一些普通的 Unicode&lt;/h2>
&lt;p>上下标字符花体字粗斜体也能接受，人家看起来至少还是个正经字符。解决方法也是一样的简单粗暴：找一个定义了这部分字符的字体，单独匹配渲染。&lt;/p>
&lt;p>于是脚本里的 UnicodeA_F 慢慢增加到了 UnicodeD_F。划一个新的正则范围，匹配上一个新的字体，如此查漏补缺的工作也还能接受，毕竟遇到特殊字符的场合相对还是非常有限的。&lt;/p>
&lt;p>直到真正的重量级上场了。&lt;/p>
&lt;h2 id="combining-字符">Combining 字符&lt;/h2>
&lt;p>&lt;a class="link" href="https://www.bilibili.com/video/BV1U44y1E7Wm" target="_blank" rel="noopener"
>✥我҉͛̀̈̈̾̓̀͂̊͝的模҉̖̭̱͍̩͕͓̋̓͋̈̑͋̉͢͞ͅ样吓҈̎̍̅̒̎͂̈́̚͞.到你҈̛́̐̄́̃͗̓͒͒͊̿͛̒了？～❤✥&lt;/a>&lt;/p>
&lt;p>只能承认它杀死了比赛。但其实类似的标题并不是第一次见，在更早之前，我算是见过它的一位大前辈：&lt;/p>
&lt;p>&lt;a class="link" href="https://www.nicovideo.jp/watch/sm28869680" target="_blank" rel="noopener"
>&lt;img src="https://naizi.ch/pic/2022-08-09-fight-with-unicode/Snipaste_2022-08-10_16-32-58.png"
loading="lazy"
alt="【初音ミク】宇宙大规模 11 次元构造体【ATOLS】"
>&lt;/a>&lt;/p>
&lt;p>这位前辈更是重量级，即便在写这篇记录的时候，我也不得不佩服这种和视频契合的标题风格确实是非常到位。&lt;/p>
&lt;p>能做到这种效果的原因是由于其中大量的 Combining 字符，比如日文的清浊音符号，部分小语种的音标和一些独特的组词方式。&lt;/p>
&lt;p>但是 Pillow 罢工了呀，臣妾渲染不来，您还是另请高明吧。&lt;/p>
&lt;p>思来想去不得法，最后想到，那既然网页前端能够正常展示，应该可以通过无头浏览器前端渲染然后截屏的方式，把这些特殊字符正确的显示出来。&lt;/p>
&lt;p>简单糊了个页面，算是解决了这个问题。哔哩哔哩周刊与 Unicode 的斗争也算是告一段落了。&lt;/p>
&lt;h2 id="mad-周刊新的挑战">MAD 周刊，新的挑战&lt;/h2>
&lt;p>接手开始&lt;a class="link" href="https://github.com/Neutralization/MADRankingScripts" target="_blank" rel="noopener"
>&lt;code>MAD 周刊的自动化&lt;/code>&lt;/a>后，很快又不得不面对 Unicode 带来的问题。然而实际上，应该说是 Unicode 和 Adobe 共同带来的问题。&lt;/p>
&lt;p>为了保证原始 MAD 周刊工程文件的纯洁性，所有的文本都按照文本图层来进行修改。本来这也没有什么问题。&lt;/p>
&lt;p>但是原工程使用的大量表达式，是基于旧版的 ExtendScript 引擎，毕竟工程本身是 2016 年的产物。但之前做哔哩哔哩周刊时一直用的是 JavaScript 引擎的表达式，这就产生了几个问题：&lt;/p>
&lt;ol>
&lt;li>特殊的 Unicode 字符需要使用不同的字体才能正常显示&lt;/li>
&lt;li>ExtendScript 不能修改文本图层的部分字体样式属性&lt;/li>
&lt;li>相比 ExtendScript，JavaScript 能够修改字体字号颜色等属性&lt;/li>
&lt;li>为了使用 JavaScript 引擎就要把整个工程的其他表达式全部重写&lt;/li>
&lt;li>AfterEffects 对通过脚本修改字体的行为有奇怪的问题，不能保证正常显示&lt;/li>
&lt;/ol>
&lt;p>由于工作量太大，重写全部表达式显然不现实。基于此，我选择了另一种方式：让所有花活都变成正常的字符。&lt;/p>
&lt;p>这就需要有一个表来记录这些特殊字符应该被替换为具体哪一个字符，于是诞生了 &lt;a class="link" href="https://github.com/Neutralization/Antimemetics-Division" target="_blank" rel="noopener"
>&lt;code>Antimemetics-Division&lt;/code>&lt;/a> 这样一个对抗项目。&lt;/p>
&lt;p>期间则是枯燥的 Unicode 查表浏览过程，尽可能多的搜集这类特殊字符的标题来保证更广的覆盖面。&lt;/p>
&lt;p>而后 Emoji 的问题也需要处理，这就要划分 Emoji 的 Unicode 字符范围。幸好这部分有据可查，在 Unicode 官方规范中有详细的记录各个 Emoji。&lt;/p>
&lt;p>Combining 字符的问题也得到了解决，Unicode 官方规范中规定了 Mc Me Mn 三类 Mark 字符正好能够覆盖这个问题。&lt;/p>
&lt;p>最终 Antimemetics-Division 不负使命，让 MAD 周刊中的标题都成为了“正经标题”，可喜可贺，可喜可贺。&lt;/p>
&lt;p>&lt;del>虽然留下了反向利用此项目可以生成更加让人血压爆炸的标题的问题，暂且不提&lt;/del>&lt;/p>
&lt;h2 id="本可以更优雅的解决方式">本可以更优雅的解决方式&lt;/h2>
&lt;p>如是几期视频更新后，我逐渐意识到一种割裂感。&lt;/p>
&lt;p>为什么哔哩哔哩周刊就应该渲染正常，MAD 周刊就要去掉这些字符呢？这种区别对待说白了，不过还是一种懒惰。&lt;/p>
&lt;p>不同于我最初承认 ATOLS 玩的一手好标题，在 MAD 周刊上我反而以字符论标题优劣，不应是正确的解法。&lt;/p>
&lt;p>于是决定同哔哩哔哩周刊一样通过前端截图来解决这个问题。同时考虑到排版影响，部分 MAD 标题常用全角符号，导致标题的起始像素位置其实并不顶格，便在脚本中加入了一个粗略的判断，进行一个 X 轴的移位使得视觉上显得更对齐一些。&lt;/p>
&lt;p>但 Emoji 还是给我捣了乱，Emoji 因为都是全角宽度，无法判断像素位置是否顶格，这个问题只能留到之后再寻找合适的解法了。&lt;/p>
&lt;h3 id="update-20221003">Update 2022/10/03&lt;/h3>
&lt;p>成了，生成图片然后切左右透明像素就是了，&lt;a class="link" href="https://github.com/Neutralization/MADRankingScripts/commit/100cb976e83db6e9f45575e43742b2bed3ca598b#diff-627c0a0588769c50e32e1fc457e9c787835f1d5ad8ff6845142d32b63589b1ff" target="_blank" rel="noopener"
>问题解决&lt;/a>（&lt;/p>
&lt;h2 id="未尽之事">未尽之事&lt;/h2>
&lt;p>计划将哔哩哔哩周刊的图片生成也改为前端截图的方式。且不谈特殊字符的问题，前端这种方便可视化调整的形式，应该会对代码修改更友好一些，虽然想来是不会有什么改版和修改了。&lt;/p>
&lt;p>而且通过 CSS，还能够实现一些 Pillow 做不到的文本样式效果，更好的还原 PSD 的设计样式。&lt;/p>
&lt;p>曾经还想过写 Photoshop 脚本来做图，虽然还没试验过 Photoshop，考虑到前述 AfterEffects 文本图层的问题，以及前端截图确实更灵活，也就不再考虑了。&lt;/p></description></item><item><title>OpenWrt 折腾笔记</title><link>https://naizi.ch/2021/10/05/openwrt-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0/</link><pubDate>Tue, 05 Oct 2021 00:00:00 +0000</pubDate><guid>https://naizi.ch/2021/10/05/openwrt-%E6%8A%98%E8%85%BE%E7%AC%94%E8%AE%B0/</guid><description>&lt;h2 id="准备工作">准备工作&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>安装 WSL，不再赘述。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://openwrt.org/docs/guide-developer/build-system/wsl#setting_up_path" target="_blank" rel="noopener"
>移除 WSL 环境变量中包含的 Windows 路径&lt;/a>，为编译做准备。&lt;/p>
&lt;p>打开 WSL 终端，新建&lt;code>/etc/wsl.conf&lt;/code>文件，输入如下内容并保存。&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">[interop]
appendWindowsPath = false
&lt;/code>&lt;/pre>&lt;p>然后执行&lt;code>wsl --shutdown&lt;/code>，再重新启动 wsl，执行&lt;code>echo ${PATH}&lt;/code>检查是否含有 Windows 路径。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>更新系统&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>sudo&lt;/strong> apt update&lt;br>
&lt;strong>sudo&lt;/strong> apt full-upgrade -y&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://openwrt.org/docs/guide-developer/build-system/install-buildsystem#debianubuntu" target="_blank" rel="noopener"
>准备编译工具&lt;/a>&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>sudo&lt;/strong> apt install -y ack antlr3 asciidoc autoconf automake \&lt;br>
autopoint binutils bison build-essential bzip2 ccache cmake cpio \&lt;br>
curl device-tree-compiler fastjar flex gawk gettext gcc-multilib \&lt;br>
g++-multilib git gperf haveged help2man intltool libc6-dev-i386 \&lt;br>
libelf-dev libglib2.0-dev libgmp3-dev libltdl-dev libmpc-dev libmpfr-dev \&lt;br>
libncurses5-dev libncursesw5-dev libreadline-dev libssl-dev libtool \&lt;br>
lrzsz mkisofs msmtp nano ninja-build p7zip p7zip-full patch pkgconf \&lt;br>
python2.7 python3 python3-pip qemu-utils rsync scons squashfs-tools \&lt;br>
subversion swig texinfo uglifyjs upx-ucl unzip vim wget xmlto xxd zlib1g-dev&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;/ol>
&lt;!-- raw HTML omitted -->
&lt;h2 id="克隆仓库">克隆仓库&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>下载 OpenWrt 源码（更改为&lt;a class="link" href="https://github.com/coolsnowwolf/lede" target="_blank" rel="noopener"
>LEDE&lt;/a>）&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cd ~
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git clone https://github.com/coolsnowwolf/lede openwrt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd openwrt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git pull
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>下载 &lt;a class="link" href="https://github.com/vernesong/OpenClash" target="_blank" rel="noopener"
>OpenClash&lt;/a> 源码&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cd ~/openwrt/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir package/luci-app-openclash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cd package/luci-app-openclash
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git init
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git remote add origin https://github.com/vernesong/OpenClash.git
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git config core.sparsecheckout true
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo &lt;span style="color:#e6db74">&amp;#34;luci-app-openclash&amp;#34;&lt;/span> &amp;gt;&amp;gt; .git/info/sparse-checkout
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git pull --depth &lt;span style="color:#ae81ff">1&lt;/span> origin master
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>git branch --set-upstream-to&lt;span style="color:#f92672">=&lt;/span>origin/master master
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;li>
&lt;p>修改路由 IP 地址&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; sed -i &lt;span style="color:#e6db74">&amp;#39;s/192.168.1.1/192.168.2.1/g&amp;#39;&lt;/span> package/base-files/files/bin/config_generate
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;/li>
&lt;/ol>
&lt;h2 id="开始编译">开始编译&lt;/h2>
&lt;ol>
&lt;li>
&lt;p>更新 feeds&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>cd&lt;/strong> ~/openwrt/&lt;br>
./scripts/feeds update -a&lt;br>
./scripts/feeds install -a&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;li>
&lt;p>选择编译内容&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>cd&lt;/strong> ~/openwrt/&lt;br>
&lt;strong>make defconfig&lt;/strong>&lt;br>
&lt;strong>make menuconfig&lt;/strong>&lt;br>
./scripts/diffconfig.sh &amp;gt; diffconfig&lt;/p>
&lt;/blockquote>
&lt;p>diffconfig 后期可用于更新镜像时导入&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>cp&lt;/strong> diffconfig ~/openwrt/.config&lt;br>
&lt;strong>make defconfig&lt;/strong>&lt;br>
&lt;strong>make menuconfig&lt;/strong>&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-menuconfig" data-lang="menuconfig">Target System ---&amp;gt;
(X) Rockchip
Subtarget ---&amp;gt;
(X) RK33xx boards (64 bit)
Target Profile ---&amp;gt;
(X) FriendlyARM NanoPi R2S
Target Images ---&amp;gt;
[*] ext4
[*] squashfs
[*] GZip images
(128) Kernel partition size (in MB)
(2048) Root filesystem partition size (in MB)
Kernel modules ---&amp;gt;
Cryptographic API modules ---&amp;gt;
ALL
LuCI ---&amp;gt;
2. Modules ---&amp;gt;
Translations ---&amp;gt;
&amp;lt;*&amp;gt; Simplified Chinese (zh-cn)
&amp;lt;*&amp;gt; luci-compat
3. Applications ---&amp;gt;
&amp;lt;*&amp;gt; luci-app-openclash
&amp;lt;*&amp;gt; luci-app-turboacc
&amp;lt;*&amp;gt; luci-app-unblockmusic
[*] UnblockNeteaseMusic NodeJS Version
&amp;lt;*&amp;gt; luci-app-wireguard
&amp;lt;*&amp;gt; luci-app-zerotier
4. Themes ---&amp;gt;
&amp;lt;*&amp;gt; luci-theme-argon
Utilities ---&amp;gt;
Editors ---&amp;gt;
&amp;lt;*&amp;gt; nano-full
&lt;/code>&lt;/pre>&lt;/li>
&lt;li>
&lt;p>执行编译&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>cd&lt;/strong> ~/openwrt/&lt;br>
&lt;strong>make&lt;/strong> -j8 download&lt;br>
&lt;strong>make&lt;/strong> -j8&lt;/p>
&lt;/blockquote>
&lt;/li>
&lt;/ol>
&lt;p>编译生成的镜像文件在 &lt;code>~/openwrt/bin/targets/rockchip/armv8/&lt;/code> 路径下&lt;/p>
&lt;h2 id="收尾工作">收尾工作&lt;/h2>
&lt;p>opkg 安装部分官方包的时候会提示 Kernel 版本不对&lt;code>kernel is not compatible&lt;/code>，这是因为自己编译的 kernel 指纹和官方不一致造成的，&lt;a class="link" href="https://github.com/iyuangang/openwrt/issues/8#issuecomment-605431578" target="_blank" rel="noopener"
>替换成官方指纹即可&lt;/a>。&lt;/p>
&lt;p>比如目前 kernel 版本是&lt;code>5.4.143-1-fb881fbbae69f30da18e7c6eb01310c1&lt;/code>&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>sed&lt;/strong> -i s/&lt;code>编译的kernel hash&lt;/code>/fb881fbbae69f30da18e7c6eb01310c1/g /usr/lib/opkg/status&lt;/p>
&lt;/blockquote>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>编译 run 了一万年，再不写点什么记下来，下次编译根本记不住了.jpg&lt;/p>
&lt;p>后来参考学习了 P3TERX 的 &lt;a class="link" href="https://p3terx.com/archives/build-openwrt-with-github-actions.html" target="_blank" rel="noopener"
>这篇文章&lt;/a> 利用 Github Action 进行编译，并且按照 &lt;a class="link" href="https://github.com/coolsnowwolf/lede/pull/7796" target="_blank" rel="noopener"
>LEDE 的这个 PR&lt;/a> 打开了缓存工具链，再也不用面对奇怪的环境问题和该参数重编又是四、五个小时的窘境了。&lt;/p></description></item><item><title>升级 3D 打印机固件及远程打印方案</title><link>https://naizi.ch/2021/08/28/%E5%8D%87%E7%BA%A7-3d-%E6%89%93%E5%8D%B0%E6%9C%BA%E5%9B%BA%E4%BB%B6%E5%8F%8A%E8%BF%9C%E7%A8%8B%E6%89%93%E5%8D%B0%E6%96%B9%E6%A1%88/</link><pubDate>Sat, 28 Aug 2021 00:00:00 +0000</pubDate><guid>https://naizi.ch/2021/08/28/%E5%8D%87%E7%BA%A7-3d-%E6%89%93%E5%8D%B0%E6%9C%BA%E5%9B%BA%E4%BB%B6%E5%8F%8A%E8%BF%9C%E7%A8%8B%E6%89%93%E5%8D%B0%E6%96%B9%E6%A1%88/</guid><description>&lt;p>Ender 3 不愧是创想三维家的经典款 3D 打印机，一直没关注产品消息，不知道什么时候已经出了 V2 款了。&lt;/p>
&lt;p>买当然是不会买的，原来的版本还是挺好用，购买的时候正好京东打折，当时 1k 出头的价格还是很香的。&lt;/p>
&lt;p>买来时间也有点久了，考虑升级一下固件，但是官方并没有提供这一款的后续固件升级，也没有相关教程。搜索了一下发现还需要自行刷入 bootloader 后才能刷写固件，看来是当时就没有考虑用户侧的后续升级。&lt;/p>
&lt;p>升级固件的想法主要是因为看到了 OctoPrint，一个利用树莓派控制和实时监控 3D 打印机的项目。&lt;/p>
&lt;h2 id="刷写-bootloader">刷写 bootloader&lt;/h2>
&lt;p>主要是跟着&lt;a class="link" href="https://www.th3dstudio.com/hc/guides/bootloader/bootloader-flashing-guide-cr-10-ender-2-3-5-wanhao-i3-anet-1284p-boards/" target="_blank" rel="noopener"
>Bootloader Flashing Guide&lt;/a>这篇文章中的指导。因为主板芯片是 1284P，和其他打印机的刷写存在区别，建议直接下载链接中的刷写包来操作。&lt;/p>
&lt;p>概括的来讲，需要 Arduino 开发板作为中介，与打印机主板的刷写接口连接，再通过 Arduino IDE 将 bootloader 引导程序由 Arduino 刷写到打印机主板芯片上。&lt;/p>
&lt;p>找同事借了一块 Arduino MEGA 2560 和几根杜邦线，因为 Arduino 的不同版本引脚定义几乎没有区别，也不是一定要用 Arduino Uno。&lt;/p>
&lt;p>完成这一步后，打印机有了 bootloader，就可以方便的进行固件升级操作了。&lt;/p>
&lt;h2 id="编译固件并上传至打印机">编译固件并上传至打印机&lt;/h2>
&lt;p>&lt;a class="link" href="https://www.th3dstudio.com/hc/downloads/unified-2-firmware/creality/creality-ender-3-firmware-melzi-board/" target="_blank" rel="noopener"
>TH3D&lt;/a> 给出了 Ender 3 的完整固件升级流程。&lt;/p>
&lt;p>真的是个不错的网站，除了 Creality 还有其他很多常见 3D 打印机厂家不同型号的操作教程。根据教程描述，安装 VSCode，编译固件上传即可，实际操作中我也完全没有遇到阻碍，非常顺利的编译上传完成了，见到了启动画面的 TH3D 标志 logo。除了界面是全英文，但这也并不影响机上操作。&lt;/p>
&lt;h2 id="octoprint-远程打印">OctoPrint 远程打印&lt;/h2>
&lt;p>又是一个利用树莓派的项目，于是我的树莓派又经历了一次 SD 卡格式化重写映像。&lt;/p>
&lt;p>同样也是完全跟随&lt;a class="link" href="https://howchoo.com/g/y2rhnzm3odz/control-your-3d-printer-with-octoprint-and-raspberry-pi" target="_blank" rel="noopener"
>教程&lt;/a>操作，在树莓派上使用 OctoPrint 映像，连接 WiFi，USB 连接打印机。&lt;/p>
&lt;p>教程中还提供了一套用于固定树莓派摄像机的 3D 打印文件，可以固定在打印机 X 轴上用来监测打印状态。也可以选择使用普通的 USB 摄像头，但是架设摄像头的方式就要靠自己另外实现了。&lt;/p>
&lt;p>正好很早以前买过一个树莓派专用摄像头，这就给它用上了。&lt;/p>
&lt;h2 id="finish">Finish&lt;/h2>
&lt;p>一切设置好，https 访问树莓派 IP 或者 &lt;a class="link" href="https://octopi.local/" target="_blank" rel="noopener"
>https://octopi.local/&lt;/a> ，如果摄像头正常的话，就能看到打印状态了。&lt;/p>
&lt;p>界面里可以一览无余的看到打印机当前的喷嘴和热床温度，打印状态，Gcode 图示等等。也可以直接通过网页上传 Gcode 文件直接打印，由于是通过 USB 连接到打印机，免去了反复插拔 SD 卡传输文件的步骤，真的是方便了很多。&lt;/p>
&lt;p>OctoPrint 还提供视频录制，开启后可以录制打印过程中的摄像头画面，生成延时视频。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2021-08-28-upgrade-3d-printer/Snipaste_2021-08-28_14-55-44.png"
loading="lazy"
alt="Timelapse"
>&lt;/p></description></item><item><title>圆周率里的麻将</title><link>https://naizi.ch/2021/03/17/%E5%9C%86%E5%91%A8%E7%8E%87%E9%87%8C%E7%9A%84%E9%BA%BB%E5%B0%86/</link><pubDate>Wed, 17 Mar 2021 00:00:00 +0000</pubDate><guid>https://naizi.ch/2021/03/17/%E5%9C%86%E5%91%A8%E7%8E%87%E9%87%8C%E7%9A%84%E9%BA%BB%E5%B0%86/</guid><description>&lt;p>2 月的时候，群友看 VTB 直播雀魂，发现主播为了防止互动的时候窥屏看到手牌，PS 了一张看起来像手牌的图来挡住真实的手牌。&lt;/p>
&lt;p>群友受此启发，纷纷讨论自己也要这么玩，PS 各种役种的牌扮老虎，比如大家都喜欢的国士无双：&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2021-03-17-mahjong-in-pai/19m19p19s1234567z_1p.png"
loading="lazy"
alt="国士无双"
>&lt;/p>
&lt;p>大家云集响应，因为之前正好写过&lt;a class="link" href="https://github.com/Neutralization/majsoul-generator" target="_blank" rel="noopener"
>生成器&lt;/a>，趁此机会我就说群友要啥我就帮群友 PS，都可以做。后来群友觉得正经的役没意思，想搞点不一样的，说要搞个圆周率。这就有意思了。话题很自然的转到了那能不能胡牌的问题上，因为只有数字，先默认牌型是清一色，一试，发现小数点后最开始的 14 位数字就真的能凑成一副胡牌：&lt;/p>
&lt;blockquote>
&lt;p>3.&lt;code>14159265358979&lt;/code> -&amp;gt; 234 678 555 999 11&lt;/p>
&lt;/blockquote>
&lt;!-- raw HTML omitted -->
&lt;p>我立刻兴致来了，马上就想看看位数再往后，还能有哪里能凑成胡牌，这就开始写脚本。&lt;/p>
&lt;p>圆周率位数的迭代之前写飞花令的时候用过，Ctrl+V 过来，然后是判断是否和牌的算法。简单来说按照一般和牌的标准型&lt;code>n*AAA+m*ABC+DD=14&lt;/code>，通过先取雀头&lt;code>DD&lt;/code>，然后交换先取刻子&lt;code>AAA&lt;/code>后顺子&lt;code>ABC&lt;/code>或者先顺子后刻子的逻辑迭代完整副手牌，看残张是否为零就能确认这副手牌是否和牌。&lt;/p>
&lt;p>写完代码，测试了几次，又另外加入了七对子牌型的判断，然后以一万位为目标执行了一遍。不出意料还真的有很多位置的数列能够凑成和牌。感觉这还是值得&lt;a class="link" href="https://www.bilibili.com/video/BV1pU4y1a7W8" target="_blank" rel="noopener"
>做个视频&lt;/a>记录一下。&lt;/p>
&lt;p>视频里只迭代了小数点后一万位的数字。在测试脚本的时候，其实试验过迭代到二十万位，已经相当耗时了，不过也因此遇到了一些比较值得一说的牌型：&lt;/p>
&lt;p>比如遇到的第一个&lt;code>平和&lt;/code>是在第 209 位小数，数列是&lt;code>7 5 6 6 5 9 3 3 4 4 6 1 2 8&lt;/code>，牌型是&lt;code>66 123 345 456 789&lt;/code>，还是一个&lt;code>一气贯通&lt;/code>&lt;/p>
&lt;p>&lt;code>断幺九&lt;/code>在第 222 位小数，数列是&lt;code>8 4 7 5 6 4 8 2 3 3 7 8 6 7&lt;/code>，牌型是&lt;code>66 777 888 234 345&lt;/code>&lt;/p>
&lt;p>&lt;code>四暗刻&lt;/code>在第 5391 位小数，数列是&lt;code>8 8 2 8 1 6 1 3 3 2 3 1 6 6&lt;/code>，牌型是&lt;code>22 111 333 666 888&lt;/code>&lt;/p>
&lt;p>&lt;code>二杯口&lt;/code>在第 15912 位小数，数列是&lt;code>8 5 7 4 5 6 9 8 1 4 7 1 9 6&lt;/code>，牌型是&lt;code>11 456 456 789 789&lt;/code>，这也是第一个七对子牌型（但规则只计算二杯口），下一个七对子牌型在 16834 位，但同样也是二杯口，再下一个则是 21513 的一色双龙会，国标役种。真正的七对子要到 35441 位才能遇到，&lt;code>6 4 6 4 5 5 1 2 7 8 1 2 7 8&lt;/code>，牌型&lt;code>11 22 44 55 66 77 88&lt;/code>&lt;/p>
&lt;p>&lt;code>一色双龙会&lt;/code>在第 21513 位小数，数列是&lt;code>5 7 2 3 9 7 3 9 5 1 2 8 8 1&lt;/code>，牌型是&lt;code>123 123 55 789 789&lt;/code>&lt;/p>
&lt;p>&lt;code>九莲宝灯&lt;/code>在第 32452 位小数，数列是&lt;code>9 2 1 9 5 1 9 6 7 5 1 4 8 3&lt;/code>，牌型是&lt;code>111 234 55 678 999&lt;/code>，还是个最漂亮的九莲宝灯。&lt;/p>
&lt;p>&lt;code>四暗刻绿一色&lt;/code>在第 162552 位小数，数列是&lt;code>4 2 4 3 3 3 4 8 8 2 2 6 6 6&lt;/code>，牌型是&lt;code>222 333 444 666 88&lt;/code>，显然这还是个纯绿一色。&lt;/p>
&lt;p>最后回顾下一切的开端：&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2021-03-17-mahjong-in-pai/screenshot.jpg"
loading="lazy"
alt="开端"
>&lt;/p></description></item><item><title>自动化制作周刊哔哩哔哩排行榜</title><link>https://naizi.ch/2021/03/07/%E8%87%AA%E5%8A%A8%E5%8C%96%E5%88%B6%E4%BD%9C%E5%91%A8%E5%88%8A%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E6%8E%92%E8%A1%8C%E6%A6%9C/</link><pubDate>Sun, 07 Mar 2021 00:00:00 +0000</pubDate><guid>https://naizi.ch/2021/03/07/%E8%87%AA%E5%8A%A8%E5%8C%96%E5%88%B6%E4%BD%9C%E5%91%A8%E5%88%8A%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E6%8E%92%E8%A1%8C%E6%A6%9C/</guid><description>&lt;p>总结一下周刊哔哩哔哩排行榜制作流程自动化过程中遇到的一些问题。&lt;/p>
&lt;p>之前的周刊制作过程中，组员各自领取不同部分的制作任务，每一部分的内容格式包含在不同的 yml 文件中。&lt;/p>
&lt;p>通常先把 yml 中的 av 号取出，去下载对应的视频到本地，然后人工浏览选择实际制作时选取的片段，记录下片段的开始时间，修改 yml 中的对应字段。然后上传修改完成的 yml，转换成之后导入 Vegas 时需要的 xml 文件。&lt;/p>
&lt;p>由于旧流程的局限性，没有考虑到视频尺寸的适配，因此在导入 Vegas 后，往往要手动对齐所有视频。另外由于不同部分的效果不一致（比如是否有过渡场景），也没有在 yml 中进行区分，往往还要再手动调整。工作量本质不大，但其实都是可以进行优化的场景。&lt;/p>
&lt;p>早几年的时候一直听说 Adobe After Effects 脚本如何如何强大，表达式如何如何好用。怎奈 javascript 从未接触，一直没能下决心去了解。这次也算是终于下了决心，开始了 AE 自动化制作周刊之路。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="vegas---aftereffects">Vegas -&amp;gt; AfterEffects&lt;/h2>
&lt;h3 id="流程整理">流程整理&lt;/h3>
&lt;p>首先是重新整理一遍 Vegas 的工作流，尽可能符合原来人工的操作顺序，也方便我理清代码逻辑。&lt;/p>
&lt;p>Vegas 的导入视频过程，使用的是 xml 还是 yml 文件其实没有本质的区别，需要的内容无非是导入哪个视频的哪个时间点，时长多久，视频如何定位到准确的位置，以及导入对应的图片素材，添加视频过渡效果等等。&lt;/p>
&lt;p>确定基本顺序后，我就开始对照 Adobe 的文档，一步步的编写代码。有一说一，Adobe 的文档确实非常详细，比 Vegas 的文档看起来是清晰多了（最初的确考虑过直接用 Vegas 脚本来做）。&lt;/p>
&lt;p>最初先写死了各种变量来进行调试，基本是写一行测试一行的方式。逐步学会了 AE 中如何使用脚本添加合成，再往合成中添加视频/图片层，调整视频的入点和出点等等。熟悉了之后，由于一个个视频的导入其实是非常模块化的操作，在复制粘贴了十几次代码后，我终于意识到应该把它抽象成函数调用。&lt;/p>
&lt;h3 id="编写函数调用">编写函数调用&lt;/h3>
&lt;p>流程已经很清晰了，也熟悉了代码，函数要实现的事情其实很简单：&lt;/p>
&lt;ol>
&lt;li>添加一个新图层，指定视频文件和时长节点&lt;/li>
&lt;li>给视频图层添加时间出入点和过渡效果&lt;/li>
&lt;li>给视频图层添加音频过渡效果&lt;/li>
&lt;/ol>
&lt;p>于是我分别编写了 &lt;code>AddLayer&lt;/code>, &lt;code>AddVideoProperty&lt;/code>, &lt;code>AddAudioProperty&lt;/code> 三个函数，包装了 Adobe 的 &lt;code>layers.add&lt;/code> 和 &lt;code>property.setValueAtTime&lt;/code> 两个方法。&lt;/p>
&lt;p>在编写函数的过程中，因为是 Vegas-&amp;gt;AfterEffects 复刻，Vegas 中的过渡曲线我在 AE 中没有找到一致的傻瓜式功能，只能自己去实现曲线的效果。这里就遇到了 Adobe 文档“点到为止”的问题：它没有提供详细的示例代码。&lt;/p>
&lt;p>原本是计划使用 AE 表达式（区别于 AE 脚本）来实现的，但是文档中对于如何添加表达式并没有解释的很详细，Google 和 StackOverflow 也没能提供有价值的信息。反复翻阅文档之后，我决定采用其他的方式来实现：通过微分法添加透明度/音频增益关键帧来逼近曲线效果。&lt;/p>
&lt;p>于是 &lt;code>AddVideoProperty&lt;/code>, &lt;code>AddAudioProperty&lt;/code> 的主要代码就变成了基于三角函数的 &lt;code>property.setValueAtTime&lt;/code>，复习正/余弦曲线方程的过程还真是有些令人怀念 (￣_,￣ )&lt;/p>
&lt;h3 id="函数的函数">函数的函数&lt;/h3>
&lt;p>对单个视频的函数完成之后，意识到单个 yml 文件其实就是一个更大的函数。&lt;/p>
&lt;p>这时需要解决的就是之前提到的对于过渡场景有无的判断。为了能够将工作流无缝转换到 AE，此时不可能再对 yml 做新的要求，好在数量不多，这部分区别直接手工指定写死在代码中即可。&lt;/p>
&lt;p>同时为了减少重复的代码，将原本写在 &lt;code>AddVideoProperty&lt;/code> 函数中的视频定位部分的代码放到了这个大函数 &lt;code>AddRankPart&lt;/code> 中，通过 for 循环迭代 yml 列表（这时其实还是一个写死的列表，而不是读取 yml 文件）的数据，对视频进行长宽比的判断同比缩放，再定位至正确的坐标。&lt;/p>
&lt;p>另外为了兼容周刊的排名规则，即长期视频的出现会造成视频数量的变化，以及每部分排名中最后一个视频过渡效果与其他不同，和视频间是否需要过渡画面等做了相应的判断，最后通过额外的函数参数兼容了不同样式的榜单部分。&lt;/p>
&lt;p>但由于主榜前三实在比较特殊，就没有使用 &lt;code>AddRankPart&lt;/code> 而是直接重写了一遍逻辑。&lt;/p>
&lt;blockquote>
&lt;h4 id="插曲">插曲&lt;/h4>
&lt;p>在编写完整个自动化代码后，遇到了久未见到的视频源失效的特殊样式，需要在失效的视频上添加遮挡。Vegas 时期这也是靠人工完成的，因为失效视频已经极少遇到，采用的方案是添加一个 array 记录失效的视频，在 &lt;code>AddRankPart&lt;/code> 的 for 循环中添加视频是否在 array 中的判断来解决。这就需要当出现此类情况的时候修改代码中的变量。&lt;/p>
&lt;p>但新增一个文件来记录也是同样要人工修改，罢了，就这样吧。&lt;/p>
&lt;/blockquote>
&lt;h3 id="定型文">定型文&lt;/h3>
&lt;p>榜单各个部分间的过渡画面都是不随榜单变化，或者素材文件名保持固定，这部分内容就直接使用 &lt;code>AddLayer&lt;/code>, &lt;code>AddVideoProperty&lt;/code>, &lt;code>AddAudioProperty&lt;/code> 写死了逻辑和时间点。&lt;/p>
&lt;p>之后把写死的测试数据改写成读取实际的 yml 文件内容和素材文件，然后就是大量的 MagicNumber 时间点和不断地调试。为了测试兼容性还试验了 AfterEffects 的不同版本，结果发现不同版本的 AE 使用的 js 版本大相径庭，各种神秘问题频出。最后还是放弃了去追求更高的兼容性，优先保障了较新版本的 2019CC 和 2020CC 的使用。&lt;/p>
&lt;h2 id="aftereffects-的问题">AfterEffects 的问题&lt;/h2>
&lt;h3 id="音频处理">音频处理&lt;/h3>
&lt;p>AE 作为视频软件，对音频的处理就显得较弱了，这本来不是什么大问题。&lt;/p>
&lt;p>但是上榜的视频各自的音频增益相差很大，最为明显的是 UP 主的投稿视频和 B 站番剧的动画，后者音频增益比前者低了一个等级。&lt;/p>
&lt;p>Vegas 中可以直接通过“音频标准化”操作简单解决这个问题。AE 其实也可以通过 Adobe 家族的兼容性，将工程导出使用 Premiere 或者 Audition 进行音频标准化，但这样一来就失去了“自动化”的目的。&lt;/p>
&lt;p>那么有什么其他办法可以解决么？在咨询群友和同事的意见之后，&lt;a class="link" href="https://github.com/slhck/ffmpeg-normalize" target="_blank" rel="noopener"
>ffmpeg-normalize&lt;/a>进入了我的视野。&lt;/p>
&lt;h3 id="ffmpeg-normalize">ffmpeg-normalize&lt;/h3>
&lt;p>简单而言，ffmpeg-normalize 是对 ffmpeg 中音量处理的一层包装，将整个过程自动化便于进行批量化的操作。&lt;/p>
&lt;p>试验之后发现果然方便，于是立刻开始编写代码。因为 CMD 过于简陋，选择了 powershell 来进行批处理。&lt;/p>
&lt;p>然而在试运行一周后，发现大文件处理效率是个问题。对于动辄 600MB~1G 的番剧区视频，ffmpeg-normalize 处理整个文件所消耗的时间甚至超过了最后 AE 导出视频的时间，这就比较难接受了。而且它也不支持只对视频/音频的某一部分进行标准化操作，非常尴尬。&lt;/p>
&lt;h3 id="ffmpeg-视频截取音频处理">ffmpeg 视频截取&amp;amp;音频处理&lt;/h3>
&lt;p>既然 ffmpeg-normalize 是 ffmpeg 的包装，自然想到了直接用 ffmpeg 不也可以吗？还能少装一个 python，降低了其他组员的学习成本。&lt;/p>
&lt;p>为了提升处理效率，与其处理整个视频，不如只处理选取的视频片段。于是在刚刚学习完 JavaScript 后，我又开始翻阅 powershell 的文档，研究怎样用 powershell 实现读取 yml 中的数据，传递给 ffmpeg 截取视频，并标准化截取视频的音频。&lt;/p>
&lt;p>这种不同语言间的语法差异着实又烦恼了我一阵子，好在微软的文档也很详尽，绕了几次弯路后找到了正确的写法，实现了需求。&lt;/p>
&lt;p>考虑到现在大家基本上都有不错的显卡，调用 ffmpeg 截取时使用 GPU 编/解码又能进一步提升效率，但使用 GPU 不能发挥 powershell 的并发能力，这受制于显卡的硬件，但仍比 CPU 编码快上不少。&lt;/p>
&lt;p>然而在音频标准化处理上，GPU 就没有用武之地了，只能依赖 CPU 处理，与此同时可以依靠 powershell 的 &lt;code>ForEach-Object -Parallel&lt;/code> 并发加快处理速度。只能说这两者算是有失有得吧。&lt;/p>
&lt;blockquote>
&lt;h4 id="对应修改">对应修改&lt;/h4>
&lt;p>视频先行截取意味着之前 AE 脚本中读取的视频时间点全部无效了，于是修改了对应的变量在读取数据后直接重新赋值为零，纠正了时间问题。&lt;/p>
&lt;/blockquote>
&lt;h2 id="powershell-工作流">PowerShell 工作流&lt;/h2>
&lt;p>既然 powershell 并发这么好用，我又动了新的歪脑筋，想起了之前 python 写的 B 站视频下载工具。&lt;/p>
&lt;p>因为已经尝试重构过 bash 的代码，在熟悉了 powershell 的部分函数后，这部分重构也是轻车熟路。很快一个读取分析 yml 文件自动下载视频的 powershell 脚本就编写完成了。&lt;/p>
&lt;p>不仅并发下载的代码逻辑比 python 简洁多了，甚至又填坑了之前想要指定下载分 P 的 TODO。另外 python 版的代码在下载时调用了 aria2c，powershell 的重构中也放弃了这部分依赖，如此一来整个工作流的外部依赖就只有 ffmpeg 了。&lt;/p>
&lt;p>为了完全抛弃 python 脚本的依赖，周刊使用的生成传送门评论的脚本（没错，这个也是 python）当然也要用 powershell 重构，事情到了这一步已经无法停下脚步了（ry&lt;/p>
&lt;p>最后，&lt;code>download.ps1&lt;/code>, &lt;code>normailze.ps1&lt;/code>, &lt;code>rankdoor.ps1&lt;/code> 组成了周刊自动化中 powershell 工作流的三剑客。&lt;/p>
&lt;h2 id="未尽之事">未尽之事&lt;/h2>
&lt;p>如此一来，制作周刊还剩下的人工操作就只有选取视频片段一项了。但是这也已经有了尝试性的解决方案（&lt;a class="link" href="https://www.bilibili.com/video/BV1pN411o7Cg?p=2" target="_blank" rel="noopener"
>Artificial Idiot 截取片段试运行&lt;/a>），目前仍是个 python 脚本，也许成熟之后也会再重构为 powershell，到时就是四剑客了。&lt;/p>
&lt;p>如果你对以上内容有兴趣，相关脚本&lt;a class="link" href="https://github.com/Neutralization/BiliBiliRankingScripts" target="_blank" rel="noopener"
>在 Github 已开源&lt;/a>。如果你不知道周刊哔哩哔哩排行榜，你也可以选择&lt;a class="link" href="https://space.bilibili.com/398300398" target="_blank" rel="noopener"
>关注了解一下&lt;/a>。&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>powershell 真香（&lt;/p></description></item><item><title>试做了一个明日方舟的小夜灯</title><link>https://naizi.ch/2021/01/12/%E8%AF%95%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E6%98%8E%E6%97%A5%E6%96%B9%E8%88%9F%E7%9A%84%E5%B0%8F%E5%A4%9C%E7%81%AF/</link><pubDate>Tue, 12 Jan 2021 00:00:00 +0000</pubDate><guid>https://naizi.ch/2021/01/12/%E8%AF%95%E5%81%9A%E4%BA%86%E4%B8%80%E4%B8%AA%E6%98%8E%E6%97%A5%E6%96%B9%E8%88%9F%E7%9A%84%E5%B0%8F%E5%A4%9C%E7%81%AF/</guid><description>&lt;p>之前也有在尝试用 3D 打印做奇怪的东西，建模一直在用的是 AutoDesk 家的线上工具&lt;a class="link" href="https://www.tinkercad.com/" target="_blank" rel="noopener"
>Tinkercad&lt;/a>，属于那种拖拖拽拽就能拼出差不多模型的工具，还挺好用。&lt;/p>
&lt;p>这次尝试做小夜灯，原本是打算做成游戏关卡结算时的三星标志，后来想了想，为了更显而易见的突出方舟元素，用了游戏里医疗干员的标志。主要因为医疗干员的标志几何对称性很好，估摸着可以偷懒。&lt;/p>
&lt;p>软件用了同事推荐的&lt;a class="link" href="https://www.openscad.org/" target="_blank" rel="noopener"
>OpenSCAD&lt;/a>，通过代码的方式来进行建模。我基本上是一边查文档一边写，很容易就上手找到需要的功能写出来了。&lt;/p>
&lt;p>除了没有代码自动格式化这一点略有不习惯，代码逻辑支持实体的交并补操作，渲染后可以意见导出 stl 格式文档进行后续打印工作，实在是方便的很。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;p>放两张成果图，使用的是创想三维的 Ender-3 打印机。冬天材料热涨冷缩比较严重结果留下了两道印子，有点可惜。&lt;/p>
&lt;p>电池盒是圣诞节的时候那种细绳装饰灯的电池，考虑到亮度和分散光源，接了一个 COB 灯板。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2021-01-12-arknight-lamp/battery.jpg"
loading="lazy"
alt="lamp"
>&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2021-01-12-arknight-lamp/lamp.jpg"
loading="lazy"
alt="lamp"
>&lt;/p>
&lt;p>工程文件放在&lt;a class="link" href="https://github.com/Neutralization/Arknights-lamp" target="_blank" rel="noopener"
>github&lt;/a>上了。&lt;/p></description></item><item><title>Python 使用 USB 热敏打印机</title><link>https://naizi.ch/2020/07/07/python-%E4%BD%BF%E7%94%A8-usb-%E7%83%AD%E6%95%8F%E6%89%93%E5%8D%B0%E6%9C%BA/</link><pubDate>Tue, 07 Jul 2020 00:00:00 +0000</pubDate><guid>https://naizi.ch/2020/07/07/python-%E4%BD%BF%E7%94%A8-usb-%E7%83%AD%E6%95%8F%E6%89%93%E5%8D%B0%E6%9C%BA/</guid><description>&lt;p>通过使用 python-escpos 库，就可以和常见的 USB 热敏打印机通信来进行打印，不再赘述具体方法。&lt;/p>
&lt;p>但是 python-escpos 库并不是为 Windows 写的，实际在使用的时候就会出现许多的问题。&lt;/p>
&lt;p>一个常见的错误是&lt;/p>
&lt;blockquote>
&lt;p>usb.core.NoBackendError: No backend available&lt;/p>
&lt;/blockquote>
&lt;p>下载安装 CH341SER.EXE 并重启&lt;/p>
&lt;blockquote>
&lt;p>escpos.exceptions.USBNotFoundError: USB device not found&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>ValueError: No backend available&lt;/p>
&lt;/blockquote>
&lt;p>根据&lt;a class="link" href="https://stackoverflow.com/questions/13773132/pyusb-on-windows-no-backend-available" target="_blank" rel="noopener"
>Stackoverflow&lt;/a>上的解答，应该是缺少 dll 文件，按解答中提供的链接地址下载安装就行了。&lt;/p>
&lt;p>但安装后会出现新的错误：&lt;/p>
&lt;blockquote>
&lt;p>NotImplementedError: Operation not supported or unimplemented on this platform&lt;/p>
&lt;/blockquote>
&lt;p>所幸在 python-escpos 的 Github &lt;a class="link" href="https://github.com/python-escpos/python-escpos/issues/316" target="_blank" rel="noopener"
>issue316&lt;/a>中有人提供了解决方式。直接修改源代码，删去对应的异常捕捉，就能够正常使用了。&lt;/p>
&lt;p>此处留档以备不时之需。&lt;/p></description></item><item><title>Windows 10 使用 OpenSSH + Rsync/SCP 同步文件</title><link>https://naizi.ch/2020/05/03/windows-10-%E4%BD%BF%E7%94%A8-openssh--rsync/scp-%E5%90%8C%E6%AD%A5%E6%96%87%E4%BB%B6/</link><pubDate>Sun, 03 May 2020 00:00:00 +0000</pubDate><guid>https://naizi.ch/2020/05/03/windows-10-%E4%BD%BF%E7%94%A8-openssh--rsync/scp-%E5%90%8C%E6%AD%A5%E6%96%87%E4%BB%B6/</guid><description>&lt;p>疫情期间在家工作一直使用公司的 VPN，于是在公司电脑开了共享用来传输文件。但不知道为什么，公司-&amp;gt;家方向传文件很顺畅，反方向传文件时就经常会出现进度条走到末尾就停住，要多等一段时间才能传输结束的问题。&lt;/p>
&lt;p>虽然可以考虑用 OneDrive 这类网盘来进行同步，但这样的同步不够实时，而且毕竟要从三方服务器绕一圈，上传速度波动也很大。&lt;/p>
&lt;p>想到既然 Windows 10 支持了 OpenSSH，家里和公司电脑又都装了 WSL，也许可以用 rsync/scp 来解决这个问题。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="安装-openssh-server">安装 OpenSSH Server&lt;/h2>
&lt;p>Windows 10(1809) 虽然自带了 OpenSSH 的 Server 和 Client，但默认并没有安装。&lt;/p>
&lt;p>参照&lt;a class="link" href="https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse" target="_blank" rel="noopener"
>Microsoft 的文档&lt;/a>给两台电脑都安装 OpenSSH：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-PowerShell" data-lang="PowerShell">&lt;span style="display:flex;">&lt;span>Get-WindowsCapability -Online | ? Name &lt;span style="color:#f92672">-like&lt;/span> &lt;span style="color:#e6db74">&amp;#39;OpenSSH*&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># This should return the following output:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Name &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> OpenSSH.Client~~~~&lt;span style="color:#ae81ff">0.0&lt;/span>.1.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>State &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> NotPresent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Name &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> OpenSSH.Server~~~~&lt;span style="color:#ae81ff">0.0&lt;/span>.1.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>State &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> NotPresent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install the OpenSSH Client&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Add-WindowsCapability -Online -Name OpenSSH.Client~~~~&lt;span style="color:#ae81ff">0.0&lt;/span>.1.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Install the OpenSSH Server&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Add-WindowsCapability -Online -Name OpenSSH.Server~~~~&lt;span style="color:#ae81ff">0.0&lt;/span>.1.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Both of these should return the following output:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Path &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Online &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> True
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>RestartNeeded &lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> False
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Start-Service sshd
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># OPTIONAL but recommended:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Set-Service -Name sshd -StartupType &lt;span style="color:#e6db74">&amp;#39;Automatic&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># Confirm the Firewall rule is configured. It should be created automatically by setup.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Get-NetFirewallRule -Name *ssh*
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># There should be a firewall rule named &amp;#34;OpenSSH-Server-In-TCP&amp;#34;, which should be enabled&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># If the firewall does not exist, create one&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>New-NetFirewallRule -Name sshd -DisplayName &lt;span style="color:#e6db74">&amp;#39;OpenSSH Server (sshd)&amp;#39;&lt;/span> -Enabled True -Direction Inbound -Protocol TCP -Action Allow -LocalPort &lt;span style="color:#ae81ff">22&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这时候就可以从其中一台电脑向另一台发起 SSH 连接了。&lt;/p>
&lt;h2 id="生成-ssh-密钥并部署">生成 SSH 密钥并部署&lt;/h2>
&lt;p>考虑到之后计划使用 rsync 传输，与 Linux 类似，我们可以使用密钥登陆的方式来避免每次都要输入密码。&lt;/p>
&lt;p>先设定作为 Client 的电脑：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-PowerShell" data-lang="PowerShell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 提示输入生成密钥的文件名，这里假设是 client_rsa&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-keygen
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 启动ssh-agent服务&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Get-Service -Name ssh-agent | Set-Service -StartupType &lt;span style="color:#e6db74">&amp;#39;Automatic&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Start-Service ssh-agent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 导入密钥&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ssh-add client_rsa
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>先通过密码登录的方式 SSH 连接到 Server 的电脑上：（注意替换成自己的 server_user 和 client_user）&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-PowerShell" data-lang="PowerShell">&lt;span style="display:flex;">&lt;span>ssh server_user@Server_ip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mkdir C:\Users\server_user\.ssh\
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>scp C:\Users\client_user\.ssh\client_rsa.pub server_user@Server_ip&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>C:\Users\server_user\.ssh\authorized_keys
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>退出 SSH 会话，然后重新连接，这时应该已经不需要再输入密码了。&lt;/p>
&lt;p>设置到这里的时候踩到一个坑：如果 SSH 登录的 Server 用户是管理员身份的话，&lt;code>authorized_keys&lt;/code>需要存到&lt;code>C:\ProgramData\ssh\administrators_authorized_keys&lt;/code>这个位置。&lt;/p>
&lt;p>不然的话此时 SSH 登录仍然会要求密码。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-PowerShell" data-lang="PowerShell">&lt;span style="display:flex;">&lt;span>ssh server_user@Server_ip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>scp C:\Users\client_user\.ssh\client_rsa.pub server_user@Server_ip&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span>C:\ProgramData\ssh\administrators_authorized_keys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 修正ACL权限&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$acl = Get-Acl C:\ProgramData\ssh\administrators_authorized_keys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$acl.SetAccessRuleProtection($true, $false)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$administratorsRule = New-Object system.security.accesscontrol.filesystemaccessrule(&lt;span style="color:#e6db74">&amp;#34;Administrators&amp;#34;&lt;/span>,&lt;span style="color:#e6db74">&amp;#34;FullControl&amp;#34;&lt;/span>,&lt;span style="color:#e6db74">&amp;#34;Allow&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$systemRule = New-Object system.security.accesscontrol.filesystemaccessrule(&lt;span style="color:#e6db74">&amp;#34;SYSTEM&amp;#34;&lt;/span>,&lt;span style="color:#e6db74">&amp;#34;FullControl&amp;#34;&lt;/span>,&lt;span style="color:#e6db74">&amp;#34;Allow&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$acl.SetAccessRule($administratorsRule)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$acl.SetAccessRule($systemRule)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>$acl | Set-Acl
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>之后对调 Client 和 Server 的身份再做一次同样的事情就可以了。&lt;/p>
&lt;h2 id="进入-wsl-部署-rsync">进入 WSL 部署 rsync&lt;/h2>
&lt;p>只用 scp 存在着一定的局限性，若要使用 rsync 则需要依靠 WSL 了。&lt;/p>
&lt;p>依旧从 Client 开始设置：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cd ~/.ssh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp /mnt/c/Users/client_user/.ssh/client_rsa ./client_rsa
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp /mnt/c/Users/client_user/.ssh/client_rsa.pub ./client_rsa.pub
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>chmod &lt;span style="color:#ae81ff">600&lt;/span> client_rsa
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>转到 Server 上：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cd ~/.ssh
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cp /mnt/c/Users/server_user/.ssh/authorized_keys ./authorized_keys
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>假设 Client 的 D 盘有个 testfile 的文件，测试 rsync：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># --rsync-path=&amp;#34;wsl rsync&amp;#34; 用来指示Server通过wsl启动rsync环境&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rsync -rtuv -e &lt;span style="color:#e6db74">&amp;#34;ssh -i ~/.ssh/client_rsa&amp;#34;&lt;/span> /mnt/d/testfile server_user@Server_ip:/mnt/d/ --rsync-path&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;wsl rsync&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>如果一切正常，应该不会提示输入密码，然后你能在 Server 的 D 盘看到这个文件。&lt;/p>
&lt;p>同样，对调 Client 和 Server 的身份再设定一次就可以双向操作了。&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>Windows 使用 WSL 配置 crontab 定时任务</title><link>https://naizi.ch/2020/01/25/windows-%E4%BD%BF%E7%94%A8-wsl-%E9%85%8D%E7%BD%AE-crontab-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/</link><pubDate>Sat, 25 Jan 2020 00:00:00 +0000</pubDate><guid>https://naizi.ch/2020/01/25/windows-%E4%BD%BF%E7%94%A8-wsl-%E9%85%8D%E7%BD%AE-crontab-%E5%AE%9A%E6%97%B6%E4%BB%BB%E5%8A%A1/</guid><description>&lt;p>有些 python 脚本任务对于树莓派来说还是负担太重了。毕竟性能上限摆在那里，也不能靠一个小小的 pi 承担太多。&lt;/p>
&lt;p>借此机会正好尝试一下 WSL(Windows Subsystem for Linux).&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="安装-wsl">安装 WSL&lt;/h2>
&lt;p>安装 WSL 并没有想象中的复杂。进入控制面板-&amp;gt;程序和功能-&amp;gt;启用或关闭 Windows 功能，勾选&lt;code>适用于Linux的Windows子系统&lt;/code>，等待安装完毕，然后重启系统。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2020-01-25-wsl-crontab/Snipaste_2020-01-25_19-14-26.png"
loading="lazy"
alt="适用于 Linux 的 Windows 子系统"
>&lt;/p>
&lt;p>重启完毕，开始菜单选择 MicrosoftStore，搜索 linux 就能够找到可供使用的 linux 发行版，我这里选择了 Ubuntu18.04LTS。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2020-01-25-wsl-crontab/Snipaste_2020-01-25_02-08-03.png"
loading="lazy"
alt="MicrosoftStore"
>&lt;/p>
&lt;p>应用安装完毕后，启动应用会要求设定基础的用户名和密码，接着，一个可供使用的 WSL 就准备就绪了。&lt;/p>
&lt;h2 id="确保-crontab-服务">确保 crontab 服务&lt;/h2>
&lt;p>由于 WSL 并不会默认启动，而且当运行中的 WSL 的最后一个 bash 关闭后，就相当于关闭了整个子系统，那我们的 crontab 也就直接去世了。&lt;/p>
&lt;p>为了保持 crontab 常驻，需要一些措施。这里参考了&lt;a class="link" href="https://blog.snowme34.com/post/schedule-tasks-using-crontab-on-windows-10-with-wsl/index.html" target="_blank" rel="noopener"
>Schedule Tasks Using Crontab on Windows 10 with WSL&lt;/a>的方法，建立一个 crontab 服务的 Windows 启动项，每次系统启动后在后台常驻。&lt;/p>
&lt;p>首先进入 WSL，修改 sudoer，避免每次启动 crontab 服务时需要输入密码确认：&lt;/p>
&lt;blockquote>
&lt;p>sudo visudo&lt;/p>
&lt;/blockquote>
&lt;p>在文件末尾加入&lt;/p>
&lt;blockquote>
&lt;p>%sudo ALL=NOPASSWD: /etc/init.d/cron start&lt;/p>
&lt;/blockquote>
&lt;p>然后保存文件。回到 Windows，Win+R 打开运行，输入&lt;code>shell:startup&lt;/code>打开启动文件夹，右键新建快捷方式，目标输入：&lt;/p>
&lt;blockquote>
&lt;p>C:\Windows\System32\wsl.exe sudo /etc/init.d/cron start&lt;/p>
&lt;/blockquote>
&lt;p>这样我们就能够保证 crontab 在每次 Windows 启动后常驻后台了。一切正常的话重启 Windows，在任务管理器能看到一个名称为 cron 的进程。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2020-01-25-wsl-crontab/Snipaste_2020-01-25_19-16-06.png"
loading="lazy"
alt="任务管理器"
>&lt;/p>
&lt;h2 id="使用-crontab">使用 crontab&lt;/h2>
&lt;p>剩下的操作和在 linux 里使用 crontab 没有区别，唯一需要注意的是文件的路径。&lt;/p>
&lt;p>在 WSL 中，Windows 的 C 盘被挂载在&lt;code>/mnt/c&lt;/code>目录下，D\E\F 以此类推，需要注意对应修改。&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>来一场圆周率的飞花令</title><link>https://naizi.ch/2019/11/26/%E6%9D%A5%E4%B8%80%E5%9C%BA%E5%9C%86%E5%91%A8%E7%8E%87%E7%9A%84%E9%A3%9E%E8%8A%B1%E4%BB%A4/</link><pubDate>Tue, 26 Nov 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/11/26/%E6%9D%A5%E4%B8%80%E5%9C%BA%E5%9C%86%E5%91%A8%E7%8E%87%E7%9A%84%E9%A3%9E%E8%8A%B1%E4%BB%A4/</guid><description>&lt;p>逛微博看到了一些有意思的东西：山东卫视《国学小名士》第三季的节目，进行了一场 π(圆周率) 的飞花令。&lt;/p>
&lt;p>节目里选手们接到了小数点后 204 位，那，能不能更长一点？&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="数据储备">数据储备&lt;/h2>
&lt;p>首先需要有足够多的古诗词储备，不然程序生成的时候耗光弹药就显得非常尴尬了。&lt;/p>
&lt;p>非常感谢&lt;a class="link" href="https://github.com/chinese-poetry/chinese-poetry" target="_blank" rel="noopener"
>chinese-poetry&lt;/a>这样的开源项目，积累了大量的已经结构化的古诗词数据。&lt;/p>
&lt;p>这里只用宋词部分的数据。&lt;/p>
&lt;h2 id="算法逻辑">算法逻辑&lt;/h2>
&lt;p>逻辑很简单粗暴：找出所有包含中文数字的词句进行汇总，然后按小数点后位数迭代 π 值随机取对应的诗词就行。&lt;/p>
&lt;p>整理诗词数据：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>filelist &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file &lt;span style="color:#66d9ef">for&lt;/span> file &lt;span style="color:#f92672">in&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>listdir(&lt;span style="color:#e6db74">&amp;#39;./json&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">if&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>search(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;poet.song&amp;#39;&lt;/span>, file)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>files &lt;span style="color:#f92672">=&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> json&lt;span style="color:#f92672">.&lt;/span>load(open(&lt;span style="color:#e6db74">&amp;#39;./json/&amp;#39;&lt;/span> &lt;span style="color:#f92672">+&lt;/span> file, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> file &lt;span style="color:#f92672">in&lt;/span> filelist
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;一&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;二&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;三&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;四&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;五&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;六&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;七&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;八&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;九&amp;#39;&lt;/span>: [],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;零&amp;#39;&lt;/span>: []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> file &lt;span style="color:#f92672">in&lt;/span> files:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> file:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> y &lt;span style="color:#f92672">in&lt;/span> x[&lt;span style="color:#e6db74">&amp;#39;paragraphs&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> z &lt;span style="color:#f92672">in&lt;/span> [&lt;span style="color:#e6db74">&amp;#39;一&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;二&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;三&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;四&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;五&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;六&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;七&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;八&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;九&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;零&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 　 &lt;span style="color:#66d9ef">if&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>search(z, y):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result[z] &lt;span style="color:#f92672">=&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(z) &lt;span style="color:#f92672">+&lt;/span> [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#ae81ff">\n\t\t\t\t&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(y, x[&lt;span style="color:#e6db74">&amp;#39;author&amp;#39;&lt;/span>], x[&lt;span style="color:#e6db74">&amp;#39;title&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;step.json&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(json&lt;span style="color:#f92672">.&lt;/span>dumps(result, ensure_ascii&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">False&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="π-值计算">π 值计算&lt;/h2>
&lt;p>无脑套用了一下别人的代码：&lt;a class="link" href="http://z-rui.github.io/post/2015/06/pi-digits/" target="_blank" rel="noopener"
>http://z-rui.github.io/post/2015/06/pi-digits/&lt;/a>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">pidigits&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q, r, t, u, i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">180&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>, &lt;span style="color:#ae81ff">168&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> y &lt;span style="color:#f92672">=&lt;/span> r &lt;span style="color:#f92672">//&lt;/span> t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">yield&lt;/span> y
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q, r, t, u, i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#f92672">*&lt;/span> q &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">*&lt;/span> (&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>), &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#f92672">*&lt;/span> u &lt;span style="color:#f92672">*&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q &lt;span style="color:#f92672">*&lt;/span> (&lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#f92672">+&lt;/span> r &lt;span style="color:#f92672">-&lt;/span> y &lt;span style="color:#f92672">*&lt;/span> t), t &lt;span style="color:#f92672">*&lt;/span> u, u &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">54&lt;/span> &lt;span style="color:#f92672">*&lt;/span> (i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>), i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>不停迭代 pidigits() 就能得到后一位小数数值。&lt;/p>
&lt;h2 id="开始飞花令">开始飞花令&lt;/h2>
&lt;p>测试一下迭代圆周率 1000 位&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sys
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> random &lt;span style="color:#f92672">import&lt;/span> randint
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>poets &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>load(open(&lt;span style="color:#e6db74">&amp;#39;step.json&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>number &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">1&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;一&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">2&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;二&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">3&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;三&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">4&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;四&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">5&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;五&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">6&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;六&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">7&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;七&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">8&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;八&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">9&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;九&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">0&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;零&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>n &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">pidigits&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q, r, t, u, i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>, &lt;span style="color:#ae81ff">180&lt;/span>, &lt;span style="color:#ae81ff">60&lt;/span>, &lt;span style="color:#ae81ff">168&lt;/span>, &lt;span style="color:#ae81ff">2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> y &lt;span style="color:#f92672">=&lt;/span> r &lt;span style="color:#f92672">//&lt;/span> t
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">yield&lt;/span> y
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q, r, t, u, i &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#f92672">*&lt;/span> q &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">*&lt;/span> (&lt;span style="color:#ae81ff">2&lt;/span> &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>), &lt;span style="color:#ae81ff">10&lt;/span> &lt;span style="color:#f92672">*&lt;/span> u &lt;span style="color:#f92672">*&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> q &lt;span style="color:#f92672">*&lt;/span> (&lt;span style="color:#ae81ff">5&lt;/span> &lt;span style="color:#f92672">*&lt;/span> i &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>) &lt;span style="color:#f92672">+&lt;/span> r &lt;span style="color:#f92672">-&lt;/span> y &lt;span style="color:#f92672">*&lt;/span> t), t &lt;span style="color:#f92672">*&lt;/span> u, u &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">54&lt;/span> &lt;span style="color:#f92672">*&lt;/span> (i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>), i &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> &lt;span style="color:#66d9ef">True&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f &lt;span style="color:#f92672">=&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;result.md&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> num &lt;span style="color:#f92672">in&lt;/span> pidigits():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> used &lt;span style="color:#f92672">=&lt;/span> []
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new &lt;span style="color:#f92672">=&lt;/span> poets[number[num]]&lt;span style="color:#f92672">.&lt;/span>pop(randint(&lt;span style="color:#ae81ff">0&lt;/span>, len(poets[number[num]])))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> new &lt;span style="color:#f92672">in&lt;/span> used:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new &lt;span style="color:#f92672">=&lt;/span> poets[number[num]]&lt;span style="color:#f92672">.&lt;/span>pop(randint(&lt;span style="color:#ae81ff">0&lt;/span>, len(poets[number[num]])))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(num, number[num], new)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#ae81ff">\t&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> num, number[num],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new&lt;span style="color:#f92672">.&lt;/span>replace(number[num], &lt;span style="color:#e6db74">&amp;#39;`&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">`&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(number[num]))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> used &lt;span style="color:#f92672">=&lt;/span> used&lt;span style="color:#f92672">.&lt;/span>append(new)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> n &lt;span style="color:#f92672">+=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> n &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">1000&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sys&lt;span style="color:#f92672">.&lt;/span>exit()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>哔哩哔哩下载工具 三</title><link>https://naizi.ch/2019/11/16/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-%E4%B8%89/</link><pubDate>Sat, 16 Nov 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/11/16/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-%E4%B8%89/</guid><description>&lt;p>有生之年我最后居然是用 Shell 来填了分 P 的 TODO……&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="curl---命令行下的文件传输工具">cURL - 命令行下的文件传输工具&lt;/h2>
&lt;p>cURL 这个工具太过强大，我也不知道该怎么去解释它比较好。几乎所有的网络访问，上传下载，都能够使用 cURL 来完成。&lt;/p>
&lt;p>也难怪诸如 Chrome 和 Firefox 等浏览器，会支持复制网络访问为 cURL。而其实我也一直依靠这个功能，通过&lt;a class="link" href="https://curl.trillworks.com/" target="_blank" rel="noopener"
>https://curl.trillworks.com/&lt;/a>来快速写我的 python 爬虫。&lt;/p>
&lt;p>大部分的工作都可以直接靠右键复制为 cURL 来完成，所以只需要简单的查看下 cURL 的说明，便于进行一些小的修改就行。&lt;/p>
&lt;p>然后开始对照之前写的 python 脚本的逻辑，一步步转成 Shell 脚本。&lt;/p>
&lt;h2 id="jq---命令行下的-json-处理工具">jq - 命令行下的 JSON 处理工具&lt;/h2>
&lt;p>B 站 API 接口的各种返回数据以 JSON 为主，直接靠 awk/sed 手撸解析是一件很痛苦的事。所以我们需要用上 jq 这个工具减轻负担。&lt;/p>
&lt;p>jq 解析 JSON 数据非常的便捷，比如获取分 P 数据时：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;code&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">0&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;message&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;0&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;ttl&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;data&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;cid&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">73802454&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;page&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">1&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;from&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;vupload&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;part&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;3&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;duration&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">66&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;vid&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;weblink&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;dimension&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;width&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">1920&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;height&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">1080&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">&amp;#34;rotate&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>↑ jq 的另一个作用就是格式化 json 数据，比如上面的代码就是通过&lt;code>cat result.json | jq .&lt;/code>自动格式化出来的。&lt;/p>
&lt;p>要取得分 P 的 cid，我们可以：
&lt;code>curl -sL 'https://api.bilibili.com/x/player/pagelist?aid=42038790&amp;amp;jsonp=jsonp' | jq -r '.data[0].cid'&lt;/code>&lt;/p>
&lt;p>如果有多个分 P，我们可以这样取得所有的 cid：
&lt;code>curl -sL 'https://api.bilibili.com/x/player/pagelist?aid=42038790&amp;amp;jsonp=jsonp' | jq -r '.data[].cid'&lt;/code>&lt;/p>
&lt;p>真的是非常方便。&lt;/p>
&lt;h2 id="sed---支持正则表达式的流编辑器">sed - 支持正则表达式的流编辑器&lt;/h2>
&lt;p>考虑到 windows 上的兼容性，如果用视频标题做文件名，势必要排除&lt;code>/?!.*|:&lt;/code>等特殊字符。这时我们就可以用 sed 来完成：
&lt;code>curl -sL -H https://api.bilibili.com/x/web-interface/view?aid=42038790 | jq -r '.data.title' | sed 's/[/?!.*|:]//g'&lt;/code>&lt;/p>
&lt;p>再后面我们会更多的用到 sed。&lt;code>(Flag)&lt;/code>&lt;/p>
&lt;h2 id="编写-shell-脚本">编写 Shell 脚本&lt;/h2>
&lt;p>&lt;code>$1&lt;/code> 代表了命令行传给脚本的第一个参数，使用场景类似 &lt;code>bash download.sh av42038790&lt;/code>
通过 sed，我们可以兼容 &lt;code>bash download.sh https://www.bilibili.com/video/av42038790?from=search&amp;amp;seid=3037567923943401758&lt;/code> 的链接模式。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $1 | sed -e &lt;span style="color:#e6db74">&amp;#39;s/.*av//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/[a-zA-Z?/].*//g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Shell 脚本中声明变量时等号前后都不能有空格，使用变量时在变量前加上&lt;code>$&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>pagelist&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/pagelist?aid=&amp;#39;&lt;/span>$aid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;jsonp=jsonp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cids&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL $pagelist | jq -r &lt;span style="color:#e6db74">&amp;#39;.data[].cid&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这里的 cids 只是一个包含空格分隔的字符串&lt;code>123 234 345&lt;/code>，为了让 Shell 正确计数，把它转为数组&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cids_arr&lt;span style="color:#f92672">=(&lt;/span>$cids&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样一来通过&lt;code>${ #cids_arr[@] }&lt;/code>就能够正确得出 cids 中的元素个数&lt;/p>
&lt;p>接下来通过 for 循环遍历 cids 中的元素，为了能同时计算元素的个数，需要另行计数：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>part&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> cid in $cids
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> episode&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$((&lt;/span> &lt;span style="color:#f92672">++&lt;/span>part &lt;span style="color:#66d9ef">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">#some code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Shell 中的 if else 判断以 if 开始，fi 结束：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${#&lt;/span>cids_arr[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename&lt;span style="color:#f92672">=&lt;/span>av$aid.$title.mp4 &lt;span style="color:#75715e"># 视频只有1P时，不标注分P&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename&lt;span style="color:#f92672">=&lt;/span>av$aid.$title【P$episode】.mp4 &lt;span style="color:#75715e"># 多P视频标注分P&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>判断视频是 DASH 还是 FLV：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>json_url&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/playurl?avid=&amp;#39;&lt;/span>$aid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;cid=&amp;#39;&lt;/span>$cid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;qn=116&amp;amp;fnver=0&amp;amp;fnval=16&amp;amp;otype=json&amp;amp;type=&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>json&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL $json_url&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dash&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq &lt;span style="color:#e6db74">&amp;#39;.data|has(&amp;#34;dash&amp;#34;)&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>durl&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq &lt;span style="color:#e6db74">&amp;#39;.data|has(&amp;#34;durl&amp;#34;)&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>处理 DASH 视频：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dash&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;true&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vp&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.dash.video[0].baseUrl&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span> &lt;span style="color:#75715e"># 视频&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ap&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.dash.audio[0].baseUrl&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span> &lt;span style="color:#75715e"># 音频&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aria2c --args $vp --out ./v_$cid.m4s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aria2c --args $vp --out ./a_$cid.m4s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ffmpeg -i ./v_$cid.m4s -i ./a_$cid.m4s -c:v copy -c:a copy ./$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rm *.m4s
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>处理 FLV 视频：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">elif&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$durl&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;true&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flvs&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.durl[].url&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> flv in $flvs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;file &amp;#39;./&amp;#34;&lt;/span>$flvname&lt;span style="color:#e6db74">&amp;#34;&amp;#39;&amp;#34;&lt;/span> &amp;gt;./merge_$cid.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flvname&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $flv | sed -e &lt;span style="color:#e6db74">&amp;#39;s/\?.*//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/.*\///g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aria2c --args $flv --out ./$flvname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ffmpeg -safe &lt;span style="color:#ae81ff">0&lt;/span> -f concat -i ./merge_$cid.txt -c copy ./$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm *.flv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm ./merge_$cid.txt
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="使用-shift">使用 shift&lt;/h2>
&lt;p>shift 命令的作用是左移参数，举例来说，如果我们传给脚本 3 个参数 a,b,c，脚本接收到的参数为：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>echo $1 $2 $3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>a b c
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>当我们执行 shift 后&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>shift
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>echo $1 $2 $3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>b c
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样就可以在循环中一直只处理$1，直到所有参数左移完毕。&lt;/p>
&lt;p>先判断没有参数时退出脚本：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> $# -eq &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Need AVID to download!\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>加上 shift 和循环：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">until&lt;/span> &lt;span style="color:#f92672">[&lt;/span> $# -eq &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $1 | sed -e &lt;span style="color:#e6db74">&amp;#39;s/.*av//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/[a-zA-Z?/].*//g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">#some code&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shift
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="记得加上-cookies">记得加上 Cookies&lt;/h2>
&lt;p>要获得高清源必须在访问 API 时加上 Cookies，cURL 要做到这点很简单。&lt;/p>
&lt;p>测试得知判断的关键是 Cookies 的如下键值：
&lt;code>DedeUserID=; DedeUserID__ckMd5=; SESSDATA=; bili_jct=&lt;/code>&lt;/p>
&lt;p>F12 打开浏览器，找到对应的键值，保存成 Cookies 的文本文件，然后修改代码：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>cookies&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>cat ./cookies&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>aria2c --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="完整代码">完整代码&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/sh
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>IFS&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">$&amp;#39;\n&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> $# -eq &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Need AVID to download!\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> exit &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">until&lt;/span> &lt;span style="color:#f92672">[&lt;/span> $# -eq &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $1 | sed -e &lt;span style="color:#e6db74">&amp;#39;s/.*av//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/[a-zA-Z?/].*//g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cookies&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>cat ./cookies&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pagelist&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/pagelist?aid=&amp;#39;&lt;/span>$aid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;jsonp=jsonp&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Getting video list: \n&amp;#34;&lt;/span>$pagelist
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cids&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies $pagelist | jq -r &lt;span style="color:#e6db74">&amp;#39;.data[].cid&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/web-interface/view?aid=&amp;#39;&lt;/span>$aid | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.title&amp;#39;&lt;/span> | sed &lt;span style="color:#e6db74">&amp;#39;s/[/?!.*|:]//g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cids_arr&lt;span style="color:#f92672">=(&lt;/span>$cids&lt;span style="color:#f92672">)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Found video pages: &amp;#34;&lt;/span>&lt;span style="color:#e6db74">${#&lt;/span>cids_arr[@]&lt;span style="color:#e6db74">}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> part&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> cid in $cids
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> episode&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">$((&lt;/span> &lt;span style="color:#f92672">++&lt;/span>part &lt;span style="color:#66d9ef">))&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Download video page: &amp;#34;&lt;/span>$episode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${#&lt;/span>cids_arr[@]&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;1&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename&lt;span style="color:#f92672">=&lt;/span>av$aid.$title.mp4
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> filename&lt;span style="color:#f92672">=&lt;/span>av$aid.$title【P$episode】.mp4
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> json_url&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/playurl?avid=&amp;#39;&lt;/span>$aid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;cid=&amp;#39;&lt;/span>$cid&lt;span style="color:#e6db74">&amp;#39;&amp;amp;qn=116&amp;amp;fnver=0&amp;amp;fnval=16&amp;amp;otype=json&amp;amp;type=&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Getting video source: \n&amp;#34;&lt;/span>$json_url
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> json&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies $json_url&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> dash&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq &lt;span style="color:#e6db74">&amp;#39;.data|has(&amp;#34;dash&amp;#34;)&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> durl&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq &lt;span style="color:#e6db74">&amp;#39;.data|has(&amp;#34;durl&amp;#34;)&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dash&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;true&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vp&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.dash.video[0].baseUrl&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ap&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.dash.audio[0].baseUrl&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Downloading Video Dash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aria2c -x10 -k1M --file-allocation&lt;span style="color:#f92672">=&lt;/span>none --auto-file-renaming&lt;span style="color:#f92672">=&lt;/span>false --allow-overwrite&lt;span style="color:#f92672">=&lt;/span>true $vp&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --show-console-readout false --quiet &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Referer: https://www.bilibili.com/video/av&amp;#34;&lt;/span>$aid&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Pragma: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --out ./v_$cid.m4s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Downloading Audio Dash&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aria2c -x10 -k1M --file-allocation&lt;span style="color:#f92672">=&lt;/span>none --auto-file-renaming&lt;span style="color:#f92672">=&lt;/span>false --allow-overwrite&lt;span style="color:#f92672">=&lt;/span>true $ap&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --show-console-readout false --quiet &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Referer: https://www.bilibili.com/video/av&amp;#34;&lt;/span>$aid&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Pragma: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --out ./a_$cid.m4s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Merge into file: &amp;#34;&lt;/span>$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ffmpeg -i ./v_$cid.m4s -i ./a_$cid.m4s -c:v copy -c:a copy&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -y -hide_banner -loglevel panic &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ./$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Removing temp files\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm *.m4s
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$durl&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;true&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flvs&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | jq -r &lt;span style="color:#e6db74">&amp;#39;.data.durl[].url&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> flv in $flvs
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">do&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flvname&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $flv | sed -e &lt;span style="color:#e6db74">&amp;#39;s/\?.*//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/.*\///g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;file &amp;#39;./&amp;#34;&lt;/span>$flvname&lt;span style="color:#e6db74">&amp;#34;&amp;#39;&amp;#34;&lt;/span> &amp;gt;./merge_$cid.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Downloading Video Part: &amp;#34;&lt;/span>$flvname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aria2c -x10 -k1M --file-allocation&lt;span style="color:#f92672">=&lt;/span>none --auto-file-renaming&lt;span style="color:#f92672">=&lt;/span>false --allow-overwrite&lt;span style="color:#f92672">=&lt;/span>true $flv&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --show-console-readout false --quiet &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Referer: https://www.bilibili.com/video/av&amp;#34;&lt;/span>$aid&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Pragma: no-cache&amp;#34;&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --header&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> --out ./$flvname
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Merge into file: &amp;#34;&lt;/span>$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ffmpeg -safe &lt;span style="color:#ae81ff">0&lt;/span> -f concat -i ./merge_$cid.txt -c copy&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> -y -hide_banner -loglevel panic &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> ./$filename
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Removing temp files\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm *.flv
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> rm ./merge_$cid.txt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Error: Video not found!\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> shift
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">done&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>清晰度选择我觉得就真的没必要了吧.jpg&lt;/p>
&lt;p>但是 jq 毕竟是个第三方工具，难道真的不能用 awk/sed 来解析 json 吗？&lt;/p>
&lt;p>可以，但没必要。&lt;/p>
&lt;p>但今天就尝试一次，用 awk 和 sed 组合，替换掉 jq 的工作：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 获取cids&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cids&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies $pagelist | sed &lt;span style="color:#e6db74">&amp;#39;s/[{,}]/\n/g&amp;#39;&lt;/span> | awk -F: &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;cid&amp;#34;/ {print $2}&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 获取视频标题&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>title&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>curl -sL -H &lt;span style="color:#e6db74">&amp;#34;Cookie: &amp;#34;&lt;/span>$cookies &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/web-interface/view?aid=&amp;#39;&lt;/span>$aid | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> | awk -F: &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;title&amp;#34;/ {print $2}&amp;#39;&lt;/span> | sed -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/[/?!.*|:]/-/g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 是否是DASH&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>dash&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;dash&amp;#34;/&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 是否是FLV&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>durl&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;durl&amp;#34;/&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$dash&lt;span style="color:#e6db74">&amp;#34;&lt;/span> !&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 视频 DASH&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vp&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\]//g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;video&amp;#34;/,/&amp;#34;audio&amp;#34;/&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;baseUrl&amp;#34;/&amp;#39;&lt;/span> | sed -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;baseUrl&amp;#34;://g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\u0026/\&amp;amp;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\\\\//g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;NR==1&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># 音频 DASH&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ap&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\]//g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;audio&amp;#34;/,/&amp;#34;&amp;#34;/&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;baseUrl&amp;#34;/&amp;#39;&lt;/span> | sed -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;baseUrl&amp;#34;://g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\u0026/\&amp;amp;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\\\\//g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;NR==1&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">elif&lt;/span> &lt;span style="color:#f92672">[&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$durl&lt;span style="color:#e6db74">&amp;#34;&lt;/span> !&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># FLV 视频&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> flvs&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>echo $json | sed -e &lt;span style="color:#e6db74">&amp;#39;s/[{}]//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/,&amp;#34;/\n&amp;#34;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\]//g&amp;#39;&lt;/span> | awk &lt;span style="color:#e6db74">&amp;#39;/&amp;#34;url&amp;#34;/&amp;#39;&lt;/span> | sed -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;url&amp;#34;://g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/&amp;#34;//g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\u0026/\&amp;amp;/g&amp;#39;&lt;/span> -e &lt;span style="color:#e6db74">&amp;#39;s/\\\\//g&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;-&amp;gt;Error: Video not found!\n&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>配置 Cmder 整合 Cygwin 与 Github</title><link>https://naizi.ch/2019/10/19/%E9%85%8D%E7%BD%AE-cmder-%E6%95%B4%E5%90%88-cygwin-%E4%B8%8E-github/</link><pubDate>Sat, 19 Oct 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/10/19/%E9%85%8D%E7%BD%AE-cmder-%E6%95%B4%E5%90%88-cygwin-%E4%B8%8E-github/</guid><description>&lt;p>记录下折腾 Cmder 的过程。&lt;/p>
&lt;p>虽然群友安利了许久的 WSL 和 Powershell，但对于前两者的 GUI 我实在看不下去。也许他们真的很棒，但好用的工具似乎注定了不能拥有好看的 UI。&lt;/p>
&lt;p>而我，还是倾向于 UI 舒服的。毕竟只要它真的不是功能上缺胳膊少腿儿，剩下的事情，大部分都可以克服。&lt;/p>
&lt;p>再说了，不多折腾折腾，不多踩几个坑，多 Google 和 Stackoverflow 一下，你怎么会知道原来世界上还能有这种问题呢.jpg&lt;/p>
&lt;h2 id="初始化">初始化&lt;/h2>
&lt;p>我们可以从&lt;a class="link" href="https://cmder.net/" target="_blank" rel="noopener"
>Cmder 的主页&lt;/a>下载它的最新版（当前是 v1.3.12）。为了避免之后和 Github 以及 Cygwin 附带的命令出现不同版本并存的问题，选择&lt;code>Download Mini&lt;/code>。&lt;/p>
&lt;p>下载完毕后解压，先把 Cmder 添加到右键菜单，方便之后随时调用。在解压目录打开一个管理员权限的 Powershell：&lt;/p>
&lt;blockquote>
&lt;p>Cmder.exe /REGISTER ALL&lt;/p>
&lt;/blockquote>
&lt;p>接下来先放着 Cmder 不管，继续装 Git for windows。&lt;/p>
&lt;p>从&lt;a class="link" href="https://gitforwindows.org/" target="_blank" rel="noopener"
>Git for windows 的主页&lt;/a>下载最新版，安装基本都是默认选项，需要注意的是：&lt;/p>
&lt;ol>
&lt;li>&lt;code>Adjusting your PATH enviroment&lt;/code>选择&lt;code>Use Git from the Windows Command Prompt&lt;/code>&lt;/li>
&lt;li>&lt;code>Configuring the line ending conversions&lt;/code>选择&lt;code>Checkout Windows-style, commit Unix-style line endings&lt;/code>&lt;/li>
&lt;li>&lt;code>Configuing the terminal emulator to use with Git Bash&lt;/code>选择&lt;code>Use MinTTY&lt;/code>&lt;/li>
&lt;/ol>
&lt;p>额外选项可以默认也可以不选，安装完成后在任意目录右键，应该能看见&lt;code>Git Bash Here&lt;/code>和&lt;code>Git GUI Here&lt;/code>的选项。&lt;/p>
&lt;p>接着继续，安装 Cygwin。&lt;/p>
&lt;p>在&lt;a class="link" href="https://www.cygwin.com/" target="_blank" rel="noopener"
>Cygwin 官网&lt;/a>下载对应版本（64-bit/32-bit），双击执行，默认选项即可。&lt;/p>
&lt;p>在&lt;code>Choose A Download Site&lt;/code>这步，我们可以添加&lt;a class="link" href="https://mirror.tuna.tsinghua.edu.cn/help/cygwin/" target="_blank" rel="noopener"
>清华大学的镜像源&lt;/a>&lt;code>https://mirrors.tuna.tsinghua.edu.cn/cygwin/&lt;/code>来提升软件包的下载速度，然后点击下一步。&lt;/p>
&lt;p>在 Search 中搜索 wget，下拉菜单将 Skip 替换成其中一个版本安装，这是之后安装 apt-cyg 需要的依赖。另外检查 gwak、tar、bzip2 这几个包是否安装（不是 Skip 就对了）。&lt;/p>
&lt;p>有需要的话可以自己选择还要安装哪些命令，不过这些都可以之后再改，先接着继续下一步默认安装就可以了。&lt;/p>
&lt;h2 id="折腾">折腾&lt;/h2>
&lt;p>现在我们有了 Cmder+Github+Cygwin，接下来把他们整一起。&lt;/p>
&lt;p>git 比较方便，打开控制面板，选择&lt;code>系统和安全&lt;/code>-&amp;gt;&lt;code>系统&lt;/code>-&amp;gt;&lt;code>高级系统设置&lt;/code>-&amp;gt;&lt;code>环境变量&lt;/code>，在&lt;code>用户变量&lt;/code>里选择&lt;code>Path&lt;/code>-&amp;gt;&lt;code>编辑&lt;/code>，如果是默认安装的话，应该会有一条&lt;code>C:\Program Files\Git\usr\bin&lt;/code>的记录，如果没有可以手动添加。&lt;/p>
&lt;p>然后打开 Cmder，输入 git 并回车，检查是否能够调用 git。&lt;/p>
&lt;p>Cygwin 按照同样的方式加入系统环境变量，默认路径是&lt;code>C:\cygwin64\bin&lt;/code>，如果修改了安装路径需要对应修改。&lt;/p>
&lt;p>接着安装 apt-cyg，有了它就可以像 Ubuntu 管理软件包一样随意 install 需要的命令了。&lt;/p>
&lt;p>apt-cyg 的项目主页是 &lt;a class="link" href="https://github.com/transcode-open/apt-cyg" target="_blank" rel="noopener"
>https://github.com/transcode-open/apt-cyg&lt;/a> ，在 Release 页面下载最新版，解压将&lt;code>apt-cyg&lt;/code>文件移动到&lt;code>C:\cygwin64\bin&lt;/code>，打开 cygwin 终端：&lt;/p>
&lt;blockquote>
&lt;p>apt-cyg install nano&lt;/p>
&lt;/blockquote>
&lt;p>测试 apt-cyg 是否正常工作。&lt;/p>
&lt;p>为了解决中文编码问题，在 Cygwin 终端窗口右键选择&lt;code>Options&lt;/code>，选择&lt;code>Text&lt;/code>，更改&lt;code>locale&lt;/code>为&lt;code>zh_CN&lt;/code>，&lt;code>Character set&lt;/code>为&lt;code>UTF-8&lt;/code>。&lt;/p>
&lt;p>然后 nano 或者 vi 编辑&lt;code>~/.bashrc&lt;/code>文件，在文件最后添加：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-~/.bashrc" data-lang="~/.bashrc">&lt;span style="display:flex;">&lt;span>export LC_ALL&lt;span style="color:#f92672">=&lt;/span>zh_CN.UTF-8
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export LC_CTYPE&lt;span style="color:#f92672">=&lt;/span>zh_CN.UTF-8
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>export LANG&lt;span style="color:#f92672">=&lt;/span>zh_CN.UTF-8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>为了 Cygwin 和 Cmder 整合后，能够识别当前工作目录，通过 apt-cyg 安装 chere 命令：&lt;/p>
&lt;blockquote>
&lt;p>apt-cyg install chere&lt;/p>
&lt;/blockquote>
&lt;p>然后还是编辑&lt;code>~/.bashrc&lt;/code>，追加内容：[参考 1]&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-~/.bashrc" data-lang="~/.bashrc">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[&lt;/span> -n &lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#e6db74">${&lt;/span>ConEmuWorkDir&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span> &lt;span style="color:#f92672">]&lt;/span>; &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cd &lt;span style="color:#e6db74">&amp;#34;&lt;/span>$ConEmuWorkDir&lt;span style="color:#e6db74">&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Cygwin 中 C 盘的路径映射为&lt;code>/cygdrive/c&lt;/code>，如果觉得太长的话，可以在 cygwin 终端里修改&lt;code>/etc/fstab&lt;/code>或者直接修改&lt;code>C:\cygwin64\etc\fstab&lt;/code>:&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-/etc/fstab" data-lang="/etc/fstab"># /etc/fstab
#
# This file is read once by the first process in a Cygwin process tree.
# To pick up changes, restart all Cygwin processes. For a description
# see https://cygwin.com/cygwin-ug-net/using.html#mount-table
# This is default anyway:
# none /cygdrive cygdrive binary,posix=0,user 0 0
none / cygdrive binary 0 0
&lt;/code>&lt;/pre>&lt;p>之后 C 盘映射到&lt;code>/c&lt;/code>，D 盘映射到&lt;code>/d&lt;/code>，以此类推。&lt;/p>
&lt;p>以上设置完可以算是告一段落了，接着把 Cygwin 整进 Cmder 里。&lt;/p>
&lt;p>打开 Cmder，右键选择&lt;code>Settings&lt;/code>，选择&lt;code>Startup&lt;/code>-&amp;gt;&lt;code>Tasks&lt;/code>。&lt;/p>
&lt;p>点击&amp;rsquo;+&amp;lsquo;号添加新的 Task，&lt;code>Task Name&lt;/code>填一个能区分出是 Cygwin 的，比如&lt;code>Cygwin::bash&lt;/code>，&lt;code>Task parameters&lt;/code>填写&lt;code>/icon C:\cygwin64\Cygwin-Terminal.ico&lt;/code>，在&lt;code>Commands&lt;/code>中填写&lt;code>set CHERE_INVOKING=1 &amp;amp; C:\cygwin64\Cygwin.bat -c &amp;quot;/bin/xhere /bin/bash.exe --login -i '%V'&amp;quot;&lt;/code>&lt;a class="link" href="https://github.com/cmderdev/cmder/wiki/Integrating-Cygwin" title="Integrating Cygwin"
target="_blank" rel="noopener"
>参考 2&lt;/a>&lt;/p>
&lt;p>然后勾选上&lt;code>Default task for new console&lt;/code>和&lt;code>Taskbar jump lists&lt;/code>。回到&lt;code>Startup&lt;/code>，选择&lt;code>Specified named task&lt;/code>-&amp;gt;&lt;code>Cygwin::bash&lt;/code>。&lt;/p>
&lt;p>这样一来 Cmder 的默认终端就是 Cygwin 了，git 命令和 windows 本身的支持也没有问题。&lt;/p>
&lt;h2 id="进一步调整">进一步调整&lt;/h2>
&lt;h3 id="general-fonts">&lt;code>General&lt;/code>-&amp;gt;&lt;code>Fonts&lt;/code>&lt;/h3>
&lt;p>先解决编码问题，选择&lt;code>General&lt;/code>-&amp;gt;&lt;code>Fonts&lt;/code>-&amp;gt;&lt;code>Unicode ranges&lt;/code>-&amp;gt;&lt;code>CJK: 2E80-9FC3;AC00-D7A3;F900-FAFF;FE30-FE4F;FF01-FF60;FFE0-FFE6;&lt;/code>-&amp;gt;&lt;code>Apply&lt;/code>。&lt;/p>
&lt;p>&lt;code>Font charset&lt;/code>还是保持 ANSI，否则 Cmder 会报错&lt;code>Failed to create font&lt;/code>然后 fail back 回缺省字体。&lt;/p>
&lt;p>然后选择&lt;code>Startup&lt;/code>-&amp;gt;&lt;code>Environment&lt;/code>，添加如下内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>set PATH&lt;span style="color:#f92672">=&lt;/span>%ConEmuBaseDir%&lt;span style="color:#ae81ff">\S&lt;/span>cripts;%PATH%
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>set LANG&lt;span style="color:#f92672">=&lt;/span>zh_CN.UTF8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接着是字体，中文字体真的太少了，好看的就更少了。（有一说一，我觉得，确实，是这样的）&lt;/p>
&lt;p>选了 Powerline Fonts：&lt;/p>
&lt;blockquote>
&lt;p>git clone &lt;a class="link" href="https://github.com/powerline/fonts.git" target="_blank" rel="noopener"
>https://github.com/powerline/fonts.git&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>我单独装了&lt;code>Noto Mono for Powerline&lt;/code>，也可以选择执行&lt;code>install.ps1&lt;/code>直接安装全部的字体。&lt;/p>
&lt;p>在&lt;code>Main console font&lt;/code>选了&lt;code>Noto Mono for Powerline&lt;/code>后，中文还是会 fail back 成宋体，这里勾上&lt;code>Alternative font&lt;/code>，选择&lt;code>微软雅黑 Light&lt;/code>，个人感觉不违和，能看。&lt;/p>
&lt;p>其他勾选上&lt;code>Monospace&lt;/code>和&lt;code>Compress long strings to fit space&lt;/code>。&lt;/p>
&lt;h3 id="general-size--pos">&lt;code>General&lt;/code>-&amp;gt;&lt;code>Size &amp;amp; Pos&lt;/code>&lt;/h3>
&lt;p>将&lt;code>Width&lt;/code>改为 80%，&lt;code>Height&lt;/code>改为 70%，这样 Cmder 启动会自动根据显示器大小调整窗口大小。&lt;/p>
&lt;h3 id="general-background">&lt;code>General&lt;/code>-&amp;gt;&lt;code>Background&lt;/code>&lt;/h3>
&lt;p>设置&lt;code>Background Image&lt;/code>和&lt;code>Darkening&lt;/code>可以给终端添加图片背景并调整图片透明度，我就敬谢不敏了。&lt;/p>
&lt;h3 id="general-confirm">&lt;code>General&lt;/code>-&amp;gt;&lt;code>Confirm&lt;/code>&lt;/h3>
&lt;p>去除&lt;code>Confirm creating new console/tab&lt;/code>和&lt;code>Confirm tab duplicating&lt;/code>的勾选，这两个太烦人了。&lt;/p>
&lt;h3 id="features-transparency">&lt;code>Features&lt;/code>-&amp;gt;&lt;code>Transparency&lt;/code>&lt;/h3>
&lt;p>&lt;code>Alpha transparency&lt;/code>可以调整终端窗口本身的透明度，我这里直接拖到了最右边不透明。屡次截图终端映出了背后的内容总是让我心有余悸。&lt;/p>
&lt;h3 id="keys--macro-paste">&lt;code>Keys &amp;amp; Macro&lt;/code>-&amp;gt;&lt;code>Paste&lt;/code>&lt;/h3>
&lt;p>确保两个&lt;code>Paste mode&lt;/code>都是&lt;code>Multi lines&lt;/code>，避免行为不一致。&lt;/p>
&lt;h2 id="其他">其他&lt;/h2>
&lt;ul>
&lt;li>
&lt;p>Cmder 有自己的&lt;code>user_alias&lt;/code>(&amp;ldquo;Cmder/config/user_alias.cmd&amp;rdquo;) 可以很方便的设定一些常用命令，可以在 cmd 或者 powershell 终端里使用，但是不能和 Cygwin 通用。Cygwin 需要 Alias 的话还是得老老实实编辑&lt;code>~/.bashrc&lt;/code>。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Cmder 可以作为 Sublime Text 的终端来使用，Sublime 安装 Terminal 插件，设置终端路径为 Cmder 安装路径即可。默认呼出终端的快捷键是 Ctrl+Shift+T。&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Cygwin 有个已知问题，Ctrl+方向键没有绑定操作，需要手动添加，方法是编辑&lt;code>~/.inputrc&lt;/code>添加两行内容&lt;code>&amp;quot;\e[1;5C&amp;quot;: forward-word&lt;/code>和&lt;code>&amp;quot;\e[1;5D&amp;quot;: backward-word&lt;/code>。[参考 3]&lt;/p>
&lt;/li>
&lt;li>
&lt;p>如果之前已经生成了 SSH KEY 的话，需要手动复制到&lt;code>C:\cygwin64\home\&amp;lt;user name&amp;gt;\.ssh&lt;/code>或者直接指定 ssh key 才能让 git 识别到。&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>jojo，python 的能力是有极限的</title><link>https://naizi.ch/2019/10/18/jojopython-%E7%9A%84%E8%83%BD%E5%8A%9B%E6%98%AF%E6%9C%89%E6%9E%81%E9%99%90%E7%9A%84/</link><pubDate>Fri, 18 Oct 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/10/18/jojopython-%E7%9A%84%E8%83%BD%E5%8A%9B%E6%98%AF%E6%9C%89%E6%9E%81%E9%99%90%E7%9A%84/</guid><description>&lt;p>&amp;ndash;&amp;gt; jojo，python 的能力是有极限的。&lt;/p>
&lt;p>&amp;ndash;&amp;gt; 我从短暂的人生当中学到一件事&amp;hellip;&amp;hellip;&lt;/p>
&lt;p>&amp;ndash;&amp;gt; 越是玩弄代码，就越会发现 python 的不足&amp;hellip;&amp;hellip;&lt;/p>
&lt;p>&amp;ndash;&amp;gt; 除非超越 python。&lt;/p>
&lt;p>你到底想说什么？&lt;/p>
&lt;p>&amp;ndash;&amp;gt; 吔我 bash 啦！&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="起因">起因&lt;/h2>
&lt;p>原因其实很简单，工作上需要使用后台导入些数据。&lt;/p>
&lt;p>吭哧吭哧把原始数据整理完，搞成 csv 了，点个“导入”，给我来了个报错：&lt;/p>
&lt;ul>
&lt;li>单次导入只支持 1000 条&lt;/li>
&lt;/ul>
&lt;p>崽啊，9102 年末了，我 csv 里面近百万条数据，你跟我说一次 1000？我不下班啦？&lt;/p>
&lt;p>然而优秀的底层员工是不会抱怨这些事情的，行吧，我自己切表总行了吧。&lt;/p>
&lt;h2 id="尝试-python-切割">尝试 python 切割&lt;/h2>
&lt;p>先用 python 瞎糊了一个：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>datas &lt;span style="color:#f92672">=&lt;/span> {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> n, line &lt;span style="color:#f92672">in&lt;/span> enumerate(open(&lt;span style="color:#e6db74">&amp;#39;test.csv&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> datas&lt;span style="color:#f92672">.&lt;/span>get(n&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> datas[n&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> datas[n&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>] &lt;span style="color:#f92672">+&lt;/span> [line]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> datas[n&lt;span style="color:#f92672">//&lt;/span>&lt;span style="color:#ae81ff">1000&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> [line]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> k,v &lt;span style="color:#f92672">in&lt;/span> datas&lt;span style="color:#f92672">.&lt;/span>items():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;test-&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.csv&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(k), &lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>join(v))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>凑合着用了，顺便再写了个脚本把切好的文件自动导入到后台，把工作做完了。&lt;/p>
&lt;p>但是回过头了，就开始嫌弃 python 不够快了。&lt;/p>
&lt;p>虽然很明显原因在于我（毕竟瞎糊凑合用），想看能不能更快一点。&lt;/p>
&lt;p>于是想起了 bash，想起了 split 和 xargs。&lt;/p>
&lt;h2 id="换成-bash">换成 bash&lt;/h2>
&lt;p>要切割文件的话 split 的确很方便：&lt;/p>
&lt;blockquote>
&lt;p>split test.csv -l 1000 -d -a 3 test__&lt;/p>
&lt;/blockquote>
&lt;p>&lt;code>-d -a 3&lt;/code>指定了按 3 位数递增添加后缀，这样就能产生 test&lt;strong>001 ~ test&lt;/strong>999 命名的切割好的文件。&lt;/p>
&lt;p>但新的问题也来了，切割完需要全部加上文件扩展名：&lt;/p>
&lt;blockquote>
&lt;p>mv test_&lt;em>001 test&lt;/em>-001.csv&lt;/p>
&lt;/blockquote>
&lt;p>但手工操作量还是太大，这时候我们就需要 xargs 了：&lt;/p>
&lt;blockquote>
&lt;p>ls | grep test__ | xargs -n1 -I file mv file file.csv&lt;/p>
&lt;/blockquote>
&lt;p>&lt;code>ls | grep test__&lt;/code>筛选了所有缺少扩展名的文件，管道命令传给 xargs 后，&lt;code>-n1&lt;/code>代表一次接受一个参数，&lt;code>-I file&lt;/code>告诉 xargs 后续命令中的&lt;code>file&lt;/code>全部用参数值替换。&lt;/p>
&lt;p>比如只有一个文件的时候，&lt;code>ls | grep test__&lt;/code>只有一个结果&lt;code>test__001&lt;/code>，xargs 接收到参数，将&lt;code>file&lt;/code>替换成&lt;code>test__001&lt;/code>，实际执行内容就是：&lt;/p>
&lt;blockquote>
&lt;p>mv test&lt;strong>001 test&lt;/strong>001.csv&lt;/p>
&lt;/blockquote>
&lt;p>将他们组合一下：&lt;/p>
&lt;blockquote>
&lt;p>split -l 1000 test.csv -d -a 3 test**&amp;amp;&amp;amp; ls | grep test** | xargs -n1 -I file mv file file.csv&lt;/p>
&lt;/blockquote>
&lt;p>回车执行，1 秒都不需要，爽死了。&lt;/p>
&lt;h2 id="人类是懒惰的动物">人类是懒惰的动物&lt;/h2>
&lt;p>但这样还是需要自己手打需要切割的文件名，不够爽。尝试把代码再变长一点：&lt;/p>
&lt;blockquote>
&lt;p>ls | grep .csv$ | sed &amp;rsquo;s/.csv//g&amp;rsquo; | xargs -n1 -I file split -l 1000 file.csv -d -a 3 file**&amp;amp;&amp;amp; ls | grep** | xargs -n1 -I file mv file file.csv&lt;/p>
&lt;/blockquote>
&lt;p>以管道命令分割解释一下：&lt;/p>
&lt;ol>
&lt;li>&lt;code>ls&lt;/code> 列出当前目录下所有文件和子目录&lt;/li>
&lt;li>&lt;code>grep .csv$&lt;/code> 筛选扩展名是 csv 的所有文件&lt;/li>
&lt;li>&lt;code>sed 's/.csv//g'&lt;/code> 去除扩展名，只留文件名&lt;/li>
&lt;li>&lt;code>xargs -n1 -I file split -l 1000 file.csv -d -a 3 file__&lt;/code> 切割所有的文件&lt;/li>
&lt;li>&lt;code>ls&lt;/code> 列出当前目录下所有文件和子目录&lt;/li>
&lt;li>&lt;code>grep __&lt;/code> 筛选文件名包含&lt;code>__&lt;/code>的文件（上一步切割好的文件）&lt;/li>
&lt;li>&lt;code>xargs -n1 -I file mv file file.csv&lt;/code> 加上扩展名&lt;/li>
&lt;/ol>
&lt;p>爽又死了。&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>尝试在 windows 上（Cmder+Github）做类似的操作，发现远没有在虚拟机（Ubuntu）里执行快，垃圾微软.jpg&lt;/p></description></item><item><title>RSS 咕咕机</title><link>https://naizi.ch/2019/10/02/rss-%E5%92%95%E5%92%95%E6%9C%BA/</link><pubDate>Wed, 02 Oct 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/10/02/rss-%E5%92%95%E5%92%95%E6%9C%BA/</guid><description>&lt;p>之前从同事那里二手买了个咕咕机，当玩具玩。享受了几天使用 APP 打印各种沙雕照片和表情包带来的乐趣后，它毫无意外的开始吃灰，就和桌子旁边的树莓派一样染上历史的颜色。&lt;/p>
&lt;p>还是拿出来用用吧……&lt;/p>
&lt;p>咕咕机 APP 里的一部分订阅号的确有点意思，比如订阅每天天气，早上 8 点自动打印一条天气内容之类的。那么拿来订阅微博更新好像也没什么不对的。&lt;/p>
&lt;p>反正一天有 12 小时在公司，闲着也是闲着，不定时的来一段小纸条也不错。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="开发者申请">开发者申请&lt;/h2>
&lt;p>为了做到这些，首先要去咕咕机的&lt;a class="link" href="http://open.memobird.cn" target="_blank" rel="noopener"
>开发者页面&lt;/a>申请 APPKEY。填写必要的信息然后提交申请就行了。等了一周左右申请就通过了。&lt;/p>
&lt;p>拿到 APPKEY 之后，可以查阅官方给的文档，可用的 API 其实不多，更多还是要靠自己去生成内容。&lt;/p>
&lt;p>&lt;a class="link" href="https://github.com/Windfarer/pymobird" target="_blank" rel="noopener"
>GitHub&lt;/a>上也有其他开发者写的 SDK，可以不用自己重复按照官方文档造轮子。&lt;/p>
&lt;h2 id="rss">RSS&lt;/h2>
&lt;p>打个比方，订阅&lt;a class="link" href="https://space.bilibili.com/332704117/dynamic" target="_blank" rel="noopener"
>小狐狸&lt;/a>的 B 站动态。&lt;/p>
&lt;p>熟练的打开小狐狸的空间，F12，刷新，过滤 XHR，找到 API 接口&lt;code>https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history&lt;/code>。B 站的动态有多种类型，需要根据类型判断爬取的内容。&lt;/p>
&lt;p>&lt;code>['data']['cards']['desc']['type']&lt;/code>的值表明了动态是何种类型，比如 1 是指转发，2 是带图动态，4 是纯文字动态，8 是投稿视频推送。&lt;/p>
&lt;p>在 &lt;code>['data']['cards']['card']&lt;/code> 中是一串 JSON 数据，动态的文本内容根据上面不同的类型存在不同的键值里，比如&lt;code>['dynamic']&lt;/code>、&lt;code>['item']['content']&lt;/code>、&lt;code>['item']['description']&lt;/code>这几个位置。&lt;/p>
&lt;h2 id="图片存储">图片存储&lt;/h2>
&lt;p>只有文本内容的纸条太单调了，信息量也不够，还是需要能够同时打印图片内容。但图片打印会增加打印时间和下载时间，折衷选择只保存附带图片的第一张。&lt;/p>
&lt;p>为了保持目录整洁，图片数据全部用 sqlite3 存储。&lt;/p>
&lt;h2 id="内容更新">内容更新&lt;/h2>
&lt;p>为了避免重复打印浪费纸张，每次爬取数据时需要判断是否有新的内容。&lt;/p>
&lt;p>建数据表，只存储必要的时间、日期、文字内容和图片数据，加上动态的 ID 作为主键来进行去重。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">TABLE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">IF&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">NOT&lt;/span> &lt;span style="color:#66d9ef">EXISTS&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">白上吹雪&lt;/span>Official (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">day&lt;/span> VARCHAR,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _id VARCHAR &lt;span style="color:#66d9ef">PRIMARY&lt;/span> &lt;span style="color:#66d9ef">KEY&lt;/span> &lt;span style="color:#66d9ef">UNIQUE&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> msg VARCHAR,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pic BLOB,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title VARCHAR);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后人工插入一条 id 为 0 的数据，用来记录是否获取到新内容，每次先单独查询这一行，有新数据再取出内容进行打印。&lt;/p>
&lt;h2 id="post-请求打印">POST 请求打印&lt;/h2>
&lt;p>利用之前提到的 SDK，将文字和图片的复合内容整合后一起 POST 给服务器。&lt;/p>
&lt;h3 id="小花样">小花样&lt;/h3>
&lt;p>为了区分普通的带图动态和视频投稿的推送，仿照动态的样式给视频投稿的图像加上了角标。同时因为咕咕机采用的是 WiFi 打印，为了减小文件体积，POST 数据前对图片尺寸进行缩小。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pymobird &lt;span style="color:#f92672">import&lt;/span> Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> pymobird &lt;span style="color:#f92672">import&lt;/span> Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>bird &lt;span style="color:#f92672">=&lt;/span> SimplePymobird(appkey, device_id, user_id)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content &lt;span style="color:#f92672">=&lt;/span> Content()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content&lt;span style="color:#f92672">.&lt;/span>add_text(&lt;span style="color:#e6db74">&amp;#39;【&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">】&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname)) &lt;span style="color:#75715e"># 用户名&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content&lt;span style="color:#f92672">.&lt;/span>add_text(time&lt;span style="color:#f92672">.&lt;/span>strftime(&lt;span style="color:#e6db74">&amp;#39;%Y-%m-&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74"> %H:%M&amp;#39;&lt;/span>,time&lt;span style="color:#f92672">.&lt;/span>gmtime(int(day)))) &lt;span style="color:#75715e"># 日期&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content&lt;span style="color:#f92672">.&lt;/span>add_text(&lt;span style="color:#e6db74">&amp;#39;https://t.bilibili.com/&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(_id)) &lt;span style="color:#75715e"># 链接&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content&lt;span style="color:#f92672">.&lt;/span>add_text(msg) &lt;span style="color:#75715e"># 文字内容&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> Image
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> ImageDraw
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> ImageFont
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>img &lt;span style="color:#f92672">=&lt;/span> Image&lt;span style="color:#f92672">.&lt;/span>open(&lt;span style="color:#e6db74">&amp;#39;peitu.jpg&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>img&lt;span style="color:#f92672">.&lt;/span>thumbnail((&lt;span style="color:#ae81ff">400&lt;/span>, &lt;span style="color:#ae81ff">400&lt;/span>), Image&lt;span style="color:#f92672">.&lt;/span>ANTIALIAS)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>font &lt;span style="color:#f92672">=&lt;/span> ImageFont&lt;span style="color:#f92672">.&lt;/span>truetype(&lt;span style="color:#e6db74">&amp;#39;SourceHanSansCN-Regular.otf&amp;#39;&lt;/span>, &lt;span style="color:#ae81ff">22&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>draw &lt;span style="color:#f92672">=&lt;/span> ImageDraw&lt;span style="color:#f92672">.&lt;/span>Draw(img)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>draw&lt;span style="color:#f92672">.&lt;/span>rectangle(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#ae81ff">268&lt;/span>, &lt;span style="color:#ae81ff">15&lt;/span>, &lt;span style="color:#ae81ff">368&lt;/span>, &lt;span style="color:#ae81ff">47&lt;/span>), fill&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">250&lt;/span>, &lt;span style="color:#ae81ff">115&lt;/span>, &lt;span style="color:#ae81ff">150&lt;/span>), outline&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">250&lt;/span>, &lt;span style="color:#ae81ff">140&lt;/span>, &lt;span style="color:#ae81ff">160&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>draw&lt;span style="color:#f92672">.&lt;/span>text((&lt;span style="color:#ae81ff">274&lt;/span>, &lt;span style="color:#ae81ff">14&lt;/span>), &lt;span style="color:#e6db74">&amp;#39;投稿视频&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;#FFFFFF&amp;#39;&lt;/span>, font&lt;span style="color:#f92672">=&lt;/span>font)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>img&lt;span style="color:#f92672">.&lt;/span>save(&lt;span style="color:#e6db74">&amp;#39;peitu.png&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;PNG&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>content&lt;span style="color:#f92672">.&lt;/span>add_image(&lt;span style="color:#e6db74">&amp;#39;peitu.png&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result &lt;span style="color:#f92672">=&lt;/span> bird&lt;span style="color:#f92672">.&lt;/span>print_multi_part_content(content)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="crontab-定时任务">Crontab 定时任务&lt;/h2>
&lt;p>开 SSH 把脚本扔进富有历史感的树莓派。&lt;/p>
&lt;blockquote>
&lt;p>crontab -e&lt;/p>
&lt;p>*/5 * * * * python3 bilibili_fubuki.py&lt;/p>
&lt;/blockquote>
&lt;h2 id="完整代码">完整代码&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/python&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> sqlite3
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> time
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## bilibili&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uname &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;白上吹雪 Official&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>uid &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;332704117&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## memobird&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>appkey &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>device_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>user_id &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>headers &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;User-Agent&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:69.0) Gecko/20100101 Firefox/69.0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;application/json, text/plain, */*&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept-Language&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Origin&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;https://space.bilibili.com&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;DNT&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Connection&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;keep-alive&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Referer&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;https://space.bilibili.com/&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>newfile &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">not&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>exists(&lt;span style="color:#e6db74">&amp;#39;bilibili_&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.sqlite&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>db &lt;span style="color:#f92672">=&lt;/span> sqlite3&lt;span style="color:#f92672">.&lt;/span>connect(&lt;span style="color:#e6db74">&amp;#39;bilibili_&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.sqlite&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname), isolation_level&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">None&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cur &lt;span style="color:#f92672">=&lt;/span> db&lt;span style="color:#f92672">.&lt;/span>cursor()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>cur&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">CREATE TABLE
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">IF
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> NOT EXISTS &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> (
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> day VARCHAR,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> _id VARCHAR PRIMARY KEY UNIQUE,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> msg VARCHAR,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> pic BLOB,
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74"> title VARCHAR);&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> newfile:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cur&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;INSERT INTO &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> VALUES ( ?, ?, ?, ?, ? );&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#66d9ef">None&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>, &lt;span style="color:#66d9ef">None&lt;/span>, &lt;span style="color:#66d9ef">None&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">bili_pic&lt;/span>(link):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> file &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>get(link, headers&lt;span style="color:#f92672">=&lt;/span>headers)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> file&lt;span style="color:#f92672">.&lt;/span>content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;visitor_uid&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;host_uid&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uid)),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;offset_dynamic_id&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.vc.bilibili.com/dynamic_svr/v1/dynamic_svr/space_history&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> headers&lt;span style="color:#f92672">=&lt;/span>headers,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params&lt;span style="color:#f92672">=&lt;/span>params)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;code&amp;#39;&lt;/span>) &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;message&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> card &lt;span style="color:#f92672">in&lt;/span> result[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;cards&amp;#39;&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> card_time &lt;span style="color:#f92672">=&lt;/span> card[&lt;span style="color:#e6db74">&amp;#39;desc&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;timestamp&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> card_id &lt;span style="color:#f92672">=&lt;/span> card[&lt;span style="color:#e6db74">&amp;#39;desc&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;dynamic_id_str&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> card_type &lt;span style="color:#f92672">=&lt;/span> card[&lt;span style="color:#e6db74">&amp;#39;desc&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;type&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(card[&lt;span style="color:#e6db74">&amp;#39;card&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> card_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;title&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pic &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;pic&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;dynamic&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> card_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">4&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pic &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;content&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> card_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>]&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;pictures&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pic &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;pictures&amp;#39;&lt;/span>][&lt;span style="color:#ae81ff">0&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;img_src&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;description&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> card_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> title &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pic &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> text &lt;span style="color:#f92672">=&lt;/span> data[&lt;span style="color:#e6db74">&amp;#39;item&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;content&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(card_time, card_id, card_type)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">continue&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> img &lt;span style="color:#f92672">=&lt;/span> bili_pic(pic) &lt;span style="color:#66d9ef">if&lt;/span> pic &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cur&lt;span style="color:#f92672">.&lt;/span>execute(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;INSERT OR REPLACE INTO &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> VALUES ( ?, ?, ?, ?, ? );&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uname), (card_time, card_id, text, img, title))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> new &lt;span style="color:#f92672">=&lt;/span> cur&lt;span style="color:#f92672">.&lt;/span>execute(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;SELECT COUNT(_id) FROM &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">;&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))&lt;span style="color:#f92672">.&lt;/span>fetchone()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> old &lt;span style="color:#f92672">=&lt;/span> cur&lt;span style="color:#f92672">.&lt;/span>execute(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;SELECT msg FROM &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> WHERE _id = 0;&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))&lt;span style="color:#f92672">.&lt;/span>fetchone()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> int(new[&lt;span style="color:#ae81ff">0&lt;/span>]) &lt;span style="color:#f92672">==&lt;/span> int(old[&lt;span style="color:#ae81ff">0&lt;/span>]):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cur&lt;span style="color:#f92672">.&lt;/span>execute(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;UPDATE &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> SET msg = ? WHERE _id = 0&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (str(new[&lt;span style="color:#ae81ff">0&lt;/span>]), ))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> limit &lt;span style="color:#f92672">=&lt;/span> int(new[&lt;span style="color:#ae81ff">0&lt;/span>]) &lt;span style="color:#f92672">-&lt;/span> int(old[&lt;span style="color:#ae81ff">0&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> limit &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> limit &lt;span style="color:#f92672">=&lt;/span> limit &lt;span style="color:#66d9ef">if&lt;/span> limit &lt;span style="color:#f92672">&amp;lt;&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from&lt;/span> pymobird &lt;span style="color:#f92672">import&lt;/span> Content
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from&lt;/span> pymobird &lt;span style="color:#f92672">import&lt;/span> SimplePymobird
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> bird &lt;span style="color:#f92672">=&lt;/span> SimplePymobird(ak&lt;span style="color:#f92672">=&lt;/span>appkey, device_id&lt;span style="color:#f92672">=&lt;/span>device_id, user_id&lt;span style="color:#f92672">=&lt;/span>user_id)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> data &lt;span style="color:#f92672">=&lt;/span> cur&lt;span style="color:#f92672">.&lt;/span>execute(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;SELECT day, _id, msg, pic, title FROM &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74"> ORDER BY _id DESC LIMIT &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">;&amp;#39;&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">.&lt;/span>format(uname, limit))&lt;span style="color:#f92672">.&lt;/span>fetchall()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> dynamic &lt;span style="color:#f92672">in&lt;/span> data[::&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>]:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> day, _id, msg, pic, title &lt;span style="color:#f92672">=&lt;/span> dynamic
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content &lt;span style="color:#f92672">=&lt;/span> Content()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_text(&lt;span style="color:#e6db74">&amp;#39;【&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">】&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_text(time&lt;span style="color:#f92672">.&lt;/span>strftime(&lt;span style="color:#e6db74">&amp;#39;%Y-%m-&lt;/span>&lt;span style="color:#e6db74">%d&lt;/span>&lt;span style="color:#e6db74"> %H:%M&amp;#39;&lt;/span>,time&lt;span style="color:#f92672">.&lt;/span>gmtime(int(day))))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_text(&lt;span style="color:#e6db74">&amp;#39;https://t.bilibili.com/&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(_id))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_text(msg)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> pic:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.jpg&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname), &lt;span style="color:#e6db74">&amp;#39;wb&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(pic)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> img &lt;span style="color:#f92672">=&lt;/span> Image&lt;span style="color:#f92672">.&lt;/span>open(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.jpg&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> img&lt;span style="color:#f92672">.&lt;/span>thumbnail((&lt;span style="color:#ae81ff">400&lt;/span>, &lt;span style="color:#ae81ff">400&lt;/span>), Image&lt;span style="color:#f92672">.&lt;/span>ANTIALIAS)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> title:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> Image
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> ImageDraw
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">from&lt;/span> PIL &lt;span style="color:#f92672">import&lt;/span> ImageFont
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_text(title)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> font &lt;span style="color:#f92672">=&lt;/span> ImageFont&lt;span style="color:#f92672">.&lt;/span>truetype(&lt;span style="color:#e6db74">&amp;#39;SourceHanSansCN-Regular.otf&amp;#39;&lt;/span>, &lt;span style="color:#ae81ff">22&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draw &lt;span style="color:#f92672">=&lt;/span> ImageDraw&lt;span style="color:#f92672">.&lt;/span>Draw(img)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draw&lt;span style="color:#f92672">.&lt;/span>rectangle((&lt;span style="color:#ae81ff">268&lt;/span>, &lt;span style="color:#ae81ff">15&lt;/span>, &lt;span style="color:#ae81ff">368&lt;/span>, &lt;span style="color:#ae81ff">47&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fill&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">250&lt;/span>, &lt;span style="color:#ae81ff">115&lt;/span>, &lt;span style="color:#ae81ff">150&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> outline&lt;span style="color:#f92672">=&lt;/span>(&lt;span style="color:#ae81ff">250&lt;/span>, &lt;span style="color:#ae81ff">140&lt;/span>, &lt;span style="color:#ae81ff">160&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> draw&lt;span style="color:#f92672">.&lt;/span>text((&lt;span style="color:#ae81ff">274&lt;/span>, &lt;span style="color:#ae81ff">14&lt;/span>), &lt;span style="color:#e6db74">&amp;#39;投稿视频&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;#FFFFFF&amp;#39;&lt;/span>, font&lt;span style="color:#f92672">=&lt;/span>font)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> img&lt;span style="color:#f92672">.&lt;/span>save(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.png&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname), &lt;span style="color:#e6db74">&amp;#39;PNG&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> content&lt;span style="color:#f92672">.&lt;/span>add_image(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.png&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(uname))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> bird&lt;span style="color:#f92672">.&lt;/span>print_multi_part_content(content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(result)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> time&lt;span style="color:#f92672">.&lt;/span>sleep(&lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;__main__&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>&lt;img src="https://naizi.ch/pic/2019-10-02-memobird-rss/2019-10-02_20-38.gif"
loading="lazy"
alt="gugugu"
>&lt;/p></description></item><item><title>哔哩哔哩下载工具 二</title><link>https://naizi.ch/2019/04/30/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-%E4%BA%8C/</link><pubDate>Tue, 30 Apr 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/04/30/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7-%E4%BA%8C/</guid><description>&lt;p>主要解决一些之前遇到的坑，填一些留下的 TODO。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="vegas-不支持-hevc-编码">Vegas 不支持 HEVC 编码&lt;/h2>
&lt;p>思路很简单，对下载下来的&lt;code>m4s&lt;/code>先读一遍编码，看是不是 HEVC 格式的，如果是就在转码的时候用&lt;code>libx264&lt;/code>编码，不是就粗暴的 copy 视频流。&lt;/p>
&lt;p>暂时不打算加上判断其他视频流格式的代码，遇到再说。&lt;/p>
&lt;h3 id="通过-ffprobe-判断编码">通过 ffprobe 判断编码&lt;/h3>
&lt;p>因为之前很少接触视频转码，也没什么机会用 ffmpeg。在判断 HEVC 编码这一步的时候，想着用类似&lt;code>ffmpeg -args | find &amp;quot;stream&amp;quot;&lt;/code>的方式来取得视频编码。&lt;/p>
&lt;p>可能是环境问题，没成功，而且还要另写正则匹配文本有些麻烦。查了一下改用 ffprobe。&lt;/p>
&lt;blockquote>
&lt;p>ffprobe.exe -v error -select_streams v:0 -show_entries stream=codec_name &lt;br>
-of default=nokey=1:noprint_wrappers=1 v.m4s &amp;raquo; ./t.txt&lt;/p>
&lt;/blockquote>
&lt;h2 id="ffmpeg-合并-flv">ffmpeg 合并 flv&lt;/h2>
&lt;p>ffmpeg 的问题主要出现在合并 flv 上，dash 视频倒没有什么问题。&lt;/p>
&lt;p>采用&lt;code>-f concat -i file.list&lt;/code>的方式合并遇到了路径问题。因为听取了建议“下载的文件最好单独放个目录，直接下在脚本根目录就会很乱”。&lt;/p>
&lt;p>所有的文件都下载在了子文件夹里，比如&lt;code>./down&lt;/code>。在下载 flv 分段的时候，文件的目录结构就类似：&lt;/p>
&lt;blockquote>
&lt;p>..
ffmpeg.exe
/down
/down/file.list
/down/part1.flv
/down/part2.flv&lt;/p>
&lt;/blockquote>
&lt;p>ffmpeg 的命令写成：&lt;/p>
&lt;blockquote>
&lt;p>ffmpeg -safe 0 -hide_banner -f concat -i ./down/file.list -c copy ./down/finish.mp4&lt;/p>
&lt;/blockquote>
&lt;p>问题来了，file.list 里该怎么写，我原本以为应该是：&lt;/p>
&lt;blockquote>
&lt;p>file &amp;lsquo;./down/part1.flv&amp;rsquo;
file &amp;lsquo;./down/part2.flv&amp;rsquo;
file &amp;lsquo;./down/part3.flv&amp;rsquo;&lt;/p>
&lt;/blockquote>
&lt;p>然而这样 ffmpeg 会报错找不到文件，根据错误信息里提示的文件路径，应该改成&lt;/p>
&lt;blockquote>
&lt;p>file &amp;lsquo;part1.flv&amp;rsquo;
file &amp;lsquo;part2.flv&amp;rsquo;
file &amp;lsquo;part3.flv&amp;rsquo;&lt;/p>
&lt;/blockquote>
&lt;p>看来 file.list 里的根目录是 file.list 所在目录。&lt;/p>
&lt;h2 id="通过子进程加速下载">通过子进程加速下载&lt;/h2>
&lt;p>音频和视频同时下载。因为原本用 os.system 的方式会阻塞，改用 subprocess.Popen 子进程，然后在 ffmpeg 合并前阻塞两个子进程，确保下载完毕再进行合并。&lt;/p>
&lt;p>单个任务的话就用上面的方式，然后开启多进程同时执行多个任务。但是这样又遇到了新的问题：&lt;/p>
&lt;h3 id="尝试多进程和-pyinstaller-的坑">尝试多进程和 pyinstaller 的坑&lt;/h3>
&lt;p>因为最后要打包成 exe 给其他人用，毕竟没必要让每个人都装一个 python 环境来跑脚本。&lt;/p>
&lt;p>在打包过程中发现，虽然 py 脚本能够正确执行，但是打包完成的 exe 反而变成了一个 fork 炸弹，会无限的产生子进程直到让系统宕机。&lt;/p>
&lt;p>如是三次之后 Google 了一下，pyinstaller 和 multiprocessing 相性不合，使用 mulitprocessing.Process 和 mulitprocessing.Pool 的代码在 pyinstaller 编译后就会出现这个问题。&lt;/p>
&lt;p>给出的解决方法只适用于 python2 版本，而我用的是 python3.7，摊手。&lt;/p>
&lt;h3 id="改用-threading-绕过问题">改用 threading 绕过问题&lt;/h3>
&lt;p>既然不能用多进程，那就用多线程好了。&lt;/p>
&lt;p>而且实际上我也的确是先用的 threading 库，遇到了问题才想的用 multiprocessing。&lt;/p>
&lt;p>原因是 threading 的场合，我编译 exe 完成，测试的时候想直接关闭窗口，发现做不到。&lt;/p>
&lt;p>结论而且这不算什么问题，比起 fork 炸弹要可接受的多了，而且一个下载工具，本来就没有必要在中途退出。&lt;/p>
&lt;p>最后上了线程池&lt;code>from multiprocessing.dummy import Pool as ThreadPool&lt;/code>，幸好这样并不会和 pyinstaller 冲突。&lt;/p>
&lt;h2 id="放弃写配置文件">放弃写配置文件&lt;/h2>
&lt;p>计划把 ffmpeg 和 aria2c 的配置单独存文件的来着，想想又要增加代码量，而且几乎不会去动这些配置，还是作罢了。&lt;/p>
&lt;p>硬编码不也挺好么.jpg&lt;/p>
&lt;h2 id="完整代码">完整代码&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/python&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> arrow
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> subprocess
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> lxml &lt;span style="color:#f92672">import&lt;/span> etree
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> multiprocessing.dummy &lt;span style="color:#f92672">import&lt;/span> Pool &lt;span style="color:#66d9ef">as&lt;/span> ThreadPool
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>today &lt;span style="color:#f92672">=&lt;/span> arrow&lt;span style="color:#f92672">.&lt;/span>utcnow()&lt;span style="color:#f92672">.&lt;/span>format(&lt;span style="color:#e6db74">&amp;#39;YYYY-MM-DD&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>exists(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today)):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>mkdir(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>session()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session&lt;span style="color:#f92672">.&lt;/span>headers &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;User-Agent&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;application/json, text/plain, */*&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept-Language&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Referer&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;https://www.bilibili.com/video/av42038790?from=search&amp;amp;seid=4077958841119274554&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Origin&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;https://www.bilibili.com&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;DNT&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Connection&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;keep-alive&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Pragma&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;no-cache&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Cache-Control&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;no-cache&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;cookies&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cookies &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session&lt;span style="color:#f92672">.&lt;/span>headers[&lt;span style="color:#e6db74">&amp;#39;cookie&amp;#39;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> cookies
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">videocid&lt;/span>(aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">42038790&lt;/span>, part&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;aid&amp;#39;&lt;/span>, aid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;jsonp&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;jsonp&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/pagelist&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params, ) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cid &lt;span style="color:#f92672">=&lt;/span> result[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>][part &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;cid&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> cid
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">videourl&lt;/span>(aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">42038790&lt;/span>, cid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">73802454&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;avid&amp;#39;&lt;/span>, aid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;cid&amp;#39;&lt;/span>, cid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;qn&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;112&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;fnver&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;fnval&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;16&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;type&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;otype&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;json&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/playurl&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params,) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>) &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/pgc/player/web/playurl&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params,) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> playinfo &lt;span style="color:#f92672">=&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> playinfo &lt;span style="color:#f92672">=&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> playinfo &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(aid, &lt;span style="color:#e6db74">&amp;#39;Not Found&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> playinfo&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aids &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;audio&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aurl &lt;span style="color:#f92672">=&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;audio&amp;#39;&lt;/span>][aids&lt;span style="color:#f92672">.&lt;/span>index(max(aids))][&lt;span style="color:#e6db74">&amp;#39;baseUrl&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vids &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;video&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vurl &lt;span style="color:#f92672">=&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;video&amp;#39;&lt;/span>][vids&lt;span style="color:#f92672">.&lt;/span>index(max(vids))][&lt;span style="color:#e6db74">&amp;#39;baseUrl&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ap &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c.exe -x10 -k1M --file-allocation=none --auto-file-renaming=false &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{aurl}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Pragma: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --out ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_a.m4s&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, aurl&lt;span style="color:#f92672">=&lt;/span>aurl, aid&lt;span style="color:#f92672">=&lt;/span>aid, cid&lt;span style="color:#f92672">=&lt;/span>cid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vp &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c -x10 -k1M --file-allocation=none --auto-file-renaming=false &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{vurl}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Pragma: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --out ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_v.m4s&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, vurl&lt;span style="color:#f92672">=&lt;/span>vurl, aid&lt;span style="color:#f92672">=&lt;/span>aid, cid&lt;span style="color:#f92672">=&lt;/span>cid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ap&lt;span style="color:#f92672">.&lt;/span>wait()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vp&lt;span style="color:#f92672">.&lt;/span>wait()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vt &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;ffprobe.exe -v error -select_streams v:0 -show_entries stream=codec_name&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> -of default=nokey=1:noprint_wrappers=1&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_v.m4s &amp;gt;&amp;gt; ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_t.txt&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vt&lt;span style="color:#f92672">.&lt;/span>wait()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_t.txt&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid), &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> v_type &lt;span style="color:#f92672">=&lt;/span> str(f&lt;span style="color:#f92672">.&lt;/span>read())&lt;span style="color:#f92672">.&lt;/span>replace(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> v_type &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;hevc&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;ffmpeg -n -hide_banner -i ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_v.m4s -i ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_a.m4s -c:v libx264 -c:a copy&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">.mp4&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid, aid&lt;span style="color:#f92672">=&lt;/span>aid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;ffmpeg -n -hide_banner -i ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_v.m4s -i ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_a.m4s -c:v copy -c:a copy&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">.mp4&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid, aid&lt;span style="color:#f92672">=&lt;/span>aid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_t.txt&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp&lt;span style="color:#f92672">.&lt;/span>wait()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_a.m4s&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{cid}&lt;/span>&lt;span style="color:#e6db74">_v.m4s&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, cid&lt;span style="color:#f92672">=&lt;/span>cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> playinfo&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;durl&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vlink &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;url&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;durl&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_merge.txt&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, aid&lt;span style="color:#f92672">=&lt;/span>aid), &lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> t:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> p, link &lt;span style="color:#f92672">in&lt;/span> enumerate(vlink):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t&lt;span style="color:#f92672">.&lt;/span>write(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;file &amp;#39;./av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_&lt;/span>&lt;span style="color:#e6db74">{p}&lt;/span>&lt;span style="color:#e6db74">.flv&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(aid&lt;span style="color:#f92672">=&lt;/span>aid, p&lt;span style="color:#f92672">=&lt;/span>p))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> p, link &lt;span style="color:#f92672">in&lt;/span> enumerate(vlink):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> subprocess&lt;span style="color:#f92672">.&lt;/span>check_call(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c.exe -x10 -k1M --file-allocation=none --auto-file-renaming=false &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{link}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept: */*&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;DNT: 1&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Connection: keep-alive&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --header=&amp;#34;Pragma: no-cache&amp;#34; --header=&amp;#34;Cache-Control: no-cache&amp;#34;&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> --out ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_&lt;/span>&lt;span style="color:#e6db74">{p}&lt;/span>&lt;span style="color:#e6db74">.flv&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, link&lt;span style="color:#f92672">=&lt;/span>link, aid&lt;span style="color:#f92672">=&lt;/span>aid, p&lt;span style="color:#f92672">=&lt;/span>p), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp &lt;span style="color:#f92672">=&lt;/span> subprocess&lt;span style="color:#f92672">.&lt;/span>Popen(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;ffmpeg -safe 0 -hide_banner -f concat -i ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_merge.txt -c copy&lt;/span>&lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span>&lt;span style="color:#e6db74"> ./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">.mp4&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, aid&lt;span style="color:#f92672">=&lt;/span>aid), shell&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#66d9ef">True&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> fp&lt;span style="color:#f92672">.&lt;/span>wait()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_merge.txt&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, aid&lt;span style="color:#f92672">=&lt;/span>aid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> p, link &lt;span style="color:#f92672">in&lt;/span> enumerate(vlink):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;./&lt;/span>&lt;span style="color:#e6db74">{today}&lt;/span>&lt;span style="color:#e6db74">/av&lt;/span>&lt;span style="color:#e6db74">{aid}&lt;/span>&lt;span style="color:#e6db74">_&lt;/span>&lt;span style="color:#e6db74">{p}&lt;/span>&lt;span style="color:#e6db74">.flv&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(today&lt;span style="color:#f92672">=&lt;/span>today, aid&lt;span style="color:#f92672">=&lt;/span>aid, p&lt;span style="color:#f92672">=&lt;/span>p))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool &lt;span style="color:#f92672">=&lt;/span> ThreadPool(processes&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">5&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;down.txt&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aids &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>findall(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;av(\d+)&amp;#39;&lt;/span>, f&lt;span style="color:#f92672">.&lt;/span>read())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> aids:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool&lt;span style="color:#f92672">.&lt;/span>apply_async(videourl, (x, videocid(x)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool&lt;span style="color:#f92672">.&lt;/span>close()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pool&lt;span style="color:#f92672">.&lt;/span>join()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;__main__&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="todo">TODO&lt;/h2>
&lt;ul>
&lt;li>&lt;del>部分视频的视频流是 HEVC 编码而不是 x264，Windows 读不出 MP4 缩略图，考虑要不要判断自动转码。&lt;/del>&lt;/li>
&lt;li>&lt;del>aria2 和 ffmpeg 的参数单独存文件方便修改，不然每次都要重新编译 exe。&lt;/del>&lt;/li>
&lt;li>分 P 选择和清晰度选择&lt;/li>
&lt;/ul>
&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>哔哩哔哩下载工具</title><link>https://naizi.ch/2019/04/07/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7/</link><pubDate>Sun, 07 Apr 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/04/07/%E5%93%94%E5%93%A9%E5%93%94%E5%93%A9%E4%B8%8B%E8%BD%BD%E5%B7%A5%E5%85%B7/</guid><description>&lt;p>给哔哩哔哩周刊组写的下载工具。每次都要下载十几个视频，一个个手动来实在是太麻烦了。&lt;/p>
&lt;p>看了下，发现下载最高清一路的视频源还是需要 cookie 认证，写不来登录，只能用 dirty 的方式读 cookie 文件了。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="分析">分析&lt;/h2>
&lt;p>获取视频需要知道视频的 av 号和视频 id，api 接口里分别是&lt;code>avid&lt;/code>和&lt;code>cid&lt;/code>，有了两者之后就可以通过 api 获得视频不同清晰度的 url 地址。&lt;/p>
&lt;p>B 站现在新视频全部使用了 dash，但是老视频没有 dash，还是单个 flv，两者在 api 上返回有区别，前者是&lt;code>dash&lt;/code>字段，后者是&lt;code>durl&lt;/code>字段，需要判断是哪一种视频流。&lt;/p>
&lt;p>对于 dash，音频和视频流分割两路，都是 m4s 格式，下载后需要合并。而 flv 根据视频时长会有多个分段文件，同样也需要合并。&lt;/p>
&lt;h2 id="代码">代码&lt;/h2>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/usr/bin/python&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">## -*- coding: utf-8 -*-&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> requests
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> lxml &lt;span style="color:#f92672">import&lt;/span> etree
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>session()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session&lt;span style="color:#f92672">.&lt;/span>headers &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;User-Agent&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;application/json, text/plain, */*&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept-Language&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e"># &amp;#39;Referer&amp;#39;: &amp;#39;https://www.bilibili.com/video/av42038790&amp;#39;,&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Origin&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;https://www.bilibili.com&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;DNT&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Connection&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;keep-alive&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Pragma&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;no-cache&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Cache-Control&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;no-cache&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;cookies&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cookies &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>session&lt;span style="color:#f92672">.&lt;/span>headers[&lt;span style="color:#e6db74">&amp;#34;cookie&amp;#34;&lt;/span>] &lt;span style="color:#f92672">=&lt;/span> cookies
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">videocid&lt;/span>(aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">42038790&lt;/span>, part&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">1&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;aid&amp;#39;&lt;/span>, aid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;jsonp&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;jsonp&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/pagelist&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params, ) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cid &lt;span style="color:#f92672">=&lt;/span> result[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>][part &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;cid&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> cid
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">videourl&lt;/span>(aid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">42038790&lt;/span>, cid&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#ae81ff">73802454&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;avid&amp;#39;&lt;/span>, aid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;cid&amp;#39;&lt;/span>, cid),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;qn&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;112&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;fnver&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;fnval&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;16&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;type&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;otype&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;json&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/x/player/playurl&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params,) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>) &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> session&lt;span style="color:#f92672">.&lt;/span>get(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;https://api.bilibili.com/pgc/player/web/playurl&amp;#39;&lt;/span>, params&lt;span style="color:#f92672">=&lt;/span>params,) &lt;span style="color:#75715e"># cookies=cookies)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> playinfo &lt;span style="color:#f92672">=&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;result&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> playinfo &lt;span style="color:#f92672">=&lt;/span> result&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> playinfo &lt;span style="color:#f92672">is&lt;/span> &lt;span style="color:#66d9ef">None&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(aid, &lt;span style="color:#e6db74">&amp;#39;Not Found&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> playinfo&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vids &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;video&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vurl &lt;span style="color:#f92672">=&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;video&amp;#39;&lt;/span>][vids&lt;span style="color:#f92672">.&lt;/span>index(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max(vids))][&lt;span style="color:#e6db74">&amp;#39;baseUrl&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aids &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;id&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;audio&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aurl &lt;span style="color:#f92672">=&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;dash&amp;#39;&lt;/span>][&lt;span style="color:#e6db74">&amp;#39;audio&amp;#39;&lt;/span>][aids&lt;span style="color:#f92672">.&lt;/span>index(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> max(aids))][&lt;span style="color:#e6db74">&amp;#39;baseUrl&amp;#39;&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>system(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c -x10 -k1M --file-allocation=none &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34; --header=&amp;#34;Accept: */*&amp;#34; --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34; --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34; --header=&amp;#34;DNT: 1&amp;#34; --header=&amp;#34;Connection: keep-alive&amp;#34; --header=&amp;#34;Pragma: no-cache&amp;#34; --header=&amp;#34;Cache-Control: no-cache&amp;#34; --out &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_v.m4s&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(vurl, aid, cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>system(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c.exe -x10 -k1M --file-allocation=none &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34; --header=&amp;#34;Accept: */*&amp;#34; --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34; --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34; --header=&amp;#34;DNT: 1&amp;#34; --header=&amp;#34;Connection: keep-alive&amp;#34; --header=&amp;#34;Pragma: no-cache&amp;#34; --header=&amp;#34;Cache-Control: no-cache&amp;#34; --out &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_a.m4s&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(aurl, aid, cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>system(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;ffmpeg -i &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_v.m4s -i &lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_a.m4s -c:v copy -c:a copy -strict experimental av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.mp4&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(cid, cid, aid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_a.m4s&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_v.m4s&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(cid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">elif&lt;/span> playinfo&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;durl&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> vlink &lt;span style="color:#f92672">=&lt;/span> [x[&lt;span style="color:#e6db74">&amp;#39;url&amp;#39;&lt;/span>] &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> playinfo[&lt;span style="color:#e6db74">&amp;#39;durl&amp;#39;&lt;/span>]]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> print(vlink)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> p, link &lt;span style="color:#f92672">in&lt;/span> enumerate(vlink):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>system(&lt;span style="color:#e6db74">&amp;#39;&amp;#39;&amp;#39;aria2c.exe -x10 -k1M --file-allocation=none &amp;#34;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:65.0) Gecko/20100101 Firefox/65.0&amp;#34; --header=&amp;#34;Accept: */*&amp;#34; --header=&amp;#34;Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2&amp;#34; --header=&amp;#34;Referer: https://www.bilibili.com/video/av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#34; --header=&amp;#34;Origin: https://www.bilibili.com&amp;#34; --header=&amp;#34;DNT: 1&amp;#34; --header=&amp;#34;Connection: keep-alive&amp;#34; --header=&amp;#34;Pragma: no-cache&amp;#34; --header=&amp;#34;Cache-Control: no-cache&amp;#34; --out av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">_&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.flv&amp;#39;&amp;#39;&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(link, aid, aid, p))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;merge.txt&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> t:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>listdir(&lt;span style="color:#e6db74">&amp;#39;.&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> str(aid) &lt;span style="color:#f92672">in&lt;/span> x:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> t&lt;span style="color:#f92672">.&lt;/span>write(&lt;span style="color:#e6db74">&amp;#34;file &amp;#39;&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(x))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>system(&lt;span style="color:#e6db74">&amp;#39;ffmpeg -f concat -i merge.txt -c copy av&lt;/span>&lt;span style="color:#e6db74">{}&lt;/span>&lt;span style="color:#e6db74">.mp4&amp;#39;&lt;/span>&lt;span style="color:#f92672">.&lt;/span>format(aid))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(&lt;span style="color:#e6db74">&amp;#39;merge.txt&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>listdir(&lt;span style="color:#e6db74">&amp;#39;.&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> x&lt;span style="color:#f92672">.&lt;/span>endswith(&lt;span style="color:#e6db74">&amp;#39;.flv&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">pass&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> os&lt;span style="color:#f92672">.&lt;/span>remove(x)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;down.txt&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> aids &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>findall(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;av(\d+)&amp;#39;&lt;/span>, f&lt;span style="color:#f92672">.&lt;/span>read())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">for&lt;/span> x &lt;span style="color:#f92672">in&lt;/span> aids:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> videourl(x, videocid(x))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#39;__main__&amp;#39;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="todo">TODO&lt;/h2>
&lt;ul>
&lt;li>部分视频的视频流是 HEVC 编码而不是 x264，Windows 读不出 MP4 缩略图，考虑要不要判断自动转码。&lt;/li>
&lt;li>aria2 和 ffmpeg 的参数单独存文件方便修改，不然每次都要重新编译 exe。&lt;/li>
&lt;li>分 P 选择和清晰度选择&lt;/li>
&lt;/ul>
&lt;h2 id="eof">EOF&lt;/h2></description></item><item><title>没有 KMSCON 的第一天，想他</title><link>https://naizi.ch/2019/01/10/%E6%B2%A1%E6%9C%89-kmscon-%E7%9A%84%E7%AC%AC%E4%B8%80%E5%A4%A9%E6%83%B3%E4%BB%96/</link><pubDate>Thu, 10 Jan 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/01/10/%E6%B2%A1%E6%9C%89-kmscon-%E7%9A%84%E7%AC%AC%E4%B8%80%E5%A4%A9%E6%83%B3%E4%BB%96/</guid><description>&lt;p>因为树莓派用了官方系统，原本在 Arch 上利用&lt;a class="link" href="https://wiki.archlinux.org/index.php/KMSCON" target="_blank" rel="noopener"
>KMSCON&lt;/a>来回显中文字符的方式不可行了，沉痛悼念。&lt;/p>
&lt;p>不会编译是罪魁祸首，而且包依赖关系看得头大，实在不想自己解决。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="前情提要">前情提要&lt;/h2>
&lt;p>因为 tty 设备不支持 CJK 字符，原本 crontab 任务只要重定向输出到&lt;code>KMSCON&lt;/code>模拟的 pts 设备上就能正常显示中文，现在不得不回归起点。&lt;/p>
&lt;p>选择只有两种：&lt;/p>
&lt;ol>
&lt;li>研究怎么在 debian 上把&lt;code>KMSCON&lt;/code>编译过去&lt;/li>
&lt;li>研究怎么在图形界面输出工作用脚本的日志&lt;/li>
&lt;/ol>
&lt;p>然后&lt;code>KMSCON&lt;/code>的编译依赖实在有点多，在 debian 内的包名又都不一样。本来就只会按照教程 &lt;code>./configure&lt;/code>、&lt;code>make&lt;/code>、&lt;code>make install&lt;/code>三步走的我实在是应付不来。&lt;/p>
&lt;p>结果只能当&lt;code>KMSCON&lt;/code>从来不存在，在图形界面寻找解决办法。&lt;/p>
&lt;h2 id="变通法">变通法&lt;/h2>
&lt;p>搜索之后发现的确有从终端弹图形界面的方式，只需要指定显示设备就行了：&lt;/p>
&lt;blockquote>
&lt;p>DISPLAY=:0.0 command&lt;/p>
&lt;/blockquote>
&lt;p>因为我的目的只是需要一个终端窗口，所以：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; crontab -e
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@reboot sleep &lt;span style="color:#ae81ff">30&lt;/span> &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> DISPLAY&lt;span style="color:#f92672">=&lt;/span>:0.0 /usr/bin/lxterminal
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样便会在系统启动之后自动启动一个终端窗口，分配一个 pts 的设备，因为是第一个终端窗口所以一定是 pts/0&lt;/p>
&lt;p>再把剩下的定时脚本输出全部重定向到 &lt;code>/dev/pts/0&lt;/code> 就行了&lt;/p>
&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>感觉还是会有回到 Arch 的一天，Arch 真香。&lt;/p></description></item><item><title>微博信息流广告抓取</title><link>https://naizi.ch/2019/01/05/%E5%BE%AE%E5%8D%9A%E4%BF%A1%E6%81%AF%E6%B5%81%E5%B9%BF%E5%91%8A%E6%8A%93%E5%8F%96/</link><pubDate>Sat, 05 Jan 2019 00:00:00 +0000</pubDate><guid>https://naizi.ch/2019/01/05/%E5%BE%AE%E5%8D%9A%E4%BF%A1%E6%81%AF%E6%B5%81%E5%B9%BF%E5%91%8A%E6%8A%93%E5%8F%96/</guid><description>&lt;p>微博注册了个小号，为了看看都是什么牛鬼蛇神在微博里推送信息流广告，尝试抓取一下。&lt;/p>
&lt;p>虽然有&lt;a class="link" href="https://github.com/tiansh/yaofang" target="_blank" rel="noopener"
>YAWF（Yet Another Weibo Filter）&lt;/a>这样好用的脚本可以屏蔽广告，但屏蔽了就看不到了。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="找到-api-接口">找到 API 接口&lt;/h2>
&lt;p>F12 切换到 Network，在首页刷新页面，筛选捕捉到 api 接口&lt;code>https://weibo.com/aj/mblog/fsearch&lt;/code>。&lt;/p>
&lt;p>新建标签页访问，去掉不必要的参数（包括一堆用来指定上下文区间和已读分界线的 id），只留下必要的部分。&lt;/p>
&lt;p>然后直接上代码开始爬取内容：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>headers &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;User-Agent&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;Mozilla/5.0 (X11; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Accept-Language&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;en-US,en;q=0.5&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;DNT&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Connection&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;keep-alive&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Upgrade-Insecure-Requests&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Cache-Control&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;max-age=0&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;Cookie&amp;#39;&lt;/span>: cookie
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>params &lt;span style="color:#f92672">=&lt;/span> (
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;ajwvr&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;6&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;pre_page&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;page&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;1&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> (&lt;span style="color:#e6db74">&amp;#39;pagebar&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;0&amp;#39;&lt;/span>),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;https://weibo.com/aj/mblog/fsearch&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> headers&lt;span style="color:#f92672">=&lt;/span>headers, params&lt;span style="color:#f92672">=&lt;/span>params)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>返回数据是 json 结构，在&lt;code>data&lt;/code>字段里存放了页面内容，这里用 lxml 库解析，保存 html 文件进行下一步。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> json
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">from&lt;/span> lxml &lt;span style="color:#f92672">import&lt;/span> etree
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>html &lt;span style="color:#f92672">=&lt;/span> etree&lt;span style="color:#f92672">.&lt;/span>HTML(result[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;feed.html&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(etree&lt;span style="color:#f92672">.&lt;/span>tostring(html, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;unicode&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="css-筛选广告内容">CSS 筛选广告内容&lt;/h2>
&lt;p>打开保存的 html，格式化一下代码，找到存在&lt;code>feedtype=&amp;quot;ad&amp;quot;&lt;/code>属性的节点（因为不是每次获取都会有信息流广告，所以可能需要获取很多次）。&lt;/p>
&lt;p>然后继续往下看子节点，直到找到具体的微博链接：&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2019-01-05-weibo-feed-adver/2019-01-05_17-47.png"
loading="lazy"
alt="信息流 HTML"
>&lt;/p>
&lt;p>获取此节点的 xpath：&lt;code>&amp;quot;//div[@feedtype]//a[@action-history='rec=1']/@action-data&amp;quot;&lt;/code>&lt;/p>
&lt;p>结合正则表达式提取链接：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> re
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>html &lt;span style="color:#f92672">=&lt;/span> etree&lt;span style="color:#f92672">.&lt;/span>HTML(result[&lt;span style="color:#e6db74">&amp;#39;data&amp;#39;&lt;/span>])
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>links &lt;span style="color:#f92672">=&lt;/span> html&lt;span style="color:#f92672">.&lt;/span>xpath(&lt;span style="color:#e6db74">&amp;#34;//div[@feedtype]//a[@action-history=&amp;#39;rec=1&amp;#39;]/@action-data&amp;#34;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">for&lt;/span> link &lt;span style="color:#f92672">in&lt;/span> links:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> adver &lt;span style="color:#f92672">=&lt;/span> re&lt;span style="color:#f92672">.&lt;/span>findall(&lt;span style="color:#e6db74">r&lt;/span>&lt;span style="color:#e6db74">&amp;#39;url=([^&amp;amp;]+)&amp;amp;&amp;#39;&lt;/span>, link)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;adver.list&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;a&amp;#39;&lt;/span>, encoding&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;utf-8-sig&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(str(adver)&lt;span style="color:#f92672">+&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最后通过 crontab 设置成定时任务自动监测。&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-crontab.conf" data-lang="crontab.conf">* * * * * python weibo_adver.py
&lt;/code>&lt;/pre>&lt;h2 id="自动更新-cookie">自动更新 cookie&lt;/h2>
&lt;p>高频率的连续访问可能会造成微博提示帐号异常，而且运行时间久了 cookie 也会过期，需要自动重新登录获取 cookie。&lt;/p>
&lt;p>微博的自动登录轮子很多，不用自己另写。cookie 过期的判断就比较粗暴了，如果读取返回的 json 抛出异常，就可以认为是 cookie 过期造成的。&lt;/p>
&lt;p>另外测试发现，不带 cookie 的 api 访问是会返回正常的 json 数据的（但不包含 html 内容）。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">import&lt;/span> os
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">login&lt;/span>(username, password):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;somecode balabala&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;cookies&amp;#39;&lt;/span>,&lt;span style="color:#e6db74">&amp;#39;w&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> f&lt;span style="color:#f92672">.&lt;/span>write(cookies)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">def&lt;/span> &lt;span style="color:#a6e22e">main&lt;/span>():
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>exists(&lt;span style="color:#e6db74">&amp;#39;./cookies&amp;#39;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#39;./cookies&amp;#39;&lt;/span>, &lt;span style="color:#e6db74">&amp;#39;r&amp;#39;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> f:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cookie &lt;span style="color:#f92672">=&lt;/span> f&lt;span style="color:#f92672">.&lt;/span>read()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;somecode balabala&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> response &lt;span style="color:#f92672">=&lt;/span> requests&lt;span style="color:#f92672">.&lt;/span>get(&lt;span style="color:#e6db74">&amp;#39;https://weibo.com/aj/mblog/fsearch&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> headers&lt;span style="color:#f92672">=&lt;/span>headers, params&lt;span style="color:#f92672">=&lt;/span>params)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">try&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> result &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>loads(response&lt;span style="color:#f92672">.&lt;/span>content)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">except&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>decoder&lt;span style="color:#f92672">.&lt;/span>JSONDecodeError:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> login(username, password)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">False&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#34;&amp;#34;&amp;#34;somecode balabala&amp;#34;&amp;#34;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> __name__ &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#e6db74">&amp;#34;__main__&amp;#34;&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> main()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>就结果而言效果还不错，但也发现信息流广告里还会有一些关注了的博主的正常微博内容，可以考虑和自己的关注列表对比来筛除。&lt;/p>
&lt;p>&lt;img src="https://naizi.ch/pic/2019-01-05-weibo-feed-adver/2019-01-05_18-03.png"
loading="lazy"
alt="信息流广告"
>&lt;/p></description></item><item><title>从零开始 Raspberry Pi</title><link>https://naizi.ch/2018/12/08/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B-raspberry-pi/</link><pubDate>Sat, 08 Dec 2018 00:00:00 +0000</pubDate><guid>https://naizi.ch/2018/12/08/%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B-raspberry-pi/</guid><description>&lt;p>为了让吃灰的树莓派重新开始工作，这回不瞎折腾了，直接用官方系统。&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;h2 id="安装">安装&lt;/h2>
&lt;p>按照&lt;a class="link" href="https://www.raspberrypi.org/downloads/noobs/" target="_blank" rel="noopener"
>官方教程&lt;/a>采用 NOOBS 安装，当然也可以选择用&lt;a class="link" href="https://www.raspberrypi.org/downloads/raspbian/" target="_blank" rel="noopener"
>镜像安装&lt;/a>，不论哪一种，选择 ↓&lt;/p>
&lt;p>&lt;code>Raspbian Stretch with desktop and recommended software&lt;/code>&lt;/p>
&lt;p>安装完成后，首次进入图形界面，选择首选项中的 &lt;code>Raspberry Pi Configuration&lt;/code> ，在 &lt;code>Interfaces&lt;/code> 书签页启用 &lt;code>SSH&lt;/code>, &lt;code>VNC&lt;/code>，之后就可以不用另接键鼠了。&lt;/p>
&lt;p>记得在 &lt;code>Localisation&lt;/code> 书签页设定好 &lt;code>WiFi Country&lt;/code>。&lt;/p>
&lt;h2 id="连接-wifi">连接 WiFi&lt;/h2>
&lt;p>因为公司 WiFi 使用了 WPA-EAP 协议，树莓派不显示热点，让我一度以为树莓派不能连接此类 WiFi。后来在&lt;a class="link" href="https://eparon.me/2016/09/09/rpi3-enterprise-wifi.html" target="_blank" rel="noopener"
>这篇文章&lt;/a>的帮助下，通过修改 &lt;code>wpa_supplicant.conf&lt;/code> 连接成功，但是 WiFi 列表中对应 SSID 选项为灰色不可点击，迷。&lt;/p>
&lt;blockquote>
&lt;p>修改 /etc/wpa_supplicant/wpa_supplicant.conf&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-textile" data-lang="textile">ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1
country=CN
network={
ssid=&amp;#34;OfficeWiFiSSID&amp;#34;
key_mgmt=WPA-EAP
identity=&amp;#34;username&amp;#34;
password=&amp;#34;password&amp;#34;
}
&lt;/code>&lt;/pre>&lt;h2 id="软件更新">软件更新&lt;/h2>
&lt;p>更换软件源，这个不多说了，国内推荐 &lt;a class="link" href="https://mirrors.ustc.edu.cn/" target="_blank" rel="noopener"
>中国科学技术大学&lt;/a> 、 &lt;a class="link" href="https://mirrors.tuna.tsinghua.edu.cn/" target="_blank" rel="noopener"
>清华大学&lt;/a> 、 &lt;a class="link" href="https://mirrors.163.com/" target="_blank" rel="noopener"
>网易开源镜像站&lt;/a> 的镜像。&lt;/p>
&lt;p>在 apt 更新前，先删除不必要的软件。反正周也是要卸载的，没必要让他们下载安装更新。&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>sudo apt-get remove --purge python3-pygame python-pygame scratch nuscratch claws-mail smartsim sonic-pi minecraft-pi python-minecraftpi wolfram-engine scratch bluej nodered greenfoot scratch2 libreoffice libreoffice-base libreoffice-core chromium-browser thonny python3-thonny python-sense-emu python3-sense-emu python-sense-emu-doc sense-emu-tools
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get autoremove
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get update
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>sudo apt-get dist-upgrade
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>卸载这部分应用后，会有一部分文件残留，通过 VNC 远程进去打开以下三个目录&lt;/p>
&lt;blockquote>
&lt;p>/usr/share/applications/&lt;/p>
&lt;p>/usr/share/raspi-ui-overrides/applications/&lt;/p>
&lt;p>/usr/share/mimelnk/application/&lt;/p>
&lt;/blockquote>
&lt;p>在三个目录里找到图标显示失效的文件，删掉它们。&lt;/p>
&lt;h2 id="自动获取树莓派-ip">自动获取树莓派 IP&lt;/h2>
&lt;p>接下来遇到的问题是，可能是因为联网设备实在太多，公司 WiFi 分配的 IP 地址短期内是和 MAC 绑定不变的，但假如长时间未连接，重新连接就会发分配不同的 IP。&lt;/p>
&lt;p>导致 SSH 记住 IP 并没有什么用，只能每次用 VNC 或者 nmap 扫描网段找到树莓派的地址。&lt;/p>
&lt;p>Google 后参考 &lt;a class="link" href="https://ariandy1.wordpress.com/2014/04/08/linux-send-email-when-ip-address-changes/" target="_blank" rel="noopener"
>这篇文章&lt;/a> 设置，让树莓派每次 IP 变动后自动发送邮件通知自己。&lt;/p>
&lt;blockquote>
&lt;p>sudo apt-get install ssmtp mailutils&lt;/p>
&lt;p>nano /etc/ssmtp/ssmtp.conf&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-textile" data-lang="textile">root=youremail@gmail.com
mailhub=smtp.gmail.com:587
AuthUser=youremail@gmail.com
AuthPass=yourpassword
UseTLS=YES
UseSTARTTLS=YES
AuthMethod=LOGIN
&lt;/code>&lt;/pre>&lt;blockquote>
&lt;p>nano /home/pi/checkip.sh&lt;/p>
&lt;/blockquote>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-shell" data-lang="shell">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#!/bin/bash
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>MYIP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>ifconfig | grep -Eo &lt;span style="color:#e6db74">&amp;#39;inet (addr:)?([0-9]*\.){3}[0-9]*&amp;#39;&lt;/span> | grep -Eo &lt;span style="color:#e6db74">&amp;#39;([0-9]*\.){3}[0-9]*&amp;#39;&lt;/span> | grep -v &lt;span style="color:#e6db74">&amp;#39;127.0.0.1&amp;#39;&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TIME&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>date&lt;span style="color:#e6db74">`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LASTIPFILE&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#39;/home/pi/.last_ip_addr&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LASTIP&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>cat &lt;span style="color:#e6db74">${&lt;/span>LASTIPFILE&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">`&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>MYIP&lt;span style="color:#e6db74">}&lt;/span> !&lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>LASTIP&lt;span style="color:#e6db74">}&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> &lt;span style="color:#f92672">[[&lt;/span> &lt;span style="color:#e6db74">${&lt;/span>MYIP&lt;span style="color:#e6db74">}&lt;/span> &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;#39;&lt;/span> &lt;span style="color:#f92672">]]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">then&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;LOST&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;Sending E-mail..&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo -e &lt;span style="color:#e6db74">&amp;#34;Hello\n\nTimestamp = &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>TIME&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\nIP = &lt;/span>&lt;span style="color:#e6db74">${&lt;/span>MYIP&lt;span style="color:#e6db74">}&lt;/span>&lt;span style="color:#e6db74">\n\nBye&amp;#34;&lt;/span> | &lt;span style="color:#ae81ff">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ae81ff">&lt;/span> /usr/bin/mail -s &lt;span style="color:#e6db74">&amp;#34;[INFO] Raspberrypi New IP&amp;#34;&lt;/span> youremail@gmail.com;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">${&lt;/span>MYIP&lt;span style="color:#e6db74">}&lt;/span> &amp;gt; &lt;span style="color:#e6db74">${&lt;/span>LASTIPFILE&lt;span style="color:#e6db74">}&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> echo &lt;span style="color:#e6db74">&amp;#34;No IP change!&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">fi&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>chmod +x checkip.sh&lt;/p>
&lt;p>crontab -e&lt;/p>
&lt;/blockquote>
&lt;pre tabindex="0">&lt;code class="language-textile" data-lang="textile">*/30 * * * * /home/pi/checkip.sh
&lt;/code>&lt;/pre>&lt;h2 id="eof">EOF&lt;/h2>
&lt;p>于是树莓派终于可以一边通电一边吃灰了。&lt;/p></description></item></channel></rss>