[{"content":"","date":null,"permalink":"/","section":"","summary":"","title":""},{"content":"","date":null,"permalink":"/tags/docker/","section":"标签","summary":"","title":"Docker"},{"content":"","date":null,"permalink":"/categories/server/","section":"分类","summary":"","title":"Server"},{"content":" 你可以通过 分类 快速查找你需要的文章\n","date":null,"permalink":"/categories/","section":"分类","summary":"","title":"分类"},{"content":"Zerotier 回家 #安装 Zerotier #使用 Zerotier 作为虚拟局域网工具，可以让我们方便的连接到 Homelab 服务\n为了方便管理 Docker Compose，我选择使用一个专用文件夹\n新建文件夹 ~/docker-compose/zerotier，将以下内容写入 docker-compose.yml\nversion: \u0026#34;3.8\u0026#34; services: zerotier: image: zerotier/zerotier container_name: zerotier restart: unless-stopped network_mode: host # 必须是 host 模式，才能加入虚拟网络 cap_add: - NET_ADMIN - SYS_ADMIN devices: - /dev/net/tun # 允许使用 TUN 设备 volumes: - ./zerotier_data:/var/lib/zerotier-one volumes: zerotier_data: 执行 docker-compose up -d进行部署\n过后还需要加入网络，网络 ID 从 Zerotier Central 处可获得\n运行命令以加入局域网\ndocker-compose exec zerotier zerotier-cli join \u0026lt;网络 ID\u0026gt; 完成后，在其余设备上安装 Zerotier 即可\n但是，仅限如此，我们只能访问已经加入 Zerotier 网络的设备，不能访问局域网内其他设备，所以还需要配置路由\n配置局域网路由 #进入 Zerotier Central，点选自己创建的网络\n这里需要注意：网络类型需要设置为 Private，这样会分配固定 IP 地址；如果不选择 Private，请对 Homelab 设备编辑 IP Assignments 和 Do Not Auto-Assign IPs 接着划到 Advanced -\u0026gt; Managed Routes -\u0026gt; Add Routes，添加路由\nDestination Via 局域网子网\n（如 192.168.1.0/24） Homelab IP\n（如 172.30.0.1） 添加提交后就可以通过 Zerotier 访问局域网设备\n用家中网络访问外网 #有时候或许需要通过家里的网络访问外网，此时也需配置路由\n添加路由\nDestination Via 0.0.0.0/0 Homelab IP\n（如 172.30.0.1） 当然，此处仅为 Zerotier 云端配置，还需要修改每个设备\nWindows #右键 Zerotier 托盘图标，选择 Allow Default Route Override\nAndroid #进入 Zerotier APP，进入网络详细界面，转到 Configuration，勾选 Route all traffic through ZeroTier\nDNS 配置 #部分 DNS 提供了 DNS 重写功能，允许我们指定域名解析，可以在 Zerotier 中配置，使得每个设备都可以通过自定义域名连接到某个服务\n搭配 Nginx Proxy Manager 食用更佳\n不过，这部分笔者并未跑通，目前笔者选择在 Zerotier 客户端上使用自定义 DNS\n服务导航（列表） #我自己手搓了一个 FloatSheep FNG Gateway | Export Port\nDemo 原理其实就是前端访问 API -\u0026gt; API 利用 Unix Socket 访问 Docker Engine API，获取数据然后返回\n对于访问来源的判断：有一个数组，依次是 本地地址、内网地址、外网地址，请求到哪个就判断是哪个来源\n其他玩法等待挖掘\u0026hellip;\n","date":"2026 年 02 月 21 日","permalink":"/posts/homelab-practice/","section":"所有文章","summary":"记录一次 Homelab 实践","title":"小记 | Homelab 实践"},{"content":" 你可以通过 RSS 订阅所有文章\n「所有文章」加起来约 26,185 字 / 相当于《小王子》法文原版\n","date":null,"permalink":"/posts/","section":"所有文章","summary":"","title":"所有文章"},{"content":" 你可以通过 标签 快速查找你需要的文章\n","date":null,"permalink":"/tags/","section":"标签","summary":"","title":"标签"},{"content":"","date":null,"permalink":"/tags/automatic/","section":"标签","summary":"","title":"Automatic"},{"content":"","date":null,"permalink":"/categories/automatic/","section":"分类","summary":"","title":"Automatic"},{"content":"起因 #其实在中考前我就规划了暑假，但是一放假，我就会因为懒癌晚期而不想起床，这就打乱了我的计划\n采用方法 #由于我的问题是不能够起床，因此我选择了醒来后锁机的方式\n将醒来作为条件，是因为我的穿戴设备（Xiaomi Watch S1）可以提供准确的苏醒时间，而我每次醒来后第一时间就会拿手机 当然，还有一种方法是将手机放在房间外面，但是这对于我来说做不到😭😭\n根据我的需求，很容易选择以下组合\nMacroDroid 实现自动化，清醒状态进行锁机操作 自律锁机 作为锁机工具，就不需要重复造轮子了 考虑到 自律锁机 没有提供外部调用锁机的方法（当然也可能是我没发现），所以我决定用 Shell 脚本实现\n实现 #获取所需的 Activity #因为我这里的 MacroDroid 直接使用内置方法打开应用程序会出问题，所以我选择用 Shell 调用 Activity\nMT 管理器 提供了 Activity 记录 功能\n启动服务，然后进入锁机工具，再返回 MT 管理器，这里就会将我需要的 Activity 显示出来\n获取控件位置 #有了 Activity 还不够，我还需要获取锁机按钮的位置\n因为锁机工具的锁机要分两步（锁机 -\u0026gt; 确定），所以我要获取两个控件位置\n通过 uiautomator 就可以轻松完成\n进入 Termux 并切换到 root 用户，将其挂在小窗\n进入锁机工具后在 Termux 中输入 uiautomator dump /sdcard/ui.xml，将屏幕 UI 结构导出为 xml 文件\n进入文件，搜索关键词 锁机，找到 bounds，这一项记录了控件的对角线坐标，其格式为 [x1,y1] [x2,y2]，使用简单的数学运算就可以得到控件的点击位置\n$$ x = \\frac{(x_1+x_2)}{2} $$ $$ y = \\frac{(y_1+y_2)}{2} $$\n计算后坐标 (\\(x,y\\)) 即为需要的点击坐标\n同理，确定控件的点击位置获取方法如上\n非 root 情况下，可以使用 adb 执行对应命令\n除此之外，getevent -l 也可以用来获取点击坐标\n编辑 MacroDroid（动作） #触发器略\n首先需要启动应用，在 MacroDroid 中添加动作 Shell 脚本，选择 已 Root（非 root 选择 Shizuku），在其中输入 am start -n com.lwtsj.locks/.Mainpoi 并勾选 完成后才能继续动作\n再次，添加动作 等待 1 秒，避免应用未加载完全时进行操作\n然后点击 锁机，添加动作 Shell 脚本，配置相同，输入 input tap x y\n接着点击 确定，添加动作 Shell 脚本，配置相同，输入 input tap x y\n完成后点击右上角三点，选择测试动作，成功锁机即为成功\n","date":"2025 年 06 月 26 日","permalink":"/posts/a-practical-use-of-automation/","section":"所有文章","summary":"为了和不想起床的懒惰抗衡，使用自动化程序成为了我的选择","title":"小记 | 用自动化克服我的懒惰"},{"content":"","date":null,"permalink":"/tags/electron/","section":"标签","summary":"","title":"Electron"},{"content":"","date":null,"permalink":"/tags/hono/","section":"标签","summary":"","title":"Hono"},{"content":"","date":null,"permalink":"/categories/unless/","section":"分类","summary":"","title":"Unless"},{"content":"前情提要 # 阅读本文需要对 Electron、Hono 以及 Service Worker 等技术有一定了解 引入 #开发 Electron 应用的都知道，在其模块 protocol 中提供了一个方法protocol.registerSchemesAsPrivileged(customSchemes) 用于自定义协议\n而后，可以通过 protocol.handle(scheme, handler) 1对请求进行处理\n本文所说的，即发生在 handler 中\n正文 #一般写法如下\napp.whenReady().then(() =\u0026gt; { protocol.handle(\u0026#34;app\u0026#34;, (req) =\u0026gt; { const { host, pathname } = new URL(req.url); if (host === \u0026#34;bundle\u0026#34;) { if (pathname === \u0026#34;/\u0026#34;) { return new Response(\u0026#34;hello, world!\u0026#34;}); } } }); }); 熟悉 Service Worker 的都知道，在 SW 中也有一个 FetchEvent，用于拦截请求并自定义响应，其写法大致（省略部分代码）如下\nself.addEventListener(\u0026#34;fetch\u0026#34;, (event) =\u0026gt; { event.respondWith(new Response(\u0026#34;hello, world!\u0026#34;)); }); 这样可能不能说明什么，但如果我们将响应提到 handler 中呢？\nconst fetchHandler = () =\u0026gt; { return new Response(\u0026#34;hello, world!\u0026#34;); }; self.addEventListener(\u0026#34;fetch\u0026#34;, (event) =\u0026gt; { event.respondWith(fetchHandler()); }); 这时候，我们发现 handler 部分就和 protocol.handle 的 handler 一致了\n既然它们很相像，那么 Service Worker 上的 handler 是不是也能给 Electron 用？\n这时候，我突然想起 Hono 这个框架，它不也是 handler 么？\n而且 Hono 用起来更加简洁，还支持链式路由，比写一大堆 if-else 好多了\n于是，我就尝试用 Hono 来作为 protocol.handle 的 handler\n尝试适配 #理所当然，我们应该查看 Hono 文档中关于 Service Worker 适配器 的部分，文档中 self.addEventListener('fetch', handle(app)) 的 handle(app) 就是 handler 了\n定位到 handle 函数的来源：src/adapter/service-worker/handler.ts\n其实最后就是传入了一个类型为 Hono 的对象 app，调用 app.fetch()\n而 Hono 文档：App - Hono - Hono 的解释：「 app.fetch will be entry point of your application. 」也说明 app.fetch 就是我们需要的东西\n所以，直接传入 Request2就好了\n\u0026hellip;\u0026hellip;吗？\n我们简单变换代码，并把 Hono 的逻辑移到 protocolHandler.js 中\n// main.js import { app, protocol } from \u0026#34;electron\u0026#34;; import { protocolApp } from \u0026#34;./utils/protocolHandler.js\u0026#34;; app.whenReady().then(() =\u0026gt; { protocol.handle(\u0026#34;app\u0026#34;, async (req) =\u0026gt; { await protocolApp.fetch(req); }); }); // protocolHandler.js import { Hono } from \u0026#34;hono\u0026#34;; const app = new Hono(); app.get(\u0026#34;/bundle/\u0026#34;, (c) =\u0026gt; { return c.text(\u0026#34;hello, world!\u0026#34;); }); export const protocolApp = app; 请求 app://bundle/，会发现得到 404，是 Hono 没生效吗？但是我们如果定义 notFound\napp.notFound((c) =\u0026gt; { return c.text(`You\u0026#39;re trying to get response from: ${c.req.path}, But it\u0026#39;s undefined.`, 404); }); 会发现 Hono 已经成功处理了请求，但是为什么还是 404？\n我们看看响应：You're trying to get response from: //bundle/, But it's undefined.\n咦，路由怎么变成这样了，关键点在 //bundle/，我们想要访问 app://bundle/ 能被 Hono 响应，那我们将路由修改为 //bundle/ 呢？\n发现 Hono 能够响应了，但是每次这样写路由也太阴间了，所以我们需要简化一下\n通过输出日志判断，Hono 将 macaron: 删除，把 //bundle/ 当作 path，因此，只需要指定 Hono 的工作路径（basePath）就可以解决\n但 basePath 不能为 /，因为 Hono 默认路径就是 /，不知道为什么需要写阴间路由\n不过我们可以指定 basePath 为 //\nconst app = new Hono().basePath(\u0026#34;//\u0026#34;); 然后修改路由\napp.get(\u0026#39;bundle/\u0026#39;, async (c) =\u0026gt; { return c.text(\u0026#39;hello, world!\u0026#39;); }); 请求，发现完美解决问题\n又是麻烦的一天呢😡\n参考 # protocol | Electron Fetch Handler · Cloudflare Workers docs FetchEvent - Web API | MDN handler: https://developers.cloudflare.com/workers/runtime-apis/handlers/\u0026#160;\u0026#x21a9;\u0026#xfe0e;\nRequest: https://developer.mozilla.org/zh-CN/docs/Web/API/Request\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2025 年 02 月 05 日","permalink":"/posts/electron-protocol-with-hono/","section":"所有文章","summary":"看到 Electron 自定义协议的 handler 也是 Request/Response，便突发奇想🤓👆","title":"小记 | 使用 Hono 简化 Electron 自定义协议处理"},{"content":"","date":null,"permalink":"/circle/","section":"","summary":"","title":"友链朋友圈"},{"content":"","date":null,"permalink":"/categories/ai/","section":"分类","summary":"","title":"Ai"},{"content":"","date":null,"permalink":"/tags/cloudflare/","section":"标签","summary":"","title":"Cloudflare"},{"content":"","date":null,"permalink":"/tags/vercel/","section":"标签","summary":"","title":"Vercel"},{"content":"TianliGPT 已经出来好长一段时间了，无奈囊中羞涩，只好另寻他法\n等孩子有钱了肯定大力支持😭\n正好 Cloudflare Worker AI 已经有了经过量化的通义千问 14B 模型，并且还\n是 Beta 测试，不消耗 Neurons，就折腾了 Qwen-Post-Summary，不过最初的一版有缺陷，浪费资源，这两天缝缝补补，做出来了一个完整版的摘要😁\n因为属于 Beta 测试，不消耗 Neurons，但是会计入 Worker 用量。缓存后也会计入 KV 用量 实现 #用 Vercel 作为中间件，不让访客直接请求 Cloudflare，拉取完成后存入 KV。\n项目：FloatSheep/Qwen-Post-Summary\nCloudflare Worker AI 实现 #复制 worker.js 中的内容，进入 Cloudflare，创建 Worker\n模板分类选择 AI，选择 LLM App 模板进行部署\n创建完成后，点击右上角 编辑代码\n并将复制的 worker.js 内容粘贴进去\n如果有兴趣，可以看看提示词（系统消息），你想要 AI 如何做出摘要，以及风格、长短，都可以通过系统消息进行配置\n完成后，点击右上角蓝色的部署，并复制 Cloudflare 提供的域名（xxx.xxx.workers.dev）\nVercel 中间件实现 #中间件其实就是起到中转和缓存的作用，我是用一个 .ts 文件实现的，具体实现在 api/summary.ts 不过代码有点太乱了，其实应该把可以提出来的函数放一边去\n点击按钮进行部署\n看 Vercel 的提示填写就好了\n关于这个 PROXY_ENABLE，是因为我本地调试的时候连不上 Worker🤣，必须开个代理，但是不知道为什么，条件判断里面的 ProxyAgent 把外面污染了（我部署的时候 PROXY_ENABLE 写的 false），导致 Vercel 请求全走 127.0.0.1（我 debug 了好久，最后还是朋友让输出详细日志才发现的😡），于是我把实现注释掉了。现在 PROXY_ENABLE 完全是废弃掉的状态😅\n部署完成后，进入项目设置，绑定域名\n绑定域名是非常重要的，因为 vercel.app 被 SNI 阻断 和 DNS 污染\n详见 GitHub Discussions 803\n绑定完域名后添加 KV 储存，选择工具栏中的 Storage，创建一个数据库，类型为 KV\n名称随意，创建后进入 KV 设置界面，选择 Projects，并连接到你刚才部署的项目\n下拉框中即可选择\n完成后进入设置，找到 Read Regions 设置，选择为 sin1\n下拉框中即可选择\n前端实现 #我在项目的 client 文件夹中提供了简易的实现方式，我博客的实现方式在它的基础上抄袭借鉴了無名大佬的样式和内容\n如果你有兴趣看我的屎山代码，可以前往 FloatBlog / cfai.html，实现非常简单\n大佬们也可以自己根据 API 写前端实现😀\nAPI 接口 #可以查看 APIFox - Qwen Vercel Middle 了解更多\n","date":"2024 年 08 月 31 日","permalink":"/posts/qwen-summary/","section":"所有文章","summary":"赛博活佛 Cloudflare😁","title":"探索 | 使用 Cloudflare Worker + Vercel 无成本实现 AI 摘要"},{"content":"","date":null,"permalink":"/categories/docker/","section":"分类","summary":"","title":"Docker"},{"content":"","date":null,"permalink":"/tags/router/","section":"标签","summary":"","title":"Router"},{"content":"最近折腾 AX9000 的时候发现了一些奇奇怪怪的玩法，记录一下\n打开 SSH 的新方式 #传统的 SSH 开启方法大致如下：\n利用 xqsystem.lua 进行注入 这种方法本质上是利用了小米路由器的中继功能，将 OpenWrt 作为小米路由器的上级路由，利用 xqsystem.lua 远程执行 SSH 开启命令 利用 telnet 开启 SSH 这种方法需要恢复出厂设置，在控制台插入脚本获取修补后 bdata 分区，刷入分区开启 telnet 然后执行 SSH 开启指令 利用开发版的 Docker 功能开启 SSH 这种方法在 Docker Hub 被墙之前最简单，但被墙之后就无法做到了 最近在恩山看到了一篇文章，是利用 CVE-2023-26319 实现的，通过米家智能场景控制器（xqsmartcontroller）来打开 SSH，在所有支持米家智能场景控制器且未修复漏洞的固件上都可以用这种方式开启 SSH\n不过一个个输命令有点麻烦，我写了一个简单版的脚本\n在路由器后台登录后，按下 F12 打开开发者工具，选择控制台并把代码粘贴进去即可\n原理 #米家智能场景控制器中的 mac 没有进行过滤，并且这个参数是直接由用户控制的，会直接传递给 run_cmd，因此可以在智能场景的 action_list[0].payload.mac 中使用 ;\u0026lt;Command\u0026gt;;# 进行注入，并调用 scene_start_by_crontab 执行智能场景\n打开完 SSH 后，就可以编辑 Docker 守护进程文件来换源和部署容器了\nSSH 密码可以在 https://miwifi.dev/ssh 进行计算\n需要注意的是，在小米路由器中，启动 Docker 服务需要使用 /etc/init.d/mi_docker start 美化路由管理后台 #这里使用 WinSCP 演示\n使用 root 连接路由器后，进入 /www，此处存放着小米路由器后台管理界面的 HTML 文件，我们可以下载下来在本地美化后上传\n","date":"2024 年 08 月 13 日","permalink":"/posts/xiaomi-router-advanced-usage/","section":"所有文章","summary":"发现了一些很新奇的东西（？","title":"探索 | 小米路由器 - 高级玩法"},{"content":"","date":null,"permalink":"/tags/learn/","section":"标签","summary":"","title":"Learn"},{"content":"","date":null,"permalink":"/categories/nat/","section":"分类","summary":"","title":"Nat"},{"content":" 本文仅涉及到基础，且可能有错误 你没有公网 IP，你是怎么上网的？ #目前，运营商在 IPv4 中使用 NAT（网络地址转换） 技术，让你能够正常上网\n简单解释，NAT 就是可以让许多设备共享一个公网 IP\n当然，IP 的使用者不仅仅包括你家的设备，可能小区中的很多设备和你家的设备共享一个 IP\n常见的 NAT 类型 #在 RFC 3489 标准下，NAT 类型包括如下四种\nNAT 类型 映射情况 Full Cone（全锥型） 外部主机向内部主机发送报文，可以实现和内部主机通信 Restricted Cone（受限圆锥型） 内部主机向外部主机发送报文后，外部主机才能发送报文和内部主机通信 Port Restricted Cone（端口受限型） 内部主机向外部主机的特定 IP 和端口发送报文后，外部主机才能发送报文与内部主机通信 Symmetric（对称型） 不同的请求拥有不同的映射（相当于外部主机无法直接与内部主机通信） NAT 类型与运营商 #一般来说，运营商的分配内网 IP 时的 NAT 设备，默认 NAT 类型为 Full Cone。在这种情况下，你只需要稍微修改配置，即可获得 Full Cone 型 NAT\n在公网 IP 需求量大的地方，运营商会优先考虑企业、专线用户。这时候，运营商会为家庭用户使用其它 NAT 类型的 NAT 设备\nNAT 类型与连接 #如果运营商采用 Full Cone 型的 NAT 设备，最多只能有 0xffff（65535） 个连接\n但如果运营商采用 Symmetric 型的 NAT 设备，可以根据 dst ip dst port src port 查找 nat 表，连接数量比 Full Cone 多得多\n因此，越复杂的 NAT 越能用有限的外网地址支持更多的内网设备，这也就解释了为什么许多地方没有 Full Cone\nNAT 类型与用户 #在运营商提供 Full Cone 的情况，用户却无法获取到 Full Cone，一般与配置有关\n光猫桥接，路由器拨号 #这种情况在 2020 年之前比较常见\n在这种情况下，你的环境是这样的\n由于光猫只作为光电转换设备，NAT 就与它无关了\n进入路由器后台 找到 高级设置 - 端口转发（部分路由器叫做 DMZ 主机）\n打开 DMZ 开关，并将地址指定为你的设备 IP 地址\n（针对部分路由器）在路由器后台中找到 Full Cone 选项，将其打开 再次进行 NAT 测试，你的结果理应为 Full Cone\n或者，不配置端口转发，通过 UPnP 进行映射\n找到 高级设置 - 其他，启用 UPnP\n光猫拨号，路由器 DHCP #目前，这种情况比较常见\n光猫型号不同，操作也不同\n此处以 ZXHN F653GV9 举例\n使用超管密码，进入光猫后台 找到 安全 - 防火墙 - 安全级，改为低\n进入应用 - DMZ 主机 ，指定为路由器的 IP 地址（或 MAC 地址）\n接着进入 应用 - UPnP，将其变为使能（启用）\n进入路由器后台 找到 高级设置 - 端口转发（部分路由器叫做 DMZ 主机）\n打开 DMZ 开关，并将地址指定为你的设备 IP 地址\n（针对部分路由器）在路由器后台中找到 Full Cone 选项，将其打开\n或者，不配置端口转发，通过 UPnP 进行映射\n找到 高级设置 - 其他，启用 UPnP\n再次进行 NAT 测试，你的结果理应为 Full Cone\n使用 Natter 进行映射 # 本部分书写于 Natter v2.1.1\n如果你要使用 Natter，请使用 v2 而不是 v1\n由于 Natter 支持 Windows，我们就不需要 Linux 进行操作了\nNatter 需要 Python 环境，请提前安装 下载 Natter 克隆仓库\ngit clone https://github.com/MikeWang000000/Natter.git 打洞 我们可以直接使用 python natter.py -m test 来测试是否打洞成功\nNatter 打洞成功输出 命令行参数 #详细可以查看 Natter - 参数说明，此处介绍一些基本参数\n-p 指定需要映射的端口\n-t 指定需要映射的 IP\n-u 开启 UDP 打洞\n-U 开启 UPnP 映射\n-v 输出详细信息\n-m 配置转发方法\n在 Windows 下一般指定为 socket，gost 和 socat 也可用，但需要使用 pip 安装依赖 -r 不断重试，直到端口开放\n演示：映射本地 5421 端口，并使用 UPnP 映射，socket 转发\npython natter.py -p 5421 -t 127.0.0.1 -U -v -m socket -r UPnP 打洞输出示例 需要注意的是，socket, gost 和 socat 都无法保留源 IP，转发所属的应用程序无法获得访客的真实 IP 和端口 使用 natmap 进行映射 # natmap 仅支持 Linux、BSD 和 MacOS，Windows 上不可用\n本部分书写于 natmap 20240603 版\n首先从 release 上下载\nwget https://github.com/heiher/natmap/releases/download/20240603/natmap-linux-x86_64 -O natmap 然后就可以开始打洞了\nTCP 转发模式 ​\t使用 natmap -s turn.cloudflare.com -h example.com -b 80 -t 10.31.0.49 -p 80\n​\t在这段代码中，我们使用 turn.cloudflare.com 作为 STUN 服务器（-s turn.cloudflare.com），example.com 作为保活服务器（-h example.com）, 80 作为映射端口（-p 80）和 natmap 的绑定端口（-b 80），并指定映射到 10.31.0.49 （-t 10.31.0.49）\nUDP 转发模式 ​\t使用 natmap -s turn.cloudflare.com -h example.com -b 80 -t 10.31.0.49 -p 80 -u\n​\t在这段代码中，我们使用 turn.cloudflare.com 作为 STUN 服务器（-s turn.cloudflare.com），example.com 作为保活服务器（-h example.com）, 80 作为映射端口（-p 80）和 natmap 的绑定端口（-b 80），指定映射到 10.31.0.49 （-t 10.31.0.49），并指定使用 UDP 转发模式（-u）\n命令行参数 #详细可以查看 natmap - Usage，此处介绍一些基本参数\n-u 使用 UDP 转发模式 -s 指定 STUN 服务器 -t 指定需要映射的 IP -p 指定需要映射的端口 使用 Lucky 进行映射 #本质和 Natter 以及 natmap 类似，可以参考 基于stun穿透工具LUCKY，使BT客户端绿灯、开放TCP端口的办法（进化版） 尝试\n使用 pcp 进行映射 # 此方法需要运营商和设备部署 pcp 检查是否处于 CGN NAT（NAT444）类型 查看拨号设备获取的 IP 地址，如果 IP 以 100.64 开头，说明处于 CGN NAT 类型（如下图）\nCGN NAT 拨号获取示例 当然，并不是 CGN NAT 一定是 100.64 开头，可以自己尝试\n克隆 pcp 并编译 git clone https://github.com/libpcp/pcp.git \u0026amp;\u0026amp; ./autogen.sh \u0026amp;\u0026amp; ./configure \u0026amp;\u0026amp; make \u0026amp;\u0026amp; sudo make install 或者，你可以下载编译好的 2012 年版 pcp\nPCP Client download | SourceForge.net\n探测设备是否部署 pcp ./pcp -i \u0026lt;本地 / 局域网 IP\u0026gt;:\u0026lt;端口\u0026gt; -e \u0026lt;远程 IP\u0026gt;:\u0026lt;端口\u0026gt; -t -l \u0026lt;生命周期，如 3600\u0026gt; 无论是否成功，终端都会返回类似 1s 000ms 938us INFO : PCP server \u0026lt;PCP Server IP\u0026gt; terminated. 的输出\n如果终端出现 Flow signaling timed out.，就代表这个设备没有部署 pcp\n尝试将光猫作为 PCP server 时出错 需要注意的是，如果光猫没有部署 pcp，但是路由器（如小米 AX9000）支持，你的端口将会映射到\u0026lt;路由器 Ext. IP\u0026gt;:Port\n具体 IP:Port 可以根据终端输出判断\n成功输出，框内为 \u0026lt;Ext. IP\u0026gt;:Port 现在，你可以访问 IP:Port 查看映射效果\n映射效果示例 题外话：关于光猫桥接与路由（拨号）模式的选择 #已经是 2024 年了，过去的光猫改桥接是因为当时的光猫性能羸弱，PPPoE 协议的开销又很大，不改桥接速度跑不上去，因此当时的网民（甚至是装维）都会推荐改桥接\n但随着光猫性能的提升，PPPoE 的开销对于现代光猫来说已经没有太大的负担。因此让光猫拨号，路由器 DHCP 也许是现代的最佳方法\n以及，现在很多地方开始使用 IPoE 代替 PPPoE，在这种模式下 “DHCP Option 的计算涉及到了动态的 Challenge Secret 和使用 SM3 、SM4 算法来生成，因此一段时间内是没有桥接并通过认证的方案，故暂时放弃。“\n就算你所在的地方没有推行 IPoE，装维也会说现在不需要改桥接，也许你是为了公网 IP，但是公网 IP 与光猫拨号有什么关系呢？或者你是为了提高速度，但是现代光猫改桥接带来的网络提升可以忽略不计。亦或者你是为了实现 Full Cone，但在光猫 DMZ 路由器的情况下，这一层 NAT 可以轻松穿过。就算你改了桥接，运营商定时下发配置会变回路由模式，到时候家里无法上网，还得改一次桥接，属于费力不讨好。也许有人会说，删掉 TR069 就好了，但是运营商有时下发一些更新配置的时候怎么办？只能凉拌了\n温馨提示：以上句段属于个人观点\n在 IPoE 未来普及的时候，“光猫将会内置 PPPoE 服务器，拓扑将会改变为：用户终端 - 用户自己路由器（ PPPoE 拨号）- 光猫（ PPPoE 服务器）- 光猫（ NAT ）- 光猫（ IPoE 的 WAN ）- 运营商出口”\n参考 # 什么是NAT？NAT的类型有哪些？ - 华为 请教运营商分配内网 IP 时采用的 NAT 设备的 nat 类型是 full cone 的么 - V2EX MikeWang000000/Natter heiher/natmap libpcp/pcp 介绍一个可能有助于 CGN NAT 端口映射的工具 - V2EX 分享一个在 NAT1 下将端口打开到公网上的方法 - V2EX Port Control Protocol support - For Developers - OpenWrt Forum 电信又一新动作：上网业务不再使用 PPPoE 新装宽带无法改桥接 - V2EX ","date":"2024 年 07 月 28 日","permalink":"/posts/open-port-in-nat/","section":"所有文章","summary":"NAT 的介绍及 NAT 下端口映射","title":"探索 | NAT 简介 \u0026 端口开放"},{"content":"","date":null,"permalink":"/categories/inverse/","section":"分类","summary":"","title":"Inverse"},{"content":"","date":null,"permalink":"/tags/inverse--analysis/","section":"标签","summary":"","title":"Inverse \u0026 Analysis"},{"content":"在使用 Windhawk1 的时候对它产生了好奇，于是就有了这篇文章\nWindhawk 的源码是完全开源的，可以在 ramensoftware/windhawk 中查看源码\n进入它的目录，我们可以看到结构如下\n很好理解\n{ \u0026#34;meanings\u0026#34;: { \u0026#34;Compiler\u0026#34;: \u0026#34;编译器\u0026#34;, \u0026#34;Engine\u0026#34;: \u0026#34;引擎（Windhawk 功能的核心）\u0026#34;, \u0026#34;UI\u0026#34;: \u0026#34;Windhawk 的 GUI\u0026#34;, \u0026#34;windhawk.exe\u0026#34;: \u0026#34;Windhawk 的主程序\u0026#34; }, \u0026#34;functions\u0026#34;: { \u0026#34;Compiler\u0026#34;: \u0026#34;对编写好的功能模块进行编译\u0026#34;, \u0026#34;Engine\u0026#34;: \u0026#34;用于将编译好的模块注入进目标程序的引擎\u0026#34;, \u0026#34;UI\u0026#34;: \u0026#34;安装、编写模组\u0026#34;, \u0026#34;windhawk.exe\u0026#34;: \u0026#34;驻留后台、拉起 GUI 进程\u0026#34; } } UI 界面的简单逆向 #打开任务管理器（GUI 打开时），会发现在前台进程中出现了 VSCodium\n右键打开文件所在位置，会发现进入了 Windhawk 的 UI 目录\n其中，VSCodium.exe 就是 Windhawk 的 UI 程序\n双击运行，我们会发现直接进入了 VSCodium 的主界面\n同时，右下角出现了提示。提示的大概意思是说，我们用了错误的方式打开了 UI 进程（VSCode 直接启动了）\n于是，我写了个 Rust 来查看它的启动参数，代码如下\nuse std::env; use std::io; use std::io::prelude::*; fn main() { let args: Vec\u0026lt;String\u0026gt; = env::args().collect(); println!(\u0026#34;接收到的命令行参数：{:?}\u0026#34;, args); let command_line = format!(\u0026#34;{}\u0026#34;, args.join(\u0026#34; \u0026#34;)); println!(\u0026#34;命令行调用方式：{}\u0026#34;, command_line); println!(\u0026#34;按任意键退出...\u0026#34;); let _ = io::stdout().flush(); io::stdin().read(\u0026amp;mut [0u8]).unwrap(); } 我们将其放入 UI 目录并将名字改为 VSCodium.exe（记得备份原程序）然后再次唤起 UI 进程，发现命令行调用如下\n也就是说，这个程序是通过 C:\\Program Files\\Windhawk\\UI\\VSCodium.exe C:\\ProgramData\\Windhawk\\EditorWorkspace --locale=en --no-sandbox --disable-gpu-sandbox 启动的\n我们在终端中通过这个命令启动\n？为什么还是报VSCode 直接启动了 :thinking_face_color:\n简单分析一下，这个 UI 进程是通过 windhawk.exe 拉起的，也许需要我们伪装一下？不过在我的测试中要么是无法启动，要么是报错\n前端架构 #通过在 Windhawk 的 UI 进程中使用公开方法打开 Developer Tools，我们发现，UI 使用的是 Ant Design\n顺带提一嘴，第一行的注释内容如下 :rolling_on_the_floor_laughing_color:\n\u0026lt;!-- Copyright (C) Microsoft Corporation. All rights reserved. --\u0026gt; Engine 的简单逆向 #通过对 windhawk.dll 的反编译，我们了解到，这个动态链接库是 Windhawk 需要使用的一些钩子\n当然啦，这个动态链接库是不能通过 rundll32 启动（main entry）的\nWindows 下的自定义功能模组平台\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2024 年 06 月 06 日","permalink":"/posts/simple-inverse-of-windhawk/","section":"所有文章","summary":"记一次对 Windhawk 的简单逆向","title":"探索 | Windhawk 的简单逆向"},{"content":"","date":null,"permalink":"/categories/javascript/","section":"分类","summary":"","title":"Javascript"},{"content":"","date":null,"permalink":"/tags/service-worker/","section":"标签","summary":"","title":"Service Worker"},{"content":"最近在写 BlogOnNpm 自动更新版本号功能的时候在储存数据方面遇到了个问题，就有了这篇文章\n正文 #如题，如何在 Service Worker 中储存数据？\n当我们在写普通的 JavaScript 时，我们肯定会这样储存和读取数据：\nlocalStorage.setItem(\u0026#34;Information\u0026#34;, \u0026#34;Token BBB-BBB-BBB\u0026#34;); localStorage.getItem(\u0026#34;Information\u0026#34;); 这种方法是使用 localStorage 进行的数据储存和读取，这里是 MDN 对于 localStorage 的文档：\nWindow.localStorage - Web API | MDN\n但如果我想要在 Service Worker 中进行数据的储存和读取，使用 localStorage，就会发生这样的事情\n对，你没看错，sw.js 中会报 localStorage is not defined\n根据 StackOverflow 上的帖子，为了安全，浏览器禁止从 Web Worker 进程（也就是 Service Worker 运行的进程）访问 localStorage 和 sessionStorage\n但是我们还需要储存数据，要怎么办呢？\n这就可以使用替代方案了\n使用 Message Channel API \u0026amp; Broadcast Channel API 储存数据 #这种方法本质上也是使用了 localStorage，但是和直接使用 localStorage 不同的是，这种方法是 Service Worker 将需要储存的数据发送到 Window 线程，在 Window 线程中进行数据的存储\n这是 Message Channel API 和 Broadcast Channel API 的伪代码，可供参考（代码来自 StackOverflow）：\n// Message Channel API // include your worker var myWorker = new Worker(\u0026#39;YourWorker.js\u0026#39;), data, changeData = function() { // save data to local storage localStorage.setItem(\u0026#39;data\u0026#39;, (new Date).getTime().toString()); // get data from local storage data = localStorage.getItem(\u0026#39;data\u0026#39;); sendToWorker(); }, sendToWorker = function() { // send data to your worker myWorker.postMessage({ data: data }); }; setInterval(changeData, 1000) // Broadcast Channel API // sw.js const channel4Broadcast = new BroadcastChannel(\u0026#39;channel4\u0026#39;); channel4Broadcast.postMessage({key: value}); // Window channel4Broadcast.onmessage = (event) =\u0026gt; { value = event.data.key; } 使用 IndexedDB 储存数据 #IndexedDB 类似于 RDBMS，是一个基于 JavaScript 的面向对象数据库，用于在客户端存储大量的结构化数据\n但是，直接使用 IndexedDB 会很复杂，所以我推荐使用 localForage 这个库简化操作\nlocalForage 具有两种方法，回调 API 和 Promise，你可以根据需求自行选择\n这里是一个简单的示例：\n// sw.js self.importScripts(\u0026#34;https://registry.npmmirror.com/localforage/1.10.0/files/dist/localforage.js\u0026#34;); self.addEventListener(\u0026#34;fetch\u0026#34;, (event) =\u0026gt; { localforage.setItem(\u0026#34;Information\u0026#34;, \u0026#34;Token BBB-BBB-BBB\u0026#34;).then(() =\u0026gt; { localforage.getItem(\u0026#34;Information\u0026#34;).then((e) =\u0026gt; { console.log(e); }); }); }); 输出结果如下：\n当然，localforage 还提供了无 Promise 版（同步函数），但是由于 Service Worker 基于 Promise 实现，所以 localforage 无法使用 localStorage 这类同步函数，因此，你获得的返回结果仍然为 Promise（因为 Cache API 和 IndexedDB 也是异步执行）\n使用 Cache 储存数据 #Service Worker 中的 Cache API 也可以用来储存数据，常规的 Cache 是用来缓存一些资源（比如 html），因此，如果你要直接使用 Cache API，你需要把网络请求放入 Cache\n这是 MDN 的一段演示代码\nvar cachedResponse = caches .match(event.request) .catch(function () { return fetch(event.request); }) .then(function (response) { caches.open(\u0026#34;v1\u0026#34;).then(function (cache) { cache.put(event.request, response); }); return response.clone(); }) .catch(function () { return caches.match(\u0026#34;/sw-test/gallery/myLittleVader.jpg\u0026#34;); }); 如果你需要用 Cache 储存键值，就需要一些特殊的方法，这里我们使用 ChenYFan 大佬的 Cache-DB 库来实现\n这个库返回的仍然是 Promise，因此用法和 localForage 类似\n这里是一段演示代码：\n// sw.js self.importScripts(\u0026#39;https://registry.npmmirror.com/@chenyfan/cache-db/1.1.3/files\u0026#39;) const mainCache = new CacheDB(\u0026#34;mainCache\u0026#34;, \u0026#34;mainPrefix\u0026#34;, { auto: 1}) self.addEventListener(\u0026#34;fetch\u0026#34;, (event) =\u0026gt; {{ mainCache.write(\u0026#34;information\u0026#34;, \u0026#34;Token BBB-BBB-BBB\u0026#34;).then(() =\u0026gt; { mainCache.read(\u0026#34;information\u0026#34;).then((e) =\u0026gt; { console.log(e); }) }); )} 或者使用 await 替代 then\n// sw.js self.importScripts(\u0026#39;https://registry.npmmirror.com/@chenyfan/cache-db/1.1.3/files\u0026#39;) const mainCache = new CacheDB(\u0026#34;mainCache\u0026#34;, \u0026#34;mainPrefix\u0026#34;, { auto: 1 }) await mainCache.write(\u0026#34;information\u0026#34;, \u0026#34;Token BBB-BBB-BBB\u0026#34;); await mainCache.read(\u0026#34;information\u0026#34;); 输出内容如下：\n在 ChenYFan 的墙裂推荐下BlogOnNpm 采用的是 Cache-DB 进行数据储存\n这些是 Service Worker 进行数据储存的可行方法，可能还有更多\n","date":"2024 年 05 月 03 日","permalink":"/posts/i-just-want-to-save-keys/","section":"所有文章","summary":"在 Service Worker 中储存数据的探索","title":"探索 | 我只是想保存一个 Key！"},{"content":" Powered by TGTalk\n","date":null,"permalink":"/speak/","section":"","summary":"","title":"说说"},{"content":"","date":null,"permalink":"/categories/year/","section":"分类","summary":"","title":"Year"},{"content":"第一次写年度总结，时间已经快逼近农历春节了呢\n与其说是总结？不如说是总结 + 吐槽\n关于博客 #这年倒是没发生什么大事，不过从上学时期的咕咕咕变到放假时期的经常更新，还是挺难见的\n关于\u0026hellip;我？ #2023 年是很神奇的一年呢，上半年的研学活动，作为代表学生发言但是忘词了啊！！！！！！，下半年刚开学就被来了个下马威，开学典礼要上去演讲 我当时都快疯了啊！！！！！谁能懂我（悲） :tired_face_color:，真是祸不单行呢\n除了日常，好像还有学习？\n2023 下半年英、政、史的任课老师都换了（悲）:loudly_crying_face_color: 不仅如此，还多了门物理\n来说说我对这些老师的看法吧顺便吐槽吐槽\n对老师的看法 # 语文老师：我们“亲切”的称呼她为“XX奶奶”，代替的内容就不方便说出来了。同时，语文老师也是班主任u1s1，能当上班主任的老师果然很多招数\n数学老师：他人其实挺好，但是我们班的杂种（杂合体）实在是太智慧了，在他的课上聊天、扔东西、画画······原来这就是别的老师口中的好班级么\n英语老师：两个英语老师都一样，很凶。但是，我们这些好学生群体就不这么认为了，我个人是觉得两个都差不多反正又不是我挨骂（笑）\n地理老师：作为课代表，我就不多做评价了，这是我们班某些人对她的评价：“某些老师啊，当上了班主任就飞了”\n生物老师：温柔（确信）就是对我的要求能不能别这么高（ 46 分都要被说一顿\n其他的就没有评价的意义了，反正都一个死样\n对『Classmates』的看法 # 男生中的一大部分，都不是什么好东西（虽然还没有抽烟、喝酒的程度）\n这个学期举报了一些人，然后被这些人叫做“告人精”，这就是“急了，急了！”的典型表现，纯乐子 女生中的一小部分，喜欢勾心斗角（另一部分，喜欢磕 CP，恰好我也在他们的 CP 里面。对于这种人，我已经没办法评价了，强制把你别人绑在一起（不管你是男或女，甚至磕双男）\n男生中一大部分的一小部分（以XXX为中心），继承了女生的优良传统，顺便融入了男同、小丑行为、孤立等构成了一个杂合圈，在这个杂合圈内生存着许多杂合体\n在这以外的其他人？\n至少从表现上来看，是好种\n希望 2024 年能对『Classmates』有所改观\n对自己的看法 #有点犯贱，比较内向，其余还好\n希望 2024 年能外向起来吧\n对于学习的看法 #怎么说？反正学习很重要，毕竟普高和职高的差距在那儿，就算国家扶持职业教育，目前也改变不了什么:woozy_face_color:\n对于未来的看法 #路就在自己脚下，车马就在自己手中，现在不拼搏，何时才努力？\n希望 2024 年是学业上的进步，是生活上的改变，是精神上的焕发\n希望自己能甩掉拖拉的习惯，23年的 Todo 到现在都还没完成\n希望自己能有宽广的交际圈，毕竟那种遭遇，放谁身上都不好受吧\n","date":"2024 年 02 月 07 日","permalink":"/posts/my-2023/","section":"所有文章","summary":"2023？神奇","title":"年度总结 \u0026 展望未来 | 我的 2023"},{"content":"为什么需要一个家庭服务器 #这是一个很根本的问题，为什么我们需要家庭服务器？\n集中存储：家庭服务器提供一个集中的位置来储存媒体、文件和其他数据，便于管理和访问。 可靠的备份解决方案：家庭服务器可以用来备份重要文档和媒体，防止数据丢失。 随时可用：服务器通常会一直开机运行，这意味着您可以随时访问存储在上面的数据。 家庭自动化和安全平台：家庭服务器可以用来控制智能家居设备，增强家庭安全。 减轻主 PC 负担：将文件存储和管理任务迁移到服务器上，可以减轻您主要使用的电脑的压力。 节约费用：与长期依赖云服务相比，自己搭建和运行家庭服务器可能是一个更经济的选项。 测试、修补和游戏的机会：家庭服务器给予用户自由地测试和学习服务器管理和网络配置的机会，对于有兴趣的人来说，这是一个理想的学习平台 以上由 GPT-4 Turbo 回答\n安装系统 #服务器，首当其冲就要稳，所以我用了 Ubuntu 作为服务器系统，安装过程不在概述，推荐使用 Ventoy 安装，这样不需要额外的操作\n为什么放弃 Windows？ # 资源占用：Windows 本身就很臃肿，一个非精简版 Windows 10 开机内存都有 1G 上 2G 的能力了 Docker 不完整：截至本文书写时（2024/2/7），Docker Desktop（MacOS / Windows）依然不支持 host 网络模式，这就导致了 IPv6 配置麻烦等。当然，你可以在 WSL2 中安装原生 Docker，然后在 WSL2 外部使用 WSLPP 进行映射。或者，你也可以将 WSL2 的网络模式修改为镜像网络 WSL 子系统体验蛋疼：子系统固然方便，但是内存占用不是一般的大，使用时还需要配置分页文件 配置环境 #设置合盖操作（笔记本） #如果你是笔记本(GNOME 桌面)，如下：\nsudo apt install gnome-tweaks 安装完成后打开优化工具，选择 General（通用） -\u0026gt; Suspend when laptop lid is closed（笔记本电脑盖合上时挂起） 右侧按钮设置为关闭\n打开 SSH #sudo apt update sudo apt install openssh-server 设置开机自启动\nsudo systemctl enable ssh 安装 Docker #使用 Docker 容器可以帮助我们更方便的搭建项目\n# Add Docker\u0026#39;s official GPG key: sudo apt-get update sudo apt-get install ca-certificates curl sudo install -m 0755 -d /etc/apt/keyrings sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc sudo chmod a+r /etc/apt/keyrings/docker.asc # Add the repository to Apt sources: echo \\ \u0026#34;deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \\ $(. /etc/os-release \u0026amp;\u0026amp; echo \u0026#34;$VERSION_CODENAME\u0026#34;) stable\u0026#34; | \\ sudo tee /etc/apt/sources.list.d/docker.list \u0026gt; /dev/null sudo apt-get update sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin 如果以上安装后无法使用 Docker Compose，你可以手动安装\nsudo apt-get install docker-compose 添加用户到 Docker 组 #这个过程应该一些文章没有，可以让你执行 Docker 系列命令时无需 root 权限\nsudo usermod -aG docker ${USER} sudo systemctl restart docker sudo newgrp docker sudo chmod o+rw /var/run/docker.sock 重开终端后即可\n安装 Nginx Proxy Manager #安装 Nginx Proxy Manager 后可以帮我们更好的配置项目反向代理\n新建一个文件夹，进入后创建 docker-compose.yml\n使用如下 docker-compose.yml\nversion: \u0026#39;3\u0026#39; services: app: image: \u0026#39;jc21/nginx-proxy-manager:latest\u0026#39; restart: unless-stopped network_mode: \u0026#34;host\u0026#34; volumes: - ./data:/data - ./letsencrypt:/etc/letsencrypt 启动服务\ndocker-compose up -d 对于使用 Cloudflare 的人，你可能遇到 400 错误，你可以参考如下配置\n进入你的 Proxy Hosts 配置界面，选择 Advanced，添加如下内容\nclient_header_buffer_size 32k; large_client_header_buffers 4 32k; 保存即可\n配置映射 #此处有两种方案\nCloudflare Argo Tunnel（无需公网） IPv6 DDNS + Cloudflare CDN（需要 IPv6 公网可用） 本文演示第二种方案\ndocker run -d --name ddns-go --restart=always --net=host -v /opt/ddns-go:/root jeessy/ddns-go 如上，然后进入 IP:9876\n此处可以取消 IPv4 设置，然后我们配置 DNS 服务商\n此处以 Cloudflare 举例，点击 DDNS-GO 提供的地址\n创建令牌 -\u0026gt; 编辑区域 DNS（使用模板）\n更改区域资源为 | 包括 | 账户的所有区域 | 你的区域 |\n点击 “继续以显示摘要”，复制，粘贴到 DDNS-GO 的 Token 框，选择保存就好\n翻到 IPv6 配置，选择通过网卡获取，在 Domains 填入一个子域名（根域名可以注册或者白嫖，此处不详细展开）\n这里我们用 egcn.xxx.xx 举例子，填入进去后保存，转到 DNS 解析\n假如我已经配置了一个项目，在 Nginx Proxy Manager 后台指定的域名为 lp.xx.xx\n我们新建一个 CNAME 解析，像这样\nlp.xx.xx 是 egcn.xxx.xx 的别名并通过 Cloudflare 代理其流量\n保存即可\n这样，一个家庭服务器就搭建且暴露在了公共互联网上\n","date":"2024 年 02 月 07 日","permalink":"/posts/home-server/","section":"所有文章","summary":"落后的电脑 = 家庭服务器","title":"小记 | 家庭服务器指南"},{"content":"","date":null,"permalink":"/tags/hugo/","section":"标签","summary":"","title":"Hugo"},{"content":"","date":null,"permalink":"/categories/hugo/","section":"分类","summary":"","title":"Hugo"},{"content":" 这篇文章适用于所有 Hugo 主题，对于不同的主题，你需要按需更改前端代码\n对于 Hexo，你可以用 Tianli 的文章摘要\n写这篇文章的原因？ #看到木木的博客上更新了篇利用 Gemini Pro 添加摘要的文章，所以就折腾了一下\n准备 #需要这些东西\nAPI 密钥（Gemini） 一个合适的网络，因为会用到 GitHub、Google、Vercel、Cloudflare 脑子 获取 Gemini Pro 的 API 密钥 #Google AI Studio\n访问之后选中左边的 Get API Key 选项卡，选择 Create API Key in new project\n复制下来即可\n部署 API Proxy #这里有两个选择，Netlify 和 Vercel\n目前 Netlify 注册（似乎只针对 +86 区号）需要拍身份证上传，毕竟是个境外平台\n本文以 Vercel 演示\nantergone/palm-proxy\n访问，复制仓库，Import 仓库为自己的，改个名字，完美\nhttps://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fantergone%2Fpalm-proxy\n把这串链接的 antergone 和 palm-proxy 改为你自己的用户名和仓库名，然后访问\n部署 API 转换 #我们需要把 Google 的 API 转换成 OpenAI 的 API 格式\nzuisong/gemini-openai-proxy\n进入 dist 目录，复制 main_cloudflare-workers.mjs 的内容，粘贴到新创建的 Cloudflare Worker 里面就行\n然后修改一下内容，全局搜索 BASE_URL 把值改成你部署的 API Proxy\n全局搜索 apiKey 把代码改成字符串，值写为你的 Gemini Pro API Key\n自用设置（防盗链），全局搜索 origin 修改值为你的博客地址（可选）\n前端代码 # 本节仅适用于 Hugo\n进入博客根目录，进入文件夹 layouts/partials\n新建 gemini.html\n内容如下\n\u0026lt;div class=\u0026#34;post-ai\u0026#34; onclick=\u0026#34;geminiAI()\u0026#34;\u0026gt; \u0026lt;img alt=\u0026#34;Static Badge\u0026#34; src=\u0026#34;/gemini.svg\u0026#34; /\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;style\u0026gt; @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } } .post-summary-result svg { animation: spin 1s infinite linear; } .post-summary-result { box-shadow: rgba(0, 0, 0, 0.05) 0 0 0 1px; background-color: var(--light-header); border-radius: 6px; padding: 0.5rem 1rem; min-height: 3rem; max-width: 41rem; } .post-ai { margin-bottom: 1rem; } \u0026lt;/style\u0026gt; \u0026lt;script\u0026gt; let postAI = document.querySelector(\u0026#34;.post-ai\u0026#34;); let postTile = document.getElementById(\u0026#34;title\u0026#34;).innerHTML; // 此处修改为你的博客 Title id let element-posts = `\u0026lt;div class=\u0026#34;post-summary-result\u0026#34;\u0026gt;加载中...\u0026lt;/div\u0026gt;`; async function geminiAI() { // 尝试获取现有的摘要或错误信息元素并移除 const existingResult = document.querySelector(\u0026#34;.post-summary-result\u0026#34;); if (existingResult) { existingResult.remove(); } else { postAI.insertAdjacentHTML( \u0026#34;afterend\u0026#34;, element-ai ); } let GeminiFetch = \u0026#34;你部署的地址\u0026#34;; try { let postAIResult = document.querySelector(\u0026#34;.post-summary-result\u0026#34;); let input = document.getElementById(\u0026#34;posts-content\u0026#34;).innerHTML; // 此处修改为你的博客内容 ID let inputHanzi = input .replace(/\\n/g, \u0026#34;\u0026#34;) .replace(/[ ]+/g, \u0026#34; \u0026#34;) .replace(/\u0026lt;pre\u0026gt;[\\s\\S]*?\u0026lt;\\/pre\u0026gt;/g, \u0026#34;\u0026#34;) .substring(0, 30000); let toAI = `\u0026#34;文章标题：${postTile}，具体内容：${inputHanzi}\u0026#34;`; const res = await fetch(GeminiFetch, { headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34;, }, method: \u0026#34;POST\u0026#34;, body: JSON.stringify({ model: \u0026#34;gemini-pro\u0026#34;, messages: [ { role: \u0026#34;system\u0026#34;, content: `You are a highly skilled AI trained in language comprehension and summarization. I want you to read the text separated by triple quotes and summarize it into a concise abstract paragraph. The aim is to retain the most important points and provide a coherent and readable summary that can help one understand the main points of discussion without having to read the entire text. Avoid unnecessary details or tangent points. Just give me output and nothing else. Do not enclose responses in quotation marks. Answer in Chinese. You must read the whole article and make the most concise summary possible, which summarizes the content well.You should output the content in its entirety, not leave some behind, and when you are complete, you should issue the DONE command. `, }, { role: \u0026#34;user\u0026#34;, content: toAI }, ], temperature: 0.7, stream: true, }), }); const reader = res.body.getReader(); while (true) { const { value, done } = await reader.read(); if (done) { break; } const text = new TextDecoder().decode(value); const match = text.match(/DONE/); if (!match) { const textJson = JSON.parse(text.substring(5)); const resData = textJson.choices[0].delta.content; if (resData.length \u0026gt; 0) { postAIResult.textContent += resData; } } } } catch (error) { // 在 post-summary-result 元素内部显示错误信息 let postAIResult = document.querySelector(\u0026#34;.post-summary-result\u0026#34;); postAIResult.innerHTML = \u0026#39;\u0026lt;div class=\u0026#34;post-ai-error\u0026#34;\u0026gt;生成摘要时出错，请重试。\u0026lt;/div\u0026gt;\u0026#39;; console.log(error); } } \u0026lt;/script\u0026gt; 参照注释修改即可\n对于 Hugo 中文章 id 和标题 id 的获取方法，可以打开 F12，用鼠标指针定位文章内容元素和标题元素，记录下类，进入 layouts/_default/single.html 中修改这些地方，加入 id=\u0026quot;title\u0026quot; 和 id=\u0026quot;posts-content\u0026quot; 就好\n然后添加 svg 徽章，可以去一些网站上生成\n部署完成！\n","date":"2024 年 02 月 03 日","permalink":"/posts/summary-posts/","section":"所有文章","summary":"为 Hugo 添加文章摘要","title":"小记 | 为 Hugo 添加文章摘要"},{"content":"","date":null,"permalink":"/categories/hello/","section":"分类","summary":"","title":"Hello"},{"content":"","date":null,"permalink":"/tags/hello-world/","section":"标签","summary":"","title":"Hello World"},{"content":" 本文代码实现以图片为例\nReferer 限制，老生常谈了，也就是防盗链\n对于如何绕过这个东西，目前最好的方式估计是写一个 API 代替请求，毕竟 Service Worker 不能修改 Referer，浏览器也限制了 JavaScript 对 Referer 进行修改，所以我们可以用一个 API 代替请求\n确定实现目标 # 接受请求参数 提取请求参数中的 url 和所需要的 referer 对 url 进行请求 将请求后的内容返回前端 实现 #我们首先造出一个简单的事件处理程序\nimport https from \u0026#39;https\u0026#39;; export default async function handler(request, response) { const { fetch: fetchUrl } = request.query; response.setHeader(\u0026#39;Content-Type\u0026#39;, \u0026#39;text/plain\u0026#39;); response.send(fetchUrl); response.statusCode = 200; } 我们将 fetchUrl 传入 b，请求查看，已经给我们返回了我们所请求的值（b），那么接下来请求这个值，然后返回请求的结果\n将代码稍作更改\ntry { https.get(fetchUrl); response.setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;); response.json({ status: \u0026#34;ojbk\u0026#34; }); } catch (error) { response.setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;); response.json({ error: error.message }); response.statusCode = 500; } 查看请求，返回了 { status: \u0026quot;ojbk\u0026quot;}\n接着我们让它返回请求的数据\ntry { const responseData = await new Promise((resolve, reject) =\u0026gt; { https .get(fetchUrl, (res) =\u0026gt; { let data = \u0026#34;\u0026#34;; res.on(\u0026#34;data\u0026#34;, (chunk) =\u0026gt; { data += chunk; }); res.on(\u0026#34;end\u0026#34;, () =\u0026gt; { resolve(data); }); }) .on(\u0026#34;error\u0026#34;, (error) =\u0026gt; { reject(error); }); }); response.setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;); response.send(responseData); } catch (error) { response.setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;application/json\u0026#34;); response.json({ error: error.message }); response.statusCode = 500; } 请求一个网页，可以发现已经返回了网页的文本内容，然后我们加上 referer 设定\n把请求改成这样就行\nconst options = { headers: { \u0026#34;Referer\u0026#34;: \u0026#34;https://blog.hesiy.cn/\u0026#34; } } https.get(fetchUrl, options, (res) =\u0026gt; { ... }) 然后我们请求一张图片看看吧\n返回了一堆乱码就算成功了\n接着我们针对图片进行一下优化\nimport https from \u0026#39;https\u0026#39;; export default async function handler(request, response) { const { fetch: imageUrl } = request.query; if (!imageUrl) { return response.status(400).json({ error: \u0026#39;No image URL provided\u0026#39; }); } const options = { headers: { \u0026#39;Referer\u0026#39;: \u0026#39;https://blog.hesiy.cn\u0026#39; } }; try { https.get(imageUrl, options, (res) =\u0026gt; { if (!res.statusCode || res.statusCode \u0026lt; 200 || res.statusCode \u0026gt;= 300) { return response.status(res.statusCode).json({ msg: \u0026#39;你请求个屁\u0026#39; }); } response.setHeader(\u0026#39;Content-Type\u0026#39;, res.headers[\u0026#39;content-type\u0026#39;]); res.pipe(response); }).on(\u0026#39;error\u0026#39;, (error) =\u0026gt; { response.status(500).json({ error: error.message }); }); } catch (error) { response.status(500).json({ error: error.message }); } } 请求一张图片，可以发现图片被渐进式载入了\n好了，收工~ :smiling_face_color:\n","date":"2024 年 01 月 30 日","permalink":"/posts/referer-skip/","section":"所有文章","summary":"用一种很巧妙的方法绕过 Referer 限制","title":"技巧 ++ | 巧妙绕过 Referer 限制"},{"content":"","date":null,"permalink":"/tags/waf/","section":"标签","summary":"","title":"Waf"},{"content":"","date":null,"permalink":"/categories/waf/","section":"分类","summary":"","title":"Waf"},{"content":"2024 年第一篇文章的说，这篇文章写长亭雷池 WAF 的安装和更改 Nginx 配置\n截至 2024/01/27 18:26 雷池 WAF 社区版只支持 Linux，所以本文所写的一切命令适用于 Linux\n安装 #根据 雷池 WAF 社区版文档\n我们使用如下的命令就可以安装：\nbash -c \u0026#34;$(curl -fsSLk https://waf-ce.chaitin.cn/release/latest/setup.sh)\u0026#34; 另外两种命令参见社区版文档 - 上手指南 - 安装雷池\n修改 Nginx 配置 #对于部分应用，可能需要修改 Nginx.conf 配置\n以 Waline 为例，使用 Nginx 反向代理时需要加入配置\nproxy_set_header X-Forwarded-Proto $scheme; proxy_set_header REMOTE-HOST $remote_addr; 我们连接上服务器\n输入命令\ncd /data/\u0026lt;雷池路径\u0026gt;(可以在安装的时候设置)/resources/nginx/custom_params 发现这里有很多 Backend 文件\n这里我们叫做 backend_x\n我们打开 backend_x\n在其中增加你所需要的配置，保存关闭\n然后我们检查一下配置文件\ndocker exec safeline-tengine nginx -t 确保没有任何输出\n最后应用\ndocker exec safeline-tengine nginx -s reload ","date":"2024 年 01 月 27 日","permalink":"/posts/waf-install/","section":"所有文章","summary":"记录长亭雷池 WAF 的安装方法，以及更改 Nginx 配置","title":"探索 | 雷池 WAF 社区版安装 + Nginx 配置修改指南"},{"content":"本文是对旧文的重写\n准备工作 #需要的工具（文件、硬件）如下：\n路由 ROM（开发版 + 稳定版） 路由救砖工具 在 1GB=1024MB 换算下，剩余空间 ≥ 32GB 的 U盘（或移动硬盘）1 以上工具（文件、硬件）除硬盘外均可在小米路由器官方网站下载\n文章使用版本：\nAX9000 开发版固件（Ver. 1.0.140） AX9000 稳定版固件（Ver. 1.0.168） MIWIFIRepairTool（Dat. 19/01/24） 正文 #进入路由器后台，在常用设置 -\u0026gt; 系统状态中进行新建备份\n然后使用手动上传功能更新开发版固件\n等待 5~8 分钟，当路由器 System 指示灯亮为蓝灯闪烁或橙灯常亮时将路由器断电重启（Power 按键即可）\n这时候访问路由器后台，进入高级设置 -\u0026gt; DOCKER，你也许会看见如下页面\n这样即代表你安装成功了\n接下来格式化设备\n使用你所喜爱的磁盘格式化工具将存储设备格式化为 EXT4（Linux）就好2\n接着将储存设备插入至路由器 Mesh 组网面，然后对设备进行重启 重启完成后你可以在路由器管理页面的存储状态中查看到你的设备信息\n我们首先创建虚拟内存3，然后转到高级设置 -\u0026gt; DOCKER 对 Docker 进行安装，等待安装完成后点击安装第三方管理（此处由于天朝网络特性，可能会失败）\n安装完成后点击 管理Docker\nAnd then, enjoy it!\n拓展玩法 #开启路由器 SSH 权限 #此处有两种方法（第一种也许被修复了\n第一种：\n登录路由器后台，记下地址，类似于这样\nhttp://[IP]/cgi-bin/luci/;stok=\u0026lt;STOK\u0026gt;/web/home#router 然后我们获取 STOK 和 IP 对下面的地址进行替换\nhttp://[IP]/cgi-bin/luci/;stok=\u0026lt;STOK\u0026gt;/api/misystem/set_config_iotdev?bssid=Xiaomi\u0026amp;user_id=longdike\u0026amp;ssid=-h%3B%20nvram%20set%20ssh_en%3D1%3B%20nvram%20commit%3B%20sed%20-i%20\u0026#39;s%2Fchannel%3D.*%2Fchannel%3D%5C%22debug%5C%22%2Fg\u0026#39;%20%2Fetc%2Finit.d%2Fdropbear%3B%20%2Fetc%2Finit.d%2Fdropbear%20start%3B 访问后显示 {\u0026quot;code\u0026quot;: 0} 就成功了，然后修改密码\nhttp://[IP]/cgi-bin/luci/;stok=\u0026lt;STOK\u0026gt;/api/misystem/set_config_iotdev?bssid=Xiaomi\u0026amp;user_id=longdike\u0026amp;ssid=-h%3B%20echo%20-e%20\u0026#39;admin%5Cnadmin\u0026#39;%20%7C%20passwd%20root%3B 第二种：\n进入容器管理页面，创建一个容器，在 Image 中填写 busybox\n接着转到高级容器设置，将 Console 更改为 Interactive \u0026amp; TTY (-i -t)\n然后转到 Volumes 部分，新建 mapping 将 container 填写 /mnt 并将模式改为 Bind ，将 host 填写为 / 并将权限改为 Writable\n部署之后在容器列表的快速操作中选择 Attach Console\n然后执行如下命令：\nchroot /mnt vi /etc/init.d/dropbear 根据下面（Diff）进行更改:\n... start_service() { + #flg_ssh=`nvram get ssh_en` + #channel=`/sbin/uci get /usr/share/xiaoqiang/xiaoqiang_version.version.CHANNEL` + #if [ \u0026#34;$flg_ssh\u0026#34; != \u0026#34;1\u0026#34; -o \u0026#34;$channel\u0026#34; = \u0026#34;release\u0026#34; ]; then + # return 0 + #fi ... } 省略号部分代表内容相同，只用更改省略号以上（下）的内容\n然后启动服务\n/etc/init.d/dropbear start 修改 root 密码\npasswd root 固化可参考 《小米路由器 AX9000 开发版固件直接获取 SSH》的固化部分或《[ax9000] 小米ax3600/ax6000/ax9000/ax5/ax6获取root权限》的固化部分\n搭建 Memos #在 Memos 的官方文档获取 compose 文件\n如下：\nversion: \u0026#34;3.0\u0026#34; services: memos: image: neosmemo/memos:latest container_name: memos volumes: - ~/.memos/:/var/opt/memos ports: - 5230:5230 对文件进行修改\nversion: \u0026#34;3.0\u0026#34; services: memos: image: neosmemo/memos:latest container_name: memos volumes: - /mnt/docker_disk/docker_data/memos:/var/opt/memos ports: - 5230:5230 然后将文件内容粘贴到容器管理的 Stacks 部分部署即可\n当然，你也可以手动部署\n创建容器时 Image 改为 neosmemo/memos\n高级设置部分的 Volumes 添加 mapping /mnt/docker_disk/docker_data/memos 模式改为 Bing 然后 host 改为 /var/opt/memos 并将权限改为 Writable\n再转到 Network 新建一个 port mapping 双项都设置为 5230，网络选 host 就行，这样可以通过 路由器后台管理地址:5230 访问 Memos 了\n创建小型服务器 #转到 App Templates, 选择 Ubuntu 并部署\n然后转到容器管理页面，Attach 或 Console 进入容器\n输入如下命令：\napt-get update apt-get install ca-certificates 然后安装一些工具\napt-get install vim apt-get install systemctl 换源（可选）：\nvim /etc/apt/sources.list gg 然后 dG 然后所有内容\n粘贴清华镜像源\ndeb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-updates main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-backports main restricted universe multiverse deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-security main restricted universe multiverse # 预发布软件源，不建议启用 # deb https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-proposed main restricted universe multiverse # deb-src https://mirrors.tuna.tsinghua.edu.cn/ubuntu-ports/ jammy-proposed main restricted universe multiverse 更新并去除最小化：\napt-get update unminimize 配置 SSH:\napt-get update apt-get install openssh-server 设置 root 密码：\npasswd 修改配置文件：\nvim /etc/ssh/sshd_config 在文件中找到 PermitRootLogin prohibit-password 并注释掉\n然后添加 PermitRootLogin yes\n重启一下服务\n/etc/init.d/ssh restart 然后小型服务器就创建好了\n当然，你也可以使用消费级硬盘，但是这样未免有点大材小用了，除非有人想用路由器做简易 NAS\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n如果你的存储设备还需要存储文件，你可以将它分为两个分区，你只需要保证第一个分区为 EXT4（Linux）格式\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n如果你的存储设备容量 ≤ 32GB，创建分页文件后就无法安装 DOCKER\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":"2023 年 09 月 30 日","permalink":"/posts/mi-docker/","section":"所有文章","summary":"折腾路由器的一次经历","title":"探索 | 小米路由器 Docker 折腾指北"},{"content":"","date":null,"permalink":"/categories/blog/","section":"分类","summary":"","title":"Blog"},{"content":"","date":null,"permalink":"/tags/cache/","section":"标签","summary":"","title":"Cache"},{"content":"","date":null,"permalink":"/tags/javascript/","section":"标签","summary":"","title":"JavaScript"},{"content":"看着现在动辄几兆的字体，在网页上使用会严重拖慢加载速度，有没有什么方法能够改善这种情况？\n这时候我们就需要对字体进行优化了\n对字体进行优化的方式有很多，比如：\n分组加载字体 限制字体加载时间 队列加载 自定义字体显示 本文主要介绍如何通过缓存的方式优化字体加载\n注：本文字体应用针对全站生效\n0. 浏览器兼容性 # 1. 使用 Service Worker 进行缓存 #此方法的前提是你已经注册好了 Service Worker\n// Service Worker 文件 // 此处将文件路径修改为实际路径 const fontFiles = [ \u0026#39;/path/to/font.woff2\u0026#39;, \u0026#39;/path/to/font.woff\u0026#39;, \u0026#39;/path/to/font.ttf\u0026#39;, ]; self.addEventListener(\u0026#39;install\u0026#39;, event =\u0026gt; { event.waitUntil( caches.open(\u0026#39;font-cache\u0026#39;) .then(cache =\u0026gt; cache.addAll(fontFiles)) ); }); self.addEventListener(\u0026#39;fetch\u0026#39;, event =\u0026gt; { event.respondWith( caches.match(event.request) .then(response =\u0026gt; { if (response) { return response; } return fetch(event.request) .then(response =\u0026gt; { const clonedResponse = response.clone(); caches.open(\u0026#39;font-cache\u0026#39;) .then(cache =\u0026gt; cache.put(event.request, clonedResponse)); return response; }); }) ); }); // 注册文件 ... // 此处请根据需要添加要在网页中使用的字体名称 document.body.style.fontFamily = \u0026#39;CustomFont\u0026#39;; 2. 使用 Web Storage API 进行字体缓存 #function isFontCached() { return localStorage.getItem(\u0026#39;cachedFont\u0026#39;) === \u0026#39;true\u0026#39;; } function cacheFont() { localStorage.setItem(\u0026#39;cachedFont\u0026#39;, \u0026#39;true\u0026#39;); } function loadFont() { // 此处请根据需要添加要在网页中使用的字体名称 document.body.style.fontFamily = \u0026#39;CustomFont\u0026#39;; } if (isFontCached()) { console.log(\u0026#39;%c字体缓存: Existence\u0026#39;, \u0026#39;color: green; font-weight: bold;\u0026#39;); loadFont(); } else { console.log(\u0026#39;%c字体缓存: Missing\u0026#39;, \u0026#39;color: red; font-weight: bold;\u0026#39;); console.log(\u0026#39;%c字体缓存机制: Caching\u0026#39;, \u0026#39;color: blue; font-weight: bold;\u0026#39;); cacheFont(); loadFont(); console.log(\u0026#39;%c字体缓存机制: Success\u0026#39;, \u0026#39;color: green; font-weight: bold;\u0026#39;); } @font-face { font-family: CustomFont; font-display: optional; /* 此处将文件路径修改为实际路径 */ src: url(\u0026#34;/path/to/font.ttf\u0026#34;); } 注：使用 localStorage 进行字体缓存仅适用于较小的字体文件，因为 localStorage 和 IndexedDB 的存储容量有限\n如果字体文件较大，可能会导致存储空间不足或性能问题。\n这种方法也无法享受到 Service Worker 提供的离线缓存和自动更新的功能，如果你的环境支持 Service Worker，推荐使用 Service Worker 缓存方案\n3. 两种方法的不同 # Service Worker 缓存字体 IndexedDB/LocalStorage 缓存字体 功能和用途 提供高级缓存控制和离线支持 简单的数据存储和访问 存储位置 浏览器的缓存存储 浏览器的客户端存储 控制灵活性 高 低 离线支持 是 否 数据类型 二进制数据 字符串数据 功能复杂性 高 低 适用场景 高级缓存需求、离线访问 简单的数据缓存需求 字体大小 可缓存较大字体 仅缓存较小字体 引用 # Cache - Web API 接口参考 | MDN\n使用 Web Storage API - Web API 接口参考 | MDN\nWeb Storage API - Web API 接口参考 | MDN\nWeb Font Loading Patterns\nmdn/sw-test\nJS 缓存三种方法\nChatGPT\n","date":"2023 年 08 月 29 日","permalink":"/posts/cache-font/","section":"所有文章","summary":"通过 JavaScript 缓存自定义字体","title":"探索 | 将字体进行缓存，优化网站速度"},{"content":" 👋 我是 FloatSheep（巨蟹座，he/him），是一个 Student. Coder(garbage). Dreamer. 浮杨，一个拥有敏感心灵和深刻感性的灵魂，面对学习和生活中的挑战，他有着一份坚韧不拔的决心。在校园这个多彩的小宇宙中，他既是观察者，也是参与者，经历着成长的甘苦与考验。尽管偶尔会在压力的重重包围下流露出脆弱，但他总能从中汲取力量，如同凤凰涅槃，焕发新的生命力。浮杨正逐步踏出内向的阴影，朝向外向的阳光大步前行，带着对未来满满的期待和改变的勇气。他深知，未来的路由自己铺就，每一个努力的今天，都是为了更加灿烂的明天。\n我的性格（探险家 \u0026gt; ISFP-T）\nFloatSheep(浮杨) 是我的网名，它的含义：\n像杨树一样高大、茂盛、坚强，而且能够随风飘逸，不受拘束 不愿被束缚，想要自由地探索世界 有着独特的想法和创造力，像浮雕一样有着立体的艺术感 有着好运气和机遇，像浮萍一样能够顺水推舟 座右铭：『适水而行，不失心航』\n🌏 坐标: Hubei, China\n","date":null,"permalink":"/about/","section":"","summary":"","title":"关于"},{"content":"","date":null,"permalink":"/tags/blog/","section":"标签","summary":"","title":"Blog"},{"content":"","date":null,"permalink":"/categories/shortcodes/","section":"分类","summary":"","title":"Shortcodes"},{"content":"什么是 Shortcodes #Shortcodes（简码或短代码） 是 Hugo 为了避免内容作者因为 Markdown 语法的不足而插入 HTML 到内容中所创建的\n简单地说，Shortcodes 就好比 Hexo 中的 外挂标签（Tag Plugins）\n我们可以随意的编写属于自己的 Shortcodes\n如何使用 Shortcodes #你使用的 Hugo Theme 可能自带了一些 Shortcodes\n通过翻阅主题文档，我们可以通过简短的语法在 Markdown 中使用这些 Shortcodes\nShortcodes 的调用方式 #我们可以通过\n{{\u0026lt; shortcodename parameters \u0026gt;}} 直接调用短代码\nShortcodes 目录 #编写 Shortcodes 就像编写 Tag Plugins 一样简单\n我们首先需要了解 Shortcodes 目录\nShortcodes 目录位于 ~/layouts/ 下,我们可以通过创建 HTML 文件编写自己的 Shortcodes\n了解完目录，我们就可以尝试编写自己的 Shortcodes 了\n编写 Shortcodes #在 ~/layouts/shortcodes 下新建一个 HTML 文件\nHTML 文件的名称就是短代码的名称\n譬如:\nmyShortcodes codeDemo 我们在 Markdown 中写作时就可以通过 {{\u0026lt; myShortcodes parameters \u0026gt;}} 的方式调用\n现在我们将它命名为 colorfont\n我们在里面写一段简单的 HTML\n让我们可以通过 color=colorname 的方式传参给 Shortcodes，所以我们使用 {{ with .Get \u0026quot;color\u0026quot; }}\n接着，我们使用 Shortcodes 让文字变红，这时候，我们就需要使用 {{ .Inner }} 来获取我们传给 Shortcodes 的文字\n这样我们就完成了一个简单的 Shortcodes\n来试试效果吧！\n在 Markdown 中使用 {{\u0026lt; colorfont color=\u0026quot;red\u0026quot; \u0026gt;}} Content {{\u0026lt;/ colorfont \u0026gt;}} 来调用\nContent 看，成功了！\n它还有更多用法，比如插入一个 Emoji\n我们还可以将它们融合在一起\n泰库拉！\n它还有许多的用法，就等各位去一一发掘了\n警告! 如果你想要自定义 Shortcodes 的样式，你应该在另外的 CSS 文件中书写样式，你不应该在 Shortcodes 文件中直接使用 \u0026lt;style /\u0026gt; 语法，也不应该在 Shortcodes 中引入样式，那将会浪费不必要的流量 ","date":"2023 年 08 月 26 日","permalink":"/posts/hugo-shortcodes/","section":"所有文章","summary":"什么是 Shortcodes \u0026amp; 如何编写自己的 Shortcodes","title":"小记 | Hugo Shortcodes"},{"content":"","date":null,"permalink":"/categories/deploy/","section":"分类","summary":"","title":"Deploy"},{"content":"","date":null,"permalink":"/tags/history/","section":"标签","summary":"","title":"History"},{"content":"博客的开始 #2019 年，我跟着互联网上的教程，使用 Hexo 搭建起了我的博客\n当时的域名选用的还是 Freenom 的 slblog.ga\n主题是 hexo-theme-miho\n主题 Demo 你可以从这里观看我的黑历史：open-super.github.io\n当时为了掩盖自己的年龄，我还特别注明了是初三生的博客，其实才小学😅 后来博客文章慢慢变多，需求也随着扩大，基本的博客功能已经满足不了我了,于是开始了我的第一次换主题之旅\n第一次更换主题 #当时为了兼顾好看和功能多样,我在 Hexo Themes 上挑选了很久，最后看上了一款名为 Butterfly 的主题\nOS: 当时 Butterfly 的版本还是 2.3.0，现在 Butterfly 的版本已经迭代到 4.9.0 了，时间过的真快啊\n主题 Demo 使用 Butterfly 的这段时间，我学会了一点 HTML、CSS、JavaScript\n也知道怎么修改主题达到自己满意的效果了\n记得当时看的第一篇修改主题的文章是小康的 《Hexo 魔改日记》\n之后，我也学着修改主题，当时我做的弱智操作包括但不限于:\n给全站的所有卡片添加毛玻璃样式（结果：导致风扇呼呼抱怨） 修改暗黑模式背景（结果：配色十分有一百分的不协调看起来和老年人表情包的配色没什么两样） 第二次更换主题 #这次更换主题主要是别的主题实在是太炫酷了\n更换的主题叫 Aurora\n主题 Demo 不过由于一些不可抗力因素其实就是懒，这次换主题没有成功\n交叉路口 #看到 苏卡卡 和 宝硕 都是 Next.js\n我也不能闲着，于是找了个模板套了进去，顺便给它添加了友链的功能\n主页 友链页面 后来因为维护太麻烦了主要是 React 让我头疼，加上我的技术栈不是 React于是果断抛弃\n安稳之路 #最后我选择了 Hugo 作为 Hexo 的替代品\n为什么选择 Hugo 作为 Hexo 的替代品:\nFast - 快,推送之后秒部署 Diverse - Go 模板语法 Simple - 主题源码可以通过新建 HTML 进行修改 Convenient - Hugo Module 让我们需要修改主题某处只需本地新建对应的文件即可,无需克隆整个仓库,也无需担心无法 Pull 编不下去了 ＞︿＜ 在选择了 Hugo 之后,我用了 Congo 作为博客主题\n主题 Demo 也许之后还会换上其他的构建工具,\n也许会一直保持这样\n","date":"2023 年 08 月 25 日","permalink":"/posts/blogging-memories/","section":"所有文章","summary":"告别以前，迎接未来","title":"追溯 | Blog 回忆录"},{"content":"","date":null,"permalink":"/friends/","section":"","summary":"","title":"友链"},{"content":"前言 #交换友链，只是为了方便互相访问和串门\n一开始的满心期待，到后来的摆烂躺平，中间往往只有一瞬\n要求 # 网站原创内容占多数\n内地以外地区访问良好\n内容排版不杂乱\n网站全站资源均使用 HTTPS 协议\n网站使用非隐私入侵型统计服务1\n当然，如果你是我的朋友 / 我们两个很熟 / 我单方面想要为某个人废除这些要求，那么，以上条件忽略不计~\n个人信息 #- nickname: FloatSheep url: https://blog.hesiy.cn avatar: https://registry.npmmirror.com/@floatsheep/fsl-fim/1.0.23/files/avatar%202024.main.webp description: On the other side of obstacles 对于 avatar，我提供以下版本：\n原版本 白色背景版 如果需要 Dark version，可以通过 CSS 来处理 filter: invert(1) hue-rotate(180deg);\n在评论区用 YAML 格式留下你的信息即可\n- nickname: \u0026#34;\u0026#34; url: \u0026#34;\u0026#34; avatar: \u0026#34;\u0026#34; description: \u0026#34;\u0026#34; 评论加载较慢，请耐心等待~\n即包含用户画像、用户分群、转化归因、一键投放等功能的统计服务\u0026#160;\u0026#x21a9;\u0026#xfe0e;\n","date":null,"permalink":"/container/","section":"","summary":"","title":"友链集装箱"},{"content":"导言 #FloatSheep\u0026rsquo;s Blog 是一款由 FloatSheep （以下简称“我们”）提供的产品。 您在使用我们的服务时，我们可能会收集和使用您的相关信息。我们希望通过本《隐私政策》向您说明，在使用我们的服务时，我们如何收集、使用、储存和分享这些信息，以及我们为您提供的访问、更新、控制和保护这些信息的方式。 本《隐私政策》与您所使用的 FloatSheep\u0026rsquo;s Blog 服务息息相关，希望您仔细阅读，在需要时，按照本《隐私政策》的指引，作出您认为适当的选择。本《隐私政策》中涉及的相关技术词汇，我们尽量以简明扼要的表述，并提供进一步说明的链接，以便您的理解。\n您使用或继续使用我们的服务，即意味着同意我们按照本《隐私政策》收集、使用、储存和分享您的相关信息。\n如对本《隐私政策》或相关事宜有任何问题，请通过 sheep@hesiy.cn 与我们联系。\n1. 我们收集的信息 #我们或我们的第三方合作伙伴提供服务时，可能会收集、储存和使用下列与您有关的信息。如果您不提供相关信息，可能无法注册成为我们的用户或无法享受我们提供的某些服务，或者无法达到相关服务拟达到的效果。\n我们所收集的信息包括但不限于:\n个人信息，您在注册账户或使用我们的服务时，向我们提供的相关个人信息，例如电话号码、电子邮件等。 位置信息，指您开启设备定位功能并使用我们基于位置提供的相关服务时，收集的有关您位置的信息，包括： 您通过具有定位功能的移动设备使用我们的服务时，通过 GPS 或 WiFi 等方式收集的您的地理位置信息； 您可以通过关闭定位功能，停止对您的地理位置信息的收集。 日志信息，指您使用我们的服务时，系统可能通过 Cookies、标识符及相关技术收集的信息，包括您的设备信息、浏览信息、点击信息，并将该等信息储存为日志信息，为您提供个性化的用户体验、保障服务安全。您可以通过浏览器设置拒绝或管理 Cookie、标识符或相关技术的使用。 我们还将收集你的 IP 地址、流量路由数据、系统配置信息，以及其他关于进出客户网站、设备、应用程序和/或网络的流量信息等必须信息用于分析\n我们接入的第三方 SDK、服务、应用程序接口：\nCloudflare Self-Hosting Giscus.app GitHub API Self-Hosting Analytics 服务组成 #Cloudflare #我们的站点由 Cloudflare 提供托管(Hosting)、加速(Speed up) 及 分发(Distribute) 服务，所有的流量均会经过其全球分发网络和节点，Cloudflare 处理终端用户与客户的互联网财产和服务的互动.\n当最终用户访问的域名、网站、API、应用程序、设备、终端和使用我们一项或多项服务的网络时，以及当最终用户访问或使用 Cloudflare Zero Trust 等服务时，这些信息将得到处理。 所处理的信息可能包括但不限于 IP 地址、流量路由数据、系统配置信息，以及其他关于进出客户网站、设备、应用程序和/或网络的流量信息\n有关更多信息，请参阅 Cloudflare 的 隐私条款\nSelf-Hosting Giscus #我们的评论由 Self-Hosting Giscus 提供，所有用户包括但不限于进行登录、评论、回复操作在用户授权后都将由代表 GitHub 应用进行对应操作，用户书写的内容都将经过 Self-Hosting Giscus 服务后发送至 GitHub API\n当用户授权进行 Giscus.App 则意味着用户同意我们收集 GitHub Avatar、GitHub UserName、GitHub UserEmail 等必要信息，这些信息将会发送至 Self-Hosting Giscus 服务.\n必要时，我们可能会对用户的评论进行取证、删除、提交公安部门等，请悉知。\n有关更多信息，请参阅 Giscus.App 的 隐私政策 和 GitHub 的 隐私声明\nSelf-Hosting Analytics #我们的统计由 Self-Hosting Analytics 提供，由 umami-software 开发，当您访问我们的网站时，您的基本信息将会被收集，提供给我们分析\n当用户访问我们的网站，则意味着用户同意我们收集 IP 地址、国家、设备（终端）信息、操作系统信息 等必要信息，同意我们记录用户的行为并对其进行分类\n有关更多信息，请参阅 umami 的 隐私政策\n2. 信息的存储 #2.1 信息存储的方式和期限\n我们会通过安全的方式存储您的信息，包括本地存储（例如利用APP进行数据缓存）、数据库和服务器日志。 一般情况下，我们只会在为实现服务目的所必需的时间内或法律法规规定的条件下存储您的个人信息。 2.2 信息存储的地域\n您的信息会通过 Cloudflare 其遍布全球的节点传输至美国(或其周边地点) 您的信息也会通过 Self-Hosting Analytics 服务传输至美国、新加坡(或其周边地点) 2.3 产品或服务停止运营时的通知\n当我们的产品或服务发生停止运营的情况时，我们将以推送通知、公告等形式通知您，并在合理期限内删除您的个人信息或进行匿名化处理，法律法规另有规定的除外。 3. 信息安全 #我们使用各种安全技术和程序，以防信息的丢失、不当使用、未经授权阅览或披露。例如，在某些服务中，我们将利用加密技术（例如SSL）来保护您提供的个人信息。但请您理解，由于技术的限制以及可能存在的各种恶意手段，在互联网行业，即便竭尽所能加强安全措施，也不可能始终保证信息百分之百的安全。您需要了解，您接入我们的服务所用的系统和通讯网络，有可能因我们可控范围外的因素而出现问题。\n4. 我们如何使用信息 #我们可能将在向您提供服务的过程之中所收集的信息用作下列用途：\n向您提供服务； 在我们提供服务时，用于身份验证、客户服务、安全防范、诈骗监测、存档和备份用途，确保我们向您提供的产品和服务的安全性； 帮助我们设计新服务，改善我们现有服务； 使我们更加了解您如何接入和使用我们的服务，从而针对性地回应您的个性化需求，例如语言设定、位置设定、个性化的帮助服务和指示，或对您和其他用户作出其他方面的回应； 向您提供与您更加相关的广告以替代普遍投放的广告； 评估我们服务中的广告和其他促销及推广活动的效果，并加以改善； 软件认证或管理软件升级； 让您参与有关我们产品和服务的调查。 5. 信息共享 #目前，我们不会主动共享或转让您的个人信息至第三方，如存在其他共享或转让您的个人信息或您需要我们将您的个人信息共享或转让至第三方情形时，我们会直接或确认第三方征得您对上述行为的明示同意。\n为了投放广告，评估、优化广告投放效果等目的，我们需要向广告主及其代理商等第三方合作伙伴共享您的部分数据，要求其严格遵守我们关于数据隐私保护的措施与要求，包括但不限于根据数据保护协议、承诺书及相关数据处理政策进行处理，避免识别出个人身份，保障隐私安全。\n我们不会向合作伙伴分享可用于识别您个人身份的信息（例如您的姓名或电子邮件地址），除非您明确授权。\n我们不会对外公开披露所收集的个人信息，如必须公开披露时，我们会向您告知此次公开披露的目的、披露信息的类型及可能涉及的敏感信息，并征得您的明示同意。\n随着我们业务的持续发展，我们有可能进行合并、收购、资产转让等交易，我们将告知您相关情形，按照法律法规及不低于本《隐私政策》所要求的标准继续保护或要求新的控制者继续保护您的个人信息。\n另外，根据相关法律法规及国家标准，以下情形中，我们可能会共享、转让、公开披露个人信息无需事先征得您的授权同意：\n与国家安全、国防安全直接相关的； 与公共安全、公共卫生、重大公共利益直接相关的； 犯罪侦查、起诉、审判和判决执行等直接相关的； 出于维护个人信息主体或其他个人的生命、财产等重大合法权益但又很难得到本人同意的； 个人信息主体自行向社会公众公开个人信息的； 从合法公开披露的信息中收集个人信息的，如合法的新闻报道、政府信息公开等渠道。 6. 您的权利 #在您使用我们的服务期间，我们可能会视产品具体情况为您提供相应的操作设置，以便您可以查询、删除、更正或撤回您的相关个人信息，您可参考相应的具体指引进行操作。此外，我们还设置了投诉举报渠道，您的意见将会得到及时的处理。如果您无法通过上述途径和方式行使您的个人信息主体权利，您可以通过本《隐私政策》中提供的联系方式提出您的请求，我们会按照法律法规的规定予以反馈。\n当您决定不再使用我们的产品或服务时，可以申请注销账户。注销账户后，除法律法规另有规定外，我们将删除或匿名化处理您的个人信息。\n7. 变更 #我们可能适时修订本《隐私政策》的条款。当变更发生时，我们会在版本更新时向您提示新的《隐私政策》，并向您说明生效日期。请您仔细阅读变更后的《隐私政策》内容，若您继续使用我们的服务，即表示您同意我们按照更新后的《隐私政策》处理您的个人信息。\n8. 未成年人保护 #我们鼓励父母或监护人指导未满十八岁的未成年人使用我们的服务。我们建议未成年人鼓励他们的父母或监护人阅读本《隐私政策》，并建议未成年人在提交的个人信息之前寻求父母或监护人的同意和指导。\n","date":null,"permalink":"/policy/","section":"","summary":"","title":"隐私政策"},{"content":" 如果本网站的任何内容无意中侵犯了您的版权或利益，请及时与我联系，我将采取适当行动。 除非另有特殊说明，否则 本站 所有内容均在 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 许可下授权。\n下面是该 许可证 的人类可读摘要（而不是替代）\n您可以自由地： #共享 — 在任何媒介以任何形式复制、发行本作品\n演绎 — 修改、转换或以本作品为基础进行创作\n只要你遵守许可协议条款，许可人就无法收回你的这些权利。\n惟须遵守下列条件： #署名 — 您必须给出 适当的署名，提供指向本许可协议的链接，同时 标明是否（对原始作品）作了修改。您可以用任何合理的方式来署名，但是不得以任何方式暗示许可人为您或您的使用背书。\n非商业性使用 — 您不得将本作品用于 商业目的。\n相同方式共享 — 如果您再混合、转换或者基于本作品进行创作，您必须基于 与原先许可协议相同的许可协议 分发您贡献的作品。\n没有附加限制 — 您不得适用法律术语或者 技术措施 从而限制其他人做许可协议允许的事情。\n声明： #您不必因为公共领域的作品要素而遵守许可协议，或者您的使用被可适用的 例外或限制 所允许。\n不提供担保。许可协议可能不会给与您意图使用的所必须的所有许可。例如，其他权利比如 形象权、隐私权 或 人格权 可能限制您如何使用作品。\n署名-非商业性使用-相同方式共享 4.0 国际许可证\n","date":null,"permalink":"/copyright/","section":"","summary":"","title":"版权声明"},{"content":"","date":null,"permalink":"/categories/electron/","section":"分类","summary":"","title":"Electron"},{"content":"文章迁移计划，样式可能出错\n开始前注意,这是将 Electron 集成到 Vue 项目中的演示,如果你没有基础,建议使用样板代码\nPart 0.1: 什么是 Electron #**Electron 可以让你使用纯 JavaScript 调用丰富的原生 APIs 来创造桌面应用。**你可以把它看作一个专注于桌面应用的 Node.js 的变体，而不是 Web 服务器。\n这不意味着 Electron 是绑定了 GUI 库的 JavaScript。相反，Electron 使用 web 页面作为它的GUI，所以你能把它看作成一个被 JavaScript 控制的，精简版的 Chromium 浏览器。\nPart 0.5: 先决条件 # 良好的网络 Part 1: 安装 Node.js #Electron 是基于 Node.js 构建的 / 并且使用 Node.js 还能完成平时 Web 程序所做不到的事情: 与系统打交道\nNode.js\n进入上述地址下载并安装 Node.js\nPart 2: 安装 Vue-cli #由于我更喜欢 Vue,所以我选择用 Vue 创建项目\n$ npm install @vue/cli --global 安装 vue-cli\nPart 3: 创建项目 #$ vue create electron-vue-mdui 创建自己的项目\n这里选择最后一项(Manually select features)\n这里依照自己的需要,我选择了 Babel(语法编译器) | Router(Vue 路由) | CSS Pre-processors(CSS预处理器) Linter / Formatter(代码风格、格式校验)\n这一步选择 3.x(或更高版本)\nHistory 模式选择 Y\nCSS 预处理器我更喜欢 Stlyus, 这里依照自己的喜好选择\n这里墙裂建议使用 ESLint\n我选择的是 ESLint + Prettier\n在这一步建议两个都选择\n这一步我们选择单独放置在文件夹下\n然后这里是最后一步,让你选择下一次是否要用同样的配置, 我选择的是 N\n等待安装完成就创建好了一个 Vue 项目\nPart4: 集成 Electron 至 Vue 项目中 #进入项目文件夹,打开终端\n安装 electron 插件\n$ vue add vue-cli-plugin-electron-builder 版本选择 ^13.0.0 就可\n等待它安装完成,在安装的过程中它会重构项目架构\n等到安装完成你就拥有了一个 Electron + Vue 的 App\n运行 Electron 试试看吧!\n$ yarn electron:serve 如果弹出一个程序就成功了\nPart 5: 用 MDUI 写前端 #MDUI - Material Design 样式的前端框架\nMDUI 漂亮、轻量且好用，它能让你更轻松地开发 Material Design 网页应用\n首先安装它\n$ yarn add mdui 然后我们需要引用它\n编辑入口文件\n加入一些代码\nimport mdui from \u0026#39;mdui\u0026#39;; import \u0026#39;../vendor/mdui/css/mdui.css\u0026#39;; // 注意修改 css 文件的路径 在其他需要使用 MDUI 提供的 api 的 JS 文件头部导入 mdui 的 JS 文件 (来自 MDUI 作者)\n然后 MDUI 就已经添加进你的项目了,接下来只需要简单写一下,就可以让你的项目使用上 MDUI\n比如添加一个按钮\n\u0026lt;button @onclick=\u0026#34;javascript;;\u0026#34; class=\u0026#34;mdui-btn mdui-color-teal mdui-ripple\u0026#34;\u0026gt;button\u0026lt;/button\u0026gt; 或者一个导航栏\n\u0026lt;div class=\u0026#34;mdui-toolbar mdui-color-teal\u0026#34;\u0026gt; \u0026lt;a href=\u0026#34;javascript:;\u0026#34; class=\u0026#34;mdui-btn mdui-btn-icon\u0026#34;\u0026gt; \u0026lt;i class=\u0026#34;mdui-icon material-icons\u0026#34;\u0026gt;menu\u0026lt;/i\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;span class=\u0026#34;mdui-typo-title\u0026#34;\u0026gt;Test App\u0026lt;/span\u0026gt; \u0026lt;div class=\u0026#34;mdui-toolbar-spacer\u0026#34;\u0026gt;\u0026lt;/div\u0026gt; \u0026lt;a href=\u0026#34;javascript:;\u0026#34; class=\u0026#34;mdui-btn mdui-btn-icon\u0026#34;\u0026gt; \u0026lt;i class=\u0026#34;mdui-icon material-icons\u0026#34;\u0026gt;search\u0026lt;/i\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;a href=\u0026#34;window.onload();\u0026#34; class=\u0026#34;mdui-btn mdui-btn-icon\u0026#34;\u0026gt; \u0026lt;i class=\u0026#34;mdui-icon material-icons\u0026#34;\u0026gt;refresh\u0026lt;/i\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;a href=\u0026#34;javascript:;\u0026#34; class=\u0026#34;mdui-btn mdui-btn-icon\u0026#34;\u0026gt; \u0026lt;i class=\u0026#34;mdui-icon material-icons\u0026#34;\u0026gt;more_vert\u0026lt;/i\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/div\u0026gt; (代码从 VSCODE 编辑)\nPart 6: 打包 #在终端内输入打包命令进行打包\n$ yarn electron:build 请注意,在打包过程中需要下载一些文件,但由于天朝网络特性,可能会 timeout,请自行替换源或使用科学上网\n","date":"2022 年 07 月 22 日","permalink":"/posts/electron-vue-mdui/","section":"所有文章","summary":"将 Electron 集成到 Vue 项目中的指南","title":"小记 | Electron + Vue + Mdui"},{"content":"","date":null,"permalink":"/tags/android/","section":"标签","summary":"","title":"Android"},{"content":"","date":null,"permalink":"/tags/magisk/","section":"标签","summary":"","title":"Magisk"},{"content":"","date":null,"permalink":"/categories/magisk/","section":"分类","summary":"","title":"Magisk"},{"content":"","date":null,"permalink":"/tags/windows11/","section":"标签","summary":"","title":"Windows11"},{"content":" ⚠ 更新于2022/12/28 19:28 注意,MagiskOnWSA 已经被 GitHub 封禁\n如果有需要,可以尝试另一个项目 PeterNjeim/MagiskOnWSA\n此 repo 已经禁止 fork 有需要可以使用 GitHub 的 import 导入\n如题,这篇文章讲一讲 WSA 怎么样拥有 Magisk + LSPosed\n构建并安装你自己的 WSA #https://github.com/LSPosed/MagiskOnWSA\n这是 LSPosed 官方发布的 MagiskOnWSA 的 Actions\n进入后 Fork 仓库\nFork 完后进入 Actions\n按钮点上\n然后进入 Build WSA 这个 Workflow\n运行 Workflow\n要这样配置\n如果你要更改也是可以的\nBuild arch 代表构建的 WSA 版本,一般都是 x64\nWSA release type 是 WSA 发布的通道,我一般选择Slow\nMagisk version 是面具的版本,一般 Stable 就够用了,要尝鲜的可以用 Beta 或者 Canary\nVariants of gapps 要选择 Pico,选其他的也可以,不过工作流结束完后会有提示,OpenGapps 不支持 Android 12\nRemove Amazon AppStore 这个选 remove 就好,把亚马逊商店删掉\nRoot solution 选 Magisk / None 就不会 Root\n选好之后点击 Run Workflow\n运行好后进入 Artifacts 下载构建产物 这里的 1.95 GB 是源文件大小,用我的配置下载下来差不多是 870 MB 左右\n下载完后解压\n运行 Install.ps1\n等到部署完成\n这里 WSA 就安装完成了\n配置 WSA #打开 WSA 设置\n先配置图形\n⚠ Tips: Android 子系统目前 GPU 利用还不是很好,不要想着玩游戏\n转到开发人员\n打开开发人员模式\n安装 ADB #Android Platform Tools\n安装 ADB,解压到C盘\n进入高级系统设置 -\u0026gt; 环境变量 -\u0026gt; Path\n把 ADB 目录写进去 配置 Magisk #打开 Magisk\n点击小齿轮\n把Systemless hosts和Zygisk打开\n重启子系统\n如果主页这样显示就 OK 了\n安装 LSPosed #LSPosed Repo\n下载最新的 Release中的 Zygisk 版本\n放到一个位置,右键复制文件位置\n进入 Terminal\n连接到 WSA\nadb connect 127.0.0.1:58526 然后上传文件\nadb push 文件位置 /sdcard/ “文件位置” 记得替换成你自己的\n打开 Magisk 中的模块\n从本地安装\n选择 Pixel 5\n点击刚上传的 LSPosed\n等待安装完成即可\n安装酷安 #CoolAPK\n下载酷安安装包\n终端命令:\nadb install \u0026lt;文件位置\u0026gt; 安装 MT管理器 #打开酷安\n搜索 MT管理器,下载并安装\n安装 LSPosed Manager #打开 MT管理器,先授权超级用户权限\n然后找到之前上传的 LSPosed 压缩包\n右键长按并解压\n进入解压后的文件夹,找到 Daemon.apk\n点击它,然后点击功能 - \u0026gt; 签名 -\u0026gt; V3\n签名完成后安装 Daemon_sign.apk\n接着找到 Manager.apk\n安装它即可\n这样就安装完成了\n怎样安装 Magisk / LSPosed 模块 #先下载模块,用 adb push \u0026lt;文件位置\u0026gt; /sdcard/ 上传,然后安装即可\nLSPosed 模块安装方法 #进入 MT管理器,找到上传的的模块(APK),安装他\n然后进入 LSPosed Manager 启用即可\n","date":"2022 年 06 月 25 日","permalink":"/posts/android-windows-subsystem/","section":"所有文章","summary":"安卓子系统(Magisk)的指南","title":"介绍 | 适用于 Android™ 的 Windows 子系统 Magisk + LSPosed 指南"},{"content":"","date":null,"permalink":"/tags/picgo/","section":"标签","summary":"","title":"Picgo"},{"content":"","date":null,"permalink":"/categories/picgo/","section":"分类","summary":"","title":"Picgo"},{"content":" 文章迁移计划，样式可能出错\n最近想用 IPFS 做图床但是发现的一堆奇奇怪怪的图床\n(PS: 不会介绍传统的 GitHub + JSdelivr / SM.MS / 路过图床)\nBilibili 图床 #十分简单\nPicGO 搜索 bilibili\n接着配置 Bilibili 图床\nSESSDATA 可以登录哔哩哔哩后查看\n进入哔哩哔哩,按下 F12\n定位到应用 -\u0026gt; COOKIE\n选择 https://www.bilibili.com\n在名称内找到 SESSDATA\n点击它\n然后在 Cookie Value 中复制值,粘贴进去\n之后确定,设置默认图床即可\n良心的处理参数 #哔哩哔哩图床有十分良心的处理参数\nType URL 原图 不做更改 原分辨率, 质量压缩 原链接 + @1e_1c.jpg 原分辨率, 质量压缩(WEBP) 原链接 + @1e_1c.webp 规定宽, 高度自适应, 质量压缩(可变 WEBP) 原链接 + @104w_1e_1c.jpg 规定高, 宽度自适应, 质量压缩(可变 WEBP) 原链接 + @104h_1e_1c.jpg 规定高宽, 质量压缩(可变 WEBP) @104w_104h_1e_1c.jpg 原分辨率(WEBP) @104w_104h_1e_1c.webp 这是 哔哩哔哩 的处理参数\n哔哩哔哩还有许多节点可以选择\n自定义 CDN #金山 CDN 路线:\ni0.hdslb.com i2.hdslb.com 阿里 CDN 路线:\ni1.hdslb.com i4.hdslb.com s1.hdslb.com s3.hdslb.com 未知 CDN 路线:\ns2.hdslb.com Bilibili 图床有防盗链,如果你要在你自己的网站中用 Bilibili 图床,得先配置头\n\u0026lt;meta name=\u0026#34;referrer\u0026#34; content=\u0026#34;no-referrer\u0026#34;\u0026gt; 如果你用的 Hexo - Butterfly 则可以在配置文件中这样配置\ninject: head: - \u0026lt;meta name=\u0026#34;referrer\u0026#34; content=\u0026#34;no-referrer\u0026#34;\u0026gt; 2024 年 1 月 30 日更新：叔叔已经设置了禁止空头，目前哔哩哔哩图床使用有一些门槛了，如果你和我一样部分文章用了叔叔做图床，可以写一个跨域脚本\nimport https from \u0026#39;https\u0026#39;; export default async function handler(request, response) { const { fetch: imageUrl } = request.query; if (!imageUrl) { return response.status(400).json({ error: \u0026#39;No image URL provided\u0026#39; }); } const options = { headers: { \u0026#39;Referer\u0026#39;: \u0026#39;https://www.bilibili.com\u0026#39; } }; try { https.get(imageUrl, options, (res) =\u0026gt; { if (!res.statusCode || res.statusCode \u0026lt; 200 || res.statusCode \u0026gt;= 300) { return response.status(res.statusCode).json({ error: \u0026#39;Failed to fetch image\u0026#39; }); } response.setHeader(\u0026#39;Content-Type\u0026#39;, res.headers[\u0026#39;content-type\u0026#39;]); res.pipe(response); }).on(\u0026#39;error\u0026#39;, (error) =\u0026gt; { response.status(500).json({ error: error.message }); }); } catch (error) { response.status(500).json({ error: error.message }); } } 接着你可以写一个 Powershell 脚本进行替换\n$rootPath = \u0026#34;./\u0026#34; # 替换成你想要搜索的目录 $matchPattern = \u0026#39;https://i0\\.hdslb\\.com/\u0026#39; # 来匹配的URL $replaceWith = \u0026#39;https://api.hesiy.cn/api/cross?fetch=https://i0.hdslb.com/\u0026#39; # 替换为新的URL $markdownFiles = Get-ChildItem -Path $rootPath -Recurse -Filter *.md foreach ($file in $markdownFiles) { $content = Get-Content -Path $file.FullName -Raw $newContent = $content -replace $matchPattern, $replaceWith Set-Content -Path $file.FullName -Value $newContent } Write-Host \u0026#34;所有Markdown文件中的URL已经更新完成。\u0026#34; 注意：如果你使用了早期的哔哩哔哩图床或者自定义了 CDN，你也要跟着进行修改\n配套食用 #浏览器插件\nTypora 插件\n配合 typora 食用更佳\n如果用了很久之后,在上传时出现了错误,那就是 SESSDATA 过期了,重新去生成一个就好\nNPM + UNPKG 镜像 # 使用这个方法前记得切换回原来 NPM 的注册表.因为其他镜像没有发布这个功能\n十分简单\n本地先登录,接着创建包,然后初始化版本,最后发布\n指令:\n$ npm login $ npm init -y $ npm version patch $ npm publish 这样一个 npm 包就发布完了\n接下来是收集的一些镜像\nhttps://npm.elemecdn.com/ 饿了么(阿里 CDN) https://unpkg.zhimg.com/ 知乎(阿里 CDN) https://shadow.elemecdn.com/ 饿了么(阿里)(这个停止维护了) https://code.bdstatic.com/npm/ 百度(百度 CDN) https://cdn.jsdelivr.net/npm/ Jsdelivr(备案没封前是网宿 CDN | 后来备案封后是 Cloudflare + Fastly + Gcore)(可以自定义节点) Jsdelivr 自定义节点只需要:\ngcore.jsdelivr.net\nfastly.jsdleivr.net\n即可\n4EVERLAND BUCKET # 这应该是 IPFS 的另一个方案(但是最多 1G)\nPicGo 插件安装 # 搜索 s3 并安装\n然后就去 4EVERLAND 注册一个账户\nhttps://4everland.org/\n这个网站似乎只能用以太坊钱包登录,GitHub 登录会提示不支持了(\n我用的 MetaMask\n注册之后创建一个桶 生成 API TOKEN #点击你账户, 选择设置\n转到 Auth Token\n选择 Bucket Auth Tokens\n点击 Generate 即可\n本文章的 Key 在发布之前就删掉了,不要想着搞坏事\n之后进入 PicGo 配置下\n转到 Amazon S3\nID 和 密钥粘贴进去\n桶写你创建桶的时候的名字\n节点用 https://endpoint.4everland.co\n自定义域名是 桶名.4everland.store\n最后记得把这两个选项打开\nIPFSUPLOAD(真正意义上的 IPFS 图床) #本地装一个 IPFS DESKTOP\n等他启动后先看端口\n图中圈着的就是端口,然后去浏览器访问一下看看正不正常\nhttp://localhost:5001/api/v0/add?pin=true\n如果正常应该会提示 405 - Method Not Allowed\n接着就是上传了\nPicGo 安装一个 web-uploader\n然后配置如图\nIPFS网关我是用的我自己的,所以打了马赛克,你们可以自己先用 workers 反代一次,然后再用 CDN 过墙\n你也可以直接 CDN 我代理好的 worker (IPFS 官网)\nPS :我没有反代 IPFS 网关\n回源: dl2p.taiyu.workers.dev\n回源 HOST: dl2p.taiyu.workers.dev\n回源端口: 443\n回源协议: HTTPS\n速度测试:\n不给滥用,我设置了防盗链\nDogeDoge 图床 #没有什么说的,速度炒鸡快,使用要申请,现在应该申请不到了\n有的可以用\nBACKBLAZE B2 + Cloudflare #首先猜到有人要提问: 为什么要加 Cloudflare?\n因为 BACKBLAZE 有流量计费,但是他加入了带宽联盟,再加上 Cloudflare 超长缓存,≈ 完全免费\n但是有10G空间\n这是超出10G的价格\n注册方法 ChenYFan 大佬已经写过了,与其再写一篇问文章,不如直接用他的~~(其实就是懒)~~\n直接贴:\nhttps://blog.cyfan.top/p/ce240368.html\n云存储做图床 # 用 OneDrive Google Drive 做图床真的是闲\nOneDrive #参考 Spencer Woo 去搭建一个 onedrive-vercel-index 就行\nGoogle Drive #参考 ChenYFan 搭建一个 GDIndex 即可\n大厂的对象储存 #阿里云 OSS # 有很多人写,自己谷歌,反正价格挺便宜的\n腾讯云 COS # 🤢 这东西不推荐,计费十分离谱,而且配置头超长缓存后 COS 会在用户请求时悄悄篡改 max-cache\n新用户有 6 个月免费额度,之后就要开始付费了(\n⚠ 流量包和储存包都很贵\n又拍云 UOS # 又拍云似乎是没有免费额度的,但是又拍云有开发者联盟,只需要把他的 Logo 挂在你的网站页脚就行。\n\u0026lt;img src=\u0026#34;本地位置.svg\u0026#34;\u0026gt; 总结 #好用的图床千千万,你要自己去发现\n我总结了 7 个免费图床,其中 DogeDoge 图床需要申请\n如果细数的话,共有 8 个免费图床(每个云存储单独算一个)\n","date":"2022 年 05 月 15 日","permalink":"/posts/pictures-bed/","section":"所有文章","summary":"图床盘点","title":"小记 | 那些奇奇怪怪的图床"},{"content":"","date":null,"permalink":"/tags/gfw/","section":"标签","summary":"","title":"Gfw"},{"content":"","date":null,"permalink":"/tags/github/","section":"标签","summary":"","title":"Github"},{"content":"","date":null,"permalink":"/categories/github/","section":"分类","summary":"","title":"Github"},{"content":"我的博客即将同步至腾讯云开发者社区，邀请大家一同入驻：https://cloud.tencent.com/developer/support-plan?invite_code=3gll8aqhc2kgg\nDev-Sidecar # 请注意,由于开发者边车的 Gitee 仓库被封锁,所以这个项目不再更新了\n在使用之前,请观看知乎的一个讨论 如何评价《网络数据安全管理条例（征求意见稿）》第四十一条和第六十六条：建立数据跨境安全网关？\n进入开发者边车的 Release 找到符合自己系统的版本,点击下载 下载完成后安装,打开 系统代理建议不要开启 加速服务可以设置端口 其他的就没什么好设置的了,只需要把 NPM 加速和 Git 加速打开即可 But 因为这个软件已经停更了,所以我非常不建议各位用\nSwitchHosts + GitHub520 #这个项目是以 Hosts 来加速的 首先去 SwitchHosts 的 Release 接着下载安装 SwitchHosts 这样配置即可 如果遇到没有写入权限,就进入 Hosts 文件的属性,配置一下权限和去掉只读,玩 Windows 的应该都知道提权这东西吧\nWatt Toolkit # ⚠ 更新: 2022/12/28 19:53 新版的 Watt Toolkit 已经去掉了 Socks 设置\nWatt Toolkit\n下载安装没什么好说的\n进入软件勾选上所有的加速选项,然后选择 Hosts 加速模式 即可 如果怕加速不了的话就勾上加速选项里的 Socks5 代理 然后配置一下\nsudo git config --global http.proxy \u0026#39;socks5://127.0.0.1:8868\u0026#39; sudo git config --global https.proxy \u0026#39;socks5://127.0.0.1:8868\u0026#39; 8868 记得替换成你自己的端口\n题外话 #加速 npm 还是 Dev-Sidecar 和 local-npm 好用一点\n","date":"2022 年 05 月 14 日","permalink":"/posts/china-github/","section":"所有文章","summary":"GitHub 的多种开(访)车(问)方式","title":"小记 | GitHub 的多种访问方式"},{"content":"","date":null,"permalink":"/tags/hexo/","section":"标签","summary":"","title":"Hexo"},{"content":"","date":null,"permalink":"/categories/yuque/","section":"分类","summary":"","title":"Yuque"},{"content":"本文章首发于语雀 !\n通过各种高科技功能同步到 Hajeekn 的博客\n本文章来源于 SL\u0026rsquo;s Blog（本博客早期版本），仅作文章迁移处理 起初啦，我本来是想在语雀写文章同步到 Hexo 很方便。 可是呢？捣鼓的过程中踩了一系列的坑，这篇文章主要记录一下踩的那些坑\n1. 文章 Cover 自动被转为链接格式 #这个也是最头疼的事情 在我同步文章上去后，文章的 cover 地址全部亮亮的，起初我没注意，不过 GitHub Actions 编译静态文件的时候我注意到了。\n请大家注意看！\n在 cover 处，原本的\nxxxx.com/xxx.png 变成了 []() 格式，也就是说语雀平台把这个 cover 配置识别为了一串链接，导致 Hexo 同步下来时渲染失败了。\n解决办法 #在语雀内部点击这个被识别为链接的图片地址，选择删除链接 (最后一个) 即可\n2. 写法错误 #同步到语雀后 front-matter 格式和 Hexo 本地格式不是一样的。 也就是说本地和云端要放两套不同的模板 (建议还是把本地的数据备份一下到 OneDrive 之类的网盘)\n解决办法 #使用如下模板\n--- title: #文章标题 tags: [tag1, tag2] categories: [cate1,cate2] cover: #文章Cover top_img: false #是否启用顶部大图 copyright_author_href: #作者网站 keywords: #关键字 description: #介绍 id: #第几篇文章 date: #编写日期 updated: #更新日期 --- ","date":"2020 年 01 月 20 日","permalink":"/posts/yuque-sync-to-hexo/","section":"所有文章","summary":"将语雀文章同步到 Hexo 时踩的坑","title":"小记 | 语雀文章同步到 Hexo"}]