分类目录归档:技术

写了个简单的接力工具

两会期间,各种工具的效果都会大幅度下降。在这种情况下,作为翻译组,想要好好干活也是挺麻烦的,于是,便写了一些简单的工具,来解决这种特殊情况下的尴尬问题。

场景描述

在翻译组,我们有专门的机器来负责内容抓取 & 转化成为 markdown,但同时,文章中会有一些图片,种种原因,会导致这些图片无法访问。在翻译的时候,我们需要对这些图片进行下载,在这种情况下,就会很麻烦。我们需要一个更加简单的方式,来完成文件的下载。

解决方案 – Simple Proxy

Simple Proxy 就是来解决这个问题的,他可以让你很方便的完成外网的文件下载(当然,前提是你的 Proxy Server 可以访问到这个文件。

使用方法:

  1. 找到你要下载的文件,比如 https://img.linux.net.cn/data/attachment/album/202005/23/201448jcxlcci1f1z4c2l2.jpg
  2. 在你的域名前加入测试域名作为前缀,比如 https://simpleproxydemo.herokuapp.com/img.linux.net.cn/data/attachment/album/202005/23/201448jcxlcci1f1z4c2l2.jpg
  3. 访问新的地址,就会自动提醒你下载文件了。

使用建议

在实际的使用过程中,我给你的建议是,

  1. 使用 Heroku 部署 + 使用 Cloudflare 加速 + 一个短域名

这样可以让你的使用体验达到最佳。

如何自己部署

参考:https://github.com/bestony/simple-proxy

原理

Simple Proxy 的代码十分简单,加入了大量的注释,也不过 51 行。

最为核心的代码源自于其中的

res.set({
  'Content-Disposition': `attachment; filename=${filename}`
})
Code language: JavaScript (javascript)

这段代码的用户是为返回值设定 Header ,其中用到的 Header Content-Disposition 是 HTTP 协议早期定义的 Header 规范。

如果你将其值设置为 inline,其内容将会直接展示在界面中;

其值设置为 attachment 则可以启动系统浏览器自带的下载功能。

其值中加入 filename 则可以更进一步,在下载时,指定下载的名称,在本次的项目中,就借助了这样的功能,让下载的文件名不发生改变。

Reference

关于 Podcast Feed 的一些 SPEC

Podcast 依赖于 RSS Feed ,因此,可以参考一些官方的页面了解 Podcast Feed 的格式。

一行代码把 [[xxx]] 替换为 [xxx](xxx)格式

[[xxx]] 是目前比较常见的 backlink ,特别是各种笔记应用。

在转化格式的时候,如果你需要将 [[xxx]] 替换为传统的 markdown 格式,这个时候你可以借助一个简单的 sed 命令来完成这些工作

替换代码

使用时把 test.md 替换为源文件,output.md 替换为导出的文件

sed -E "s/\[{2}(.*)\]{2}/[\\1](\\1)/g" test.md >> output.md
Code language: JavaScript (javascript)

这段代码使用了 sed 来完成修改,其中使用正则表达式替换 backlink 。

说起正则表达式,在我高中的时候,我误以为正则表达式是一种数学表达式,还跑去问了问我的高中数学老师,老师理所当然的不知道。

一些好用的工具

我自己在测试正则表达式的时候,会用到的工具主要是 RegExr ,不过最近发现有人部署了一个速度更快的 RegExr-CN,有需要的同学可以试试看

为什么没有用 -i Flag?

因为 -i 的话,怕把文件搞坏而没有备份,用管道虽然麻烦了点,但至少不修改源文件,安全一些。


change log

2020.05.24 fix some layout error

Rust + MicroBit 按钮调试代码

#![no_main]
#![no_std]
use panic_halt;
use microbit::hal::nrf51::{interrupt, GPIOTE, UART0};
use microbit::hal::prelude::*;
use microbit::hal::serial;
use microbit::hal::serial::BAUD115200;
use cortex_m::interrupt::Mutex;
use cortex_m::peripheral::Peripherals;
use cortex_m_rt::entry;
use core::cell::RefCell;
use core::fmt::Write;
use core::ops::DerefMut;
static GPIO: Mutex<RefCell<Option<GPIOTE>>> = Mutex::new(RefCell::new(None));
static TX: Mutex<RefCell<Option<serial::Tx<UART0>>>> = Mutex::new(RefCell::new(None));
#[entry]
fn main() -> ! {
    if let (Some(p), Some(mut cp)) = (microbit::Peripherals::take(), Peripherals::take()) {
        // 引入两个版本的外设
        cortex_m::interrupt::free(move |cs| {
            /* 开启外部设备的 GPIO 中断 */
            cp.NVIC.enable(microbit::Interrupt::GPIOTE);
            microbit::NVIC::unpend(microbit::Interrupt::GPIOTE);
            /* 切分 GPIO 口,方便使用 */
            let gpio = p.GPIO.split();
            /* 将 Button 的 IO 口作为输入 IO 口 */
            let _ = gpio.pin26.into_floating_input();
            let _ = gpio.pin17.into_floating_input();
            /* 当 GPIO 17 ( A 键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[0]
                .write(|w| unsafe { w.mode().event().psel().bits(17).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in0().set_bit());
            p.GPIOTE.events_in[0].write(|w| unsafe { w.bits(0) });
            /* 当 GPIO 26 (B键)出现了下降沿的时候,触发中断 */
            p.GPIOTE.config[1]
                .write(|w| unsafe { w.mode().event().psel().bits(26).polarity().hi_to_lo() });
            p.GPIOTE.intenset.write(|w| w.in1().set_bit());
            p.GPIOTE.events_in[1].write(|w| unsafe { w.bits(0) });
            *GPIO.borrow(cs).borrow_mut() = Some(p.GPIOTE);
            /* 根据需要,设置 GPIO 口作为输入输出口 */
            let tx = gpio.pin24.into_push_pull_output().downgrade();
            let rx = gpio.pin25.into_floating_input().downgrade();
            /* 将准备的 GPIO 口作为串口来使用 */
            let (mut tx, _) = serial::Serial::uart0(p.UART0, tx, rx, BAUD115200).split();
            let _ = write!(
                tx,
                "\n\rWelcome to the buttons demo. Press buttons A and/or B for some action.\n\r",
            );
            *TX.borrow(cs).borrow_mut() = Some(tx);
        });
    }
    loop {
        continue;
    }
}
// 定义一个中断(如果函数出现了错误,就会触发中断),当我从按钮按下接收到了一个中断,这个函数就会被调用。
#[interrupt]
fn GPIOTE() {
    /* 进入中断内部的内容 */
    cortex_m::interrupt::free(|cs| {
        if let (Some(gpiote), &mut Some(ref mut tx)) = (
            GPIO.borrow(cs).borrow().as_ref(),
            TX.borrow(cs).borrow_mut().deref_mut(),
        ) {
            let buttonapressed = gpiote.events_in[0].read().bits() != 0; // 识别出 A 键按下
            let buttonbpressed = gpiote.events_in[1].read().bits() != 0; // 识别出 B 键按下
            /* 将按钮打印到串口中 */
            let _ = write!(
                tx,
                "Button pressed {}\n\r",
                match (buttonapressed, buttonbpressed) {
                    (false, false) => "",
                    (true, false) => "A",
                    (false, true) => "B",
                    (true, true) => "A + B",
                }
            );
            /* 清空事件 */
            gpiote.events_in[0].write(|w| unsafe { w.bits(0) });
            gpiote.events_in[1].write(|w| unsafe { w.bits(0) });
        }
    });
}

Code language: PHP (php)

微信开放社区抓取工具 CHANGELOG

image

描述

这个连接是我自己用的工具的更新 CHANGELOG。如果想要试用,可以私聊我获取压缩包。

需求

  • 定时刷新
  • 气泡提醒
  • macOS 版本

CHANGELOG

0.0.0.4

  • U 抽象出新的函数,优化程序结构
  • U 更新了数据更新的机制,应用程序回归前台以后会自动刷新应用程序

0.0.0.3

  • 完成数据抓取的能力

如何 Docker 化一个 Cli 工具

需求

我在看 Hexo 的 issue 时,看到了一个需求

Docker image to avoid the environment setting issue.

刚好,我自己有 Docker 的基础,就决定提交一个 PR ,解决这个问题。

核心实现

在开发这一部分的时候,一个最核心的问题是,你需要准备 2 个文件,一个是 DockerFile ,另一个是对应的 Bash Script。

原因在于

  • Docker File 用于打包基础环境,比如全局安装 Hexo
  • Bash Script 则是为了方便挂载本地的文件系统,开辟端口等(端口可以放在 Docker file 中,文件系统必须要现场挂载,因为你的目的是使用 Cli 管理本地文件,就一定要把文件挂载过去)

具体实现的思路是,Docker 镜像本身提供的是基础环境,将 CMD 设置为 Bash ,方便执行具体的命令。

而 Bash Script 则将需要执行的命令整体传递过去。

代码

Docker File

FROM node:10
RUN npm install -g hexo-cli
CMD ["/bin/bash"]
Code language: CSS (css)

Bash Script

#!/bin/sh
docker run \
  --interactive --tty --rm \
  --volume "$PWD":/hexosite \
  --workdir /hexosite \
  -p 4000:4000 \
  bestony/hexojs:latest "$@"

Code language: JavaScript (javascript)

总结

Docker 化 Cli 命令其实并不复杂,核心在于 CMD 与你的 Bash Script 的配合。

其他

你可以查看 https://github.com/hexojs/hexo/pull/3891 来学习到更多的内容。

Windows Terminal 的 WSL 配置

我在考虑将自己的笔记本从 macOS 转为 Windows,所以就开始研究 Windows 下我常用的软件的替代品,于是,便试了试 Windows Subsystem For Linux ,这一试,发现 WSL 的确不错,很好用,便下载了 Windows Terminal ,作为自己的新的开发环境。

在使用 Windows Terminal 的时候,我遇见了一个问题,我希望在 Windows Terminal 中添加一个新的配置文件,从而让我的 Windows Terminal 在启动的时候就自动进入 Linux 子系统,因此,在互联网上搜索了一些教程后,我将我自己的配置改成了这个样子的。

{
    "globals" :
    {
        "alwaysShowTabs" : true,
        "copyOnSelect" : false,
        "defaultProfile" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
        "initialCols" : 120,
        "initialRows" : 30,
        "keybindings" :
        [
            {
                "command" : "closePane",
                "keys" :
                [
                    "ctrl+shift+w"
                ]
            }
        ],
        "requestedTheme" : "system",
        "showTabsInTitlebar" : true,
        "showTerminalTitleInTitlebar" : true,
        "wordDelimiters" : " ./\\()\"'-:,.;<>~!@#$%^&*|+=[]{}~?\u2502"
    },
    "profiles" :
    [
        {
            "acrylicOpacity" : 0.5,
            "background" : "#012456",
            "closeOnExit" : true,
            "colorScheme" : "Campbell",
            "commandline" : "powershell.exe",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 12,
            "guid" : "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{61c54bbd-c2c6-5271-96e7-009a87ff44bf}.png",
            "name" : "Windows PowerShell",
            "padding" : "0, 0, 0, 0",
            "snapOnInput" : true,
            "startingDirectory" : "%USERPROFILE%",
            "useAcrylic" : false
        },
        {
            "acrylicOpacity" : 0.75,
            "closeOnExit" : true,
            "colorScheme" : "One Half Dark",
            "commandline" : "debian",
            "cursorColor" : "#FFFFFF",
            "cursorShape" : "bar",
            "fontFace" : "Consolas",
            "fontSize" : 11,
            "guid" : "{58ad8b0c-3ef8-5f4d-bc6f-13e4c00f2530}",
            "historySize" : 9001,
            "icon" : "ms-appx:///ProfileIcons/{9acb9455-ca41-5af7-950f-6bca1bc9722f}.png",
            "name" : "Debian Path",
            "padding" : "0, 0, 0, 0",
            "startingDirectory" : "/home/bestony",
            "snapOnInput" : true,
            "useAcrylic" : true
        }
    ],
    "schemes" :
    [
        {
            "background" : "#282C34",
            "black" : "#282C34",
            "blue" : "#61AFEF",
            "brightBlack" : "#5A6374",
            "brightBlue" : "#61AFEF",
            "brightCyan" : "#56B6C2",
            "brightGreen" : "#98C379",
            "brightPurple" : "#C678DD",
            "brightRed" : "#E06C75",
            "brightWhite" : "#DCDFE4",
            "brightYellow" : "#E5C07B",
            "cyan" : "#56B6C2",
            "foreground" : "#DCDFE4",
            "green" : "#98C379",
            "name" : "One Half Dark",
            "purple" : "#C678DD",
            "red" : "#E06C75",
            "white" : "#DCDFE4",
            "yellow" : "#E5C07B"
        }
    ]
}
Code language: JSON / JSON with Comments (json)

这个配置项目中,需要注意的是 profiles.[1].commandline 你会发现,我设置的是 debian 这个和很多教程是不一样的,不少教程使用的是 wsl -d debian 这样的,但是实际上,如果你使用的是 wls -d debian ,你会发现,你配置的 startingDirectory 就会失效,但是如果你的启动命令是 debian, 不会影响你的 startingDirectory 的配置,非常舒服。

Reference

  • https://lwz322.github.io/2019/06/01/Terminal.html
  • https://github.com/microsoft/terminal/issues/1183

WebSocket 如何实现服务端向客户端发送消息?

我们都知道, Websocket 是一个双向的通讯方式,一般情况下,我们都是根据 Client 的情况返回信息,但是在一个更加健壮的系统,我们可能需要主动的向客户端发送消息。我试图在中文网络去搜索,查找相关信息,无果。因此便开始搜索英文世界中的内容。如今已经实现我想要的需求,便写一篇文章分享一下。

需求

aurqs

需求是 Websocket 服务端作为中心控制服务器,会对外提供一个 RESTFul API,其他部件通过 RESTFul API 来链接 WebSocket 服务端发起请求,由 WebSocket 服务端进行设备相关的控制。

实现

首先,前提是我们的服务端和设备端是保持着 websocket 的长链接操作的,那么,我们在 RESTFul 中只要获取到这个链接,就可以发送消息了。

而 websocket 中,我们如果想要获取到一个特定的链接,就需要使用 websocket 的 socket.id 来完成我们的需求。这就要求我们提前将 socket.id 存储起来,这样当我们需要的时候,我们就可以直接拿 socket.id 来发送消息。

3oyzs

在 Socket.io 中,你需要使用 io.sockets.connected[socketID].emit(“greeting”, “Howdy, User 1!”); 来发送消息。

在 egg.js 中,则是 app.io.sockets.connected[socketID].emit(‘res’, ‘send From app’);

有了这个代码,你就可以实现在 RESTFul API 中向 Client 中发送消息了。

参考代码

说明

本例中使用的是 Socket.io + Egg.js 实现的

服务端代码

//config/config.default.js
config.io = {
    namespace: {
      '/': {
        connectionMiddleware: [ 'auth' ],
        packetMiddleware: [],
      },
    },
  };
Code language: JavaScript (javascript)
// app/io/middleware/auth.js
'use strict';
module.exports = () => {
  return async (ctx, next) => {
    const { socket, app } = ctx;
    const id = socket.id; // 获取 Socket ID
    app.redis.set('pid', id); // 设置 Socket ID
    ctx.socket.emit('res', `Your id is ${id}`);
    await next();
  };
};
Code language: JavaScript (javascript)
// app/controller/home.js
'use strict';
const Controller = require('egg').Controller;
class HomeController extends Controller {
  async send() {
    const { ctx, app } = this;
    const id = await app.redis.get('pid'); // 获取 ID
    app.io.sockets.connected[id].emit('res', 'send From app');// 发送数据
    ctx.body = 'ok';
  }
}
module.exports = HomeController;

Code language: JavaScript (javascript)

参考文章

https://michaelheap.com/sending-messages-to-certain-clients-with-socket-io/