Download Bilibili 4K Videos. The Hard Way


  • Google Chrome installed
  • Get cookies.txt extension installed
  • youtube-dl installed
  • FFmpeg installed
  • Active bilibili membership (to get 4K streaming perk)
  • Basic JSON and Bash knowledge


  • Open the video page you want to download with Google Chrome
  • Open Web Inspector and refresh the page
  • Change video quality to some lower ones (like 480p) and change back to 4K
  • Search for player/playurl in Inspector
  • You should get a JSON payload returned:

Then select one of the most recent requests with accept_quality that has 120 in array:

Then select down to the[0].baseUrl and[0].baseUrl. Remember these URLs:

Open Get cookies.txt extension and download your current cookies.

Download video and audio with youtube-dl:

youtube-dl --cookies bilibili.com_cookies.txt --referer '' ''

Then you will get two files like the following:

  • 20210510 336676712-1-30280 [336676712-1-30280].m4s
  • 20210510 336676712-1-30120 [336676712-1-30120].m4s

Rename the video extension to mp4 and audio to m4a. Play them to check if everything works.

Combine these two with FFmpeg:

ffmpeg -i 20210510\ 336676712-1-30120\ \[336676712-1-30120\].mp4 -i 20210510\ 336676712-1-30280\ \[336676712-1-30280\].m4a -c copy output-combined.mp4

Fix Invalid Package Name “.DS_Store” for Node.js NPM Global Update on macOS

If you got the following error message when run npm update -g:

$ npm update -g
npm ERR! Invalid package name ".DS_Store": name cannot start with a period

npm ERR! A complete log of this run can be found in:
npm ERR!     /Users/sparanoid/.npm/_logs/2021-04-28T13_59_32_013Z-debug.log

Simply run if you installed your Node.js via Homebrew:

find /usr/local -name '.DS_Store' -type f -print -delete

Using Motion Sensor as Occupancy Sensor for HomeKit with Homebridge


  • A HomeKit enabled motion sensor. I use Aqara Motion Sensor Hue Motion Sensor
    • The update interval of Hue Motion Sensor exposes to HomeKit is 10 seconds. Far better than Aqara Motion Sensor (120 seconds). And seems more stable after testing for months.
  • A HomeKit enabled accessory to be controlled
  • Homebridge with homebridge-delay-switch plugin installed


  • You want to stick with local automation instead of configuring automations in vendor specific apps
  • Trigger an open/turn on/start event when motion detected
  • When no motion for specific minutes. Trigger another event to close/turn off/stop

Why? This method no longer depends on the buggy HomeKit built-in “Turn Off” and Stops Detecting Motion feature. Common HomeKit motion sensors only update the motion state every n seconds. When Stops Detecting Motion triggers. It won’t be canceled or delayed when it detects another motion during the n detection interval. That causes your accessory to be turned off even motion detects during the time you set.

With my method. Your accessory won’t be turned off if your motion sensor detects motion during the specific time you set. Make your motion sensor work more like an occupancy sensor.

First. Add a delay switch in Homebridge:

    "name": "Living Room Delay Switch",
    "delay": 300000,
    "disableSensor": false,
    "startOnReboot": false,
    "accessory": "DelaySwitch"

The delay time is in milliseconds so 300000 equals 6 minutes here.

In your Home app. Create the following automations:

First Automation

  • When: Living Room Motion Sensor – Detects Motion
  • Accessories:
    • Turn on your accessory
    • Turn on your Living Room Delay Switch

Second Automation

  • When: Living Room Delay Switch Trigger – Detects Motion
  • Accessories:
    • Turn off your accessory

Third Automation

This automation is important. It can ensure your accessory can still be turned off at a specific time if the accessory was opened/turned on/started manually.

  • When: Living Room Sensor – Stops Detecting Motion
  • Accessories:
    • Turn on your Living Room Delay Switch

How to Install LineageOS on OnePlus 3T with GApps

This is the minimal guide for installing LineageOS 17.1 on OnePlus 3T abtracted from LineageOS offical guide. It works at the time of writing so no gurantee for further updates.

  1. Enable OEM unlocking and USB debugging on your phone with developer mode.
  2. Download latest Android platform tools for Windows, for Linux, or for macOS from Google. The full guide can be found at Using ADB and fastboot.
  3. Connect your phone into your computer with USB mode.
  4. Install the built-in OnePlus USB Drivers or use UniversalAdbDriver.
  5. cd to the platform-tools directory, run adb devices to see if your phone is connected.
  6. Run adb reboot bootloader to boot your phone into bootloader mode.
  7. Run fastboot devices while in bootloader mode to see if your computer still connects to it.
  8. Run fastboot oem unlock. Your phone should be reset and reboot automatically.
  9. Setup your phone. Then re-enable USB debugging.
  10. Download latest custom recovery from TWRP.
  11. Run adb reboot bootloader to boot your phone into bootloader mode. Also make sure your phone can be find in fastboot mode by re-typing fastboot devices.
  12. Run fastboot flash twrp-3.4.0-0-oneplus3.img to flash custom recovery.
  13. Run fastboot boot twrp-3.4.0-0-oneplus3.img to boot into new recovery mode.
  14. Download the LineageOS installation package.
  15. Download Google Apps using the arm64 architecture. Choose the packages you prefer (I recommend nano package).
  16. In recovery mode, tap Swipe Format Data
  17. Return to the previous menu and tap Advanced Wipe, then select the Cache and System partitions and then Swipe to Wipe.
  18. Sideload the LineageOS and Google Apps packages:
    1. On your phone, tap AdvancedADB Sideload, then swipe to begin sideload
    2. On your computer, sideload the package using adb sideload
    3. Then sideload the Google Apps using adb sideload
  19. Back to recovery main menu. Reboot your device.
  20. Setup your LineageOS.

Apple AirPods Pro vs Sony WH-1000XM4 vs Samsung Galaxy Buds+


Apple AirPods Pro


  • 优质的 ANC(主动降噪)效果,主管感受上基本打平 Sony
  • 无敌的 Transparency Mode,在目前所有的 TWS 中是最强的,穿透进来的环境声音不强不弱,大部分情况下确实可以达到「transparent」的水准
  • 小巧便携,放进裤兜里即可
  • 与 Apple 设备高度集成,体验流畅


  • 续航短,开启 ANC 基本就 3 个小时左右,只适用于国内航班
  • 不支持多设备同时在线(类似蓝牙 Multipoint 的特性)
  • 低频表现一般

Sony WH-1000XM4


  • 手感、做工不错
  • 超长续航,开启 ANC 也可以有 30 小时的续航,轻松应对国际航班/一整天的工作环境
  • 双设备同时在线
  • 可以 3.5 mm 有线连接



  • 虽然支持超长续航,但没有无线充电或 Qi 充电
  • 支持双设备同时在线,但仅支持双设备,不支持更多设备,也不支持多设备快速切换。并且开启双设备同时在线后,无法开启 LDAC,这点很重要,没有 LDAC 的 1000XM4 音质真的只能用渣来形容,然而这一点几乎所有的 KOL、测评都不会提及
  • 用 3.5 mm 有线连接后,如果这时候你拔出接头,耳机会执行一次关机,不知道这样设计的逻辑是什么?导致我每次关机后都需要再手动开机然后蓝牙配对
  • 耳机上执行上划/下划切换音量时并没有绑定系统音量。也就是说这个切换不会同步设备中的系统音量


  • 佩戴不舒适,真不知道是不是索尼在国内的水军太多了,还是我的头部构造离谱。我佩戴超过 2 小时后必头疼,夹耳朵,无论怎样调整都夹耳朵
  • 环境音模式(类似 AirPods Pro 的 Transparency Mode)效果很一般,开启低等级听不清人声,开启高等级后底噪高的离谱。可能全封闭式耳机想要达到入耳式耳塞的「透明模式」的效果确实更难一些

Samsung Galaxy Buds+



  • 价格便宜
  • 续航略优于 AIrPods Pro
  • 但在 Galaxy Wearable 中,可以调出「超高环境音量」模式,可以将环境音以高于正常的音量传入耳朵。很有意思,感觉像带了耳返


  • 没有 LDAC
  • 由于它造型的原因,无法或较难单手打开电池仓
  • 与 1000XM4 一样操作音量时不会绑定系统音量,不过这个默认是关闭的,在「实验室功能」里

The Simplest MediaWiki Update Script for Single-Server MediaWiki Site

System requirements:

  • User uploads $wgUploadDirectory are stored offsite
  • Non-Docker MediaWiki with normal setup
  • Composor installed (Can be installed automatically during updating)


  • Update MediaWiki with nearly zero downtime
  • Download and install latest tagged MediaWiki from tarball package
  • Update extensions and skins from latest git tagged branch
  • Install extension-specific dependencies during updating

First create a script name it and make sure the script exits if anything goes wrong:

# Exit the whole script if anything goes wrong
set -e

Create a config file mw-update.conf with the following content:


Switch back to your script. Add more variables to use later:

MWU_START=`date +%s`

# Set the current working directory to the directory of this script
cd ${0%/*}

# Check if config exists
if [ ! -f ./mw-update.conf ]; then
  echo -e "Config not found, run the following command first:"
  echo -e "\n$ cp mw-update.sample.yml mw-update.yml"
  exit 1

# Parse config
. mw-update.conf

# MediaWiki uses git tag for latest stable version

# Decoration
NC='\033[0m' # No Color

# Production dir

# `MW_VER_MAIN` is for the stupid download URL
MW_VER_MAIN=$(echo $MW_VER | sed -E 's/\.[0-9]+$//g')

# Temp dir prepare for update

# Extensions dir

# Extensions use git branch for latest stable version
EXT_VER=$(echo REL$MW_VER_MAIN | sed -E 's/\./_/g')

# List of custom extensions

Then print the setup info for confirmation:

# Prompt before executing anything
echo -e "${BLUE}MediaWiki Auto Updater${NC}"
echo -e "Tunghsiao Liu ([email protected])\n"
echo -e "     Core version: ${BLUE}$MW_VER${NC}"
echo -e "   Branch version: ${BLUE}$MW_VER_MAIN${NC}"
echo -e "Extension version: ${BLUE}$EXT_VER${NC}"
echo -e "   Temp directory: ${BLUE}$TMP_BASE${NC}"
echo -e " Destination base: ${BLUE}$PROD_BASE${NC}"
echo -e "      Destination: ${BLUE}$PROD_DIR${NC}"
echo -e "      Permissions: ${BLUE}$permissions${NC}"
echo -e "Custom extensions: ${BLUE}$EXTENSIONS${NC}"
echo -e "     Custom skins: ${BLUE}$SKINS${NC}"

Continue executing if user confirms:

read -p "Press enter to continue..."
echo -e "\n"

Check if PHP Composor installed:

function check_composer() {
  # Allow running Composer with root

  if command -v composer >/dev/null 2>&1 ; then
    echo -e "${BLUE}PHP Composer found: $(composer --version  | head -n 1)${NC}"
    echo -e "PHP Composer not found, trying to install it..."
    php -r "copy('', 'composer-setup.php');"
    php composer-setup.php
    php -r "unlink('composer-setup.php');"
    echo -e "Move PHP Composer to /usr/local/bin/composer for global use"
    mv composer.phar /usr/local/bin/composer

Begin to update MediaWiki cores:

# Update core files
echo -e "${BLUE}Creating essential directories${NC}"
mkdir -p $TMP_BASE

echo -e "${BLUE}Updating MediaWiki core files${NC}"
wget -c$MW_VER_MAIN/mediawiki-$MW_VER.tar.gz
tar -zxf mediawiki-$MW_VER.tar.gz

echo -e "${BLUE}Backing up LocalSettings.php${NC}"
cp $PROD_DIR/LocalSettings.php $TMP_DIR/

Update extensions:

# Update extensions
for extension in $EXTENSIONS
  echo -e "${BLUE}Updating extension $extension...${NC}"
  cd $EXT_DIR
  if [ ! -d "$EXT_DIR/$extension" ]; then
    echo -e "${EXT_DIR}/${extension} git repo does not exists!"
    git clone$extension.git
  cd $EXT_DIR/$extension
  git reset --hard
  git clean -f -d
  git pull
  git checkout $EXT_VER
  echo ""

Some extensions need special setup process like update git submodules or install dependencies with Composor:

if [[ ${extensions} =~ "Flow" ]]; then
  echo -e "${BLUE}Updating composer for Flow (StructuredDiscussions)...${NC}"
  cd $EXT_DIR/Flow
  composer update --no-dev
  echo ""

Update skins:

# Update skins
for skin in $SKINS
  echo -e "${BLUE}Updating skin $skin...${NC}"
  cd $SKIN_DIR
  if [ ! -d "$SKIN_DIR/$skin" ]; then
    git clone$skin.git
  cd $SKIN_DIR/$skin
  git reset --hard
  git clean -f -d
  git pull
  git checkout $EXT_VER
  echo ""

Please note that all the above process are done in $TMP_BASE directory. The website is still untouched at the moment. Now lets go into $TMP_BASE to finalize the prepare process:


echo -e "${BLUE}Backing up old files...${NC}"
# tar mediawiki-$MW_VER-update-backup.tar.gz -C $PROD_DIR .
mv $PROD_DIR $PROD_BASE/mediawiki-$MW_VER-backup-$(date +%F-%H:%M)

echo -e "${BLUE}Moving updated files to production...${NC}"

echo -e "${BLUE}Fixing directory permissions...${NC}"
chown -R $permissions $PROD_DIR

echo -e "${BLUE}Running MediaWiki maintenance...${NC}"
php $PROD_DIR/maintenance/update.php --quick

MWU_END=`date +%s`

echo -e "${BLUE}Done! Time took: ${MWU_RUNTIME}s${NC}"