<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Elysiam</title><description>May you, the beauty of this world, always shine</description><link>https://blog.170529.xyz/</link><language>zh_CN</language><item><title>Android 音频焦点与共存播放机制研究</title><link>https://blog.170529.xyz/posts/android_audio_focus/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/android_audio_focus/</guid><description>对 Android 音频焦点机制进行深入研究，分析其运作原理以及在 Flutter 环境下实现与其他应用共存播放的最佳实践。</description><pubDate>Fri, 03 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image &lt;a href=&quot;https://www.pixiv.net/artworks/142784872&quot;&gt;画中游 | 九道梵音 #Pixiv&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Android 音频焦点与共存播放机制研究&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最开始处理 Flutter 音视频播放的“后台被抢焦点就暂停”问题时，很容易先入为主地认为：既然想和网易云或其它音乐软件“同时播放”，那我不去抢音频焦点不就得了？毕竟按照直觉，你不惹事，别人自然也不会来弄停你。&lt;/p&gt;
&lt;p&gt;但真往 Android 的底层和实现里翻，就会发现事情没有这么简单。在现代 Android（尤其是 Android 15+ 引入了 &lt;code&gt;AudioHardening&lt;/code&gt; 机制后），音频焦点的管理可以说是一门非常“玄学”且充满强权的艺术。&lt;/p&gt;
&lt;p&gt;本文主要基于 PiliNara 的实际迭代经验和系统级日志，整理一下 Android 音频焦点的运作机制，以及在 Flutter 环境下，如何利用系统协议的“潜规则”来实现与其他应用的完美混音共存。&lt;/p&gt;
&lt;h2&gt;什么是音频焦点（Audio Focus）？&lt;/h2&gt;
&lt;h3&gt;它并不是物理排他的&lt;/h3&gt;
&lt;p&gt;我们常听说的“音频焦点（Audio Focus）”，在 Android 系统里本质上并不是一个物理开关，而是一种**“礼貌协议”（Cooperative Protocol）**。&lt;/p&gt;
&lt;p&gt;当你想播放声音时，你需要向系统（&lt;code&gt;AudioManager&lt;/code&gt;）发出一个 &lt;code&gt;requestAudioFocus&lt;/code&gt; 请求，声明你需要占用声道。系统会根据你请求的等级，给当前正在播放的其他 App 发送不同级别的 &lt;code&gt;LOSS&lt;/code&gt;（丢失焦点）回调：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AUDIOFOCUS_LOSS (-1)&lt;/code&gt;：永久丢失。比如接到了电话，或者用户打开了另一款大型的独占音乐 App。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUDIOFOCUS_LOSS_TRANSIENT (-2)&lt;/code&gt;：临时丢失。比如微信来了条提示音，或者用户在那边刷了一个短视频。&lt;/li&gt;
&lt;li&gt;&lt;code&gt;AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK (-3)&lt;/code&gt;：临时丢失，但允许你“低头”（Duck）。比如导航播报时，你可以把自己的音量压低一半，不用完全闭嘴。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
对应谷歌文档的链接：&lt;a href=&quot;https://developer.android.google.cn/reference/android/media/AudioManager#AUDIOFOCUS_LOSS&quot;&gt;AudioManager&lt;/a&gt;
:::&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;核心事实&lt;/strong&gt;：收到这些信号后，系统&lt;strong&gt;通常不会直接物理做相关的操作&lt;/strong&gt;（前提是你是合法的媒体应用），而是看你自己的代码怎么写。常规的、老实的 App 收到 &lt;code&gt;-1&lt;/code&gt; 或 &lt;code&gt;-2&lt;/code&gt; 时，代码里会乖乖调用 &lt;code&gt;player.pause()&lt;/code&gt;。于是外界看起来就像是“被抢走焦点，声音断了”。&lt;/p&gt;
&lt;h3&gt;“不申请焦点”行不通吗？&lt;/h3&gt;
&lt;p&gt;既然收到信号就要乖乖暂停，那最直觉的想法自然是：&lt;strong&gt;那我干脆一上来就不申请音频焦点，是不是系统就不管我了？&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;在早年间的 Android 系统中，这确实可行。但在最新的 Android 15+ 机制下，这其实是在“裸奔”。系统引入了 &lt;code&gt;AudioHardening&lt;/code&gt; 策略。如果你作为一个后台媒体应用，试图在没有持有有效音频焦点（或相应权限等级不足）的情况下向硬件输出音频数据，系统会立刻探测到你的非法行为，并从硬件层强制执行 Mute（静音）。&lt;/p&gt;
&lt;p&gt;抓到的底层日志非常直白：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;AudioHardening background playback would be muted for com.example.pilinara (10164), level: full
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以得出一个非常明确的结论：&lt;strong&gt;想要播放，你必须去拿焦点，这是“准入证”。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;“厚脸皮”协议：两个独占焦点如何共存&lt;/h2&gt;
&lt;p&gt;既然必须老老实实当“大哥”去申请最高级别的独占媒体身份（&lt;code&gt;AUDIOFOCUS_GAIN&lt;/code&gt;，即 &lt;code&gt;req=1&lt;/code&gt;），那当其他音乐软件也申请 &lt;code&gt;req=1&lt;/code&gt; 的时候，必定有一方会收到 &lt;code&gt;LOSS_TRANSIENT&lt;/code&gt; 或 &lt;code&gt;LOSS&lt;/code&gt; 信号。那为什么我们在用网易云音乐时，开启了对应的与其他应用同时播放功能却能一边听歌一边顺畅地刷 B站？&lt;/p&gt;
&lt;p&gt;这就是 Android 音频焦点机制中最有意思的地方。&lt;/p&gt;
&lt;p&gt;如前所述，系统发出的 &lt;code&gt;LOSS&lt;/code&gt; 信号更像是一种“道德约束”。当网易云（App B）发起 &lt;code&gt;req=1&lt;/code&gt; 抢焦点时，系统给当前正在播放的前台 App A 发送了一个 &lt;code&gt;LOSS_TRANSIENT (-2)&lt;/code&gt; 信号。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;普通 App 的反应&lt;/strong&gt;：收到信号 -&amp;gt; 触发监听 -&amp;gt; 乖乖暂停。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;网易云 / 厚脸皮 App 的反应&lt;/strong&gt;：收到信号 -&amp;gt; 代码里直接写一个 &lt;code&gt;return // ignore this signal&lt;/code&gt; -&amp;gt; &lt;strong&gt;拒不暂停&lt;/strong&gt;，继续疯狂往底层的 &lt;code&gt;AudioTrack&lt;/code&gt; 塞数据。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;结果就是，Android 系统的底层混音器（AudioFlinger）看到两个流都有数据源，就会机械地把两个 App 的数据帧混合在一起。只要你具备了前台媒体服务（&lt;code&gt;mediaPlayback&lt;/code&gt;）的系统权限，系统并不会直接封杀你。这就是实现两个 &lt;code&gt;GAIN&lt;/code&gt; 级别共存的底层密码。&lt;/p&gt;
&lt;h2&gt;Flutter (audio_session) 下的最佳实践&lt;/h2&gt;
&lt;p&gt;分析完了底层逻辑，在 Flutter 项目中，尤其是当底层视频库（如基于 mpv 的 &lt;code&gt;media_kit&lt;/code&gt;）把焦点管理都全权交给我们另外引入的 &lt;code&gt;audio_session&lt;/code&gt; 插件时，该如何写这套逻辑呢？&lt;/p&gt;
&lt;p&gt;这也是 PiliNara 在兼容混音播放时踩过的一个大坑。这块逻辑真正难的地方，不是“怎么去骗过系统”，而是&lt;strong&gt;你到底该在什么时候该装聋作哑，什么时候该乖乖闭嘴&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果无脑忽略所有系统中断，那在用户接到语音通话时，你的视频声音还在震天响，这就是严重的体验事故了。&lt;/p&gt;
&lt;p&gt;目前在 PiliNara 里的处理方式如下：&lt;/p&gt;
&lt;h3&gt;1. 配置层：持证上岗&lt;/h3&gt;
&lt;p&gt;首先是需要请求相关的权限&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;修改 AndroidManifest.xml (必须)
从 Android 14 开始，系统要求必须显式声明前台服务类型。对于视频/音频播放，必须声明 mediaPlayback。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;uses-permission android:name=&quot;android.permission.FOREGROUND_SERVICE&quot; /&amp;gt;

&amp;lt;uses-permission android:name=&quot;android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK&quot; /&amp;gt;

&amp;lt;application ...&amp;gt;
    &amp;lt;service
        android:name=&quot;.YourPlaybackService&quot; 
        android:foregroundServiceType=&quot;mediaPlayback&quot;
        android:export=&quot;false&quot;&amp;gt;
    &amp;lt;/service&amp;gt;
&amp;lt;/application&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;对于希望“与其他应用共存”的场景，我们在 Android 端坚决不再使用类似 &lt;code&gt;androidAudioFocusGainType: null&lt;/code&gt; 这样企图隐身的危险写法（实际上这也是违背 Dart 强类型语法的），而是恢复它作为主权媒体应用的声明。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在 Android 上保持不变，老老实实申请最高级别焦点
await session.configure(const AudioSessionConfiguration.music());
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这确保了我们的 App 在系统层面拥有“准入证”。&lt;/p&gt;
&lt;h3&gt;2. 逻辑层：精准拦截“劝退”信号&lt;/h3&gt;
&lt;p&gt;真正的核心手术在 &lt;code&gt;interruptionEventStream&lt;/code&gt; 监听器里。当产生冲突时：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;系统发来 &lt;strong&gt;&lt;code&gt;AudioInterruptionType.pause&lt;/code&gt;&lt;/strong&gt;（对应原生的 &lt;code&gt;LOSS_TRANSIENT&lt;/code&gt;）：比如打开了别的短视频软件。这时候如果用户开启了 &lt;code&gt;mixWithOthers&lt;/code&gt; 选项，选择“厚脸皮”，直接 &lt;code&gt;return&lt;/code&gt; 忽视他。&lt;/li&gt;
&lt;li&gt;系统发来 &lt;strong&gt;&lt;code&gt;AudioInterruptionType.unknown&lt;/code&gt;&lt;/strong&gt;（对应原生的 &lt;code&gt;LOSS&lt;/code&gt; 高危信号）：比如打进来了电话。这种涉及严重系统资源回收或高优先级通话时，必须强制暂停。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;具体的代码实现实际上非常轻量：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;session.interruptionEventStream.listen((event) {
  if (event.begin) {
    switch (event.type) {
      case AudioInterruptionType.duck:
        // 被压低音量，比如系统导航语音
        PlPlayerController.setVolumeIfExists(... * 0.5);
        break;

      case AudioInterruptionType.pause:
        // 如果系统发来临时切断（比如外部播放音乐），且我们开启了同时播放选项，直接拦截（装聋作哑）
        if (Pref.mixWithOthers) return; 
        
        // 否则乖乖暂停
        PlPlayerController.pauseIfExists(isInterrupt: true);
        break;

      case AudioInterruptionType.unknown:
        // 来了电话这种硬断事件，无论如何都要老实暂停
        PlPlayerController.pauseIfExists(isInterrupt: true);
        break;
    }
  } else {
    // 恢复逻辑...
  }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;到这里其实已经能得出一个明确的结论：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;完全避开焦点申请不仅是错的，而且非常危险&lt;/strong&gt;，尤其在现代 Android 系统的 &lt;code&gt;AudioHardening&lt;/code&gt; 环境下。&lt;/li&gt;
&lt;li&gt;真正想要实现像网易云一样的系统级完美并存，本质上就是脸皮厚：去申请最大、最长效的焦点保护自己的存活，然后在收到别人想抢你位置的 &lt;code&gt;LOSS_TRANSIENT&lt;/code&gt; 通知时装作没听见。&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>B站表情渲染机制研究</title><link>https://blog.170529.xyz/posts/bilibili_emote_rendering_research/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/bilibili_emote_rendering_research/</guid><description>对B站目前的表情渲染机制进行研究和分析，探讨其实现原理和实际开发中可能遇到的问题。</description><pubDate>Sat, 28 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image &lt;a href=&quot;https://www.pixiv.net/artworks/142135795&quot;&gt;爻光 | knmoca&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;B站表情渲染机制研究&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最开始看 B 站表情时，很容易先入为主地把它当成普通 emoji。毕竟在评论区里看到一句 &lt;code&gt;啊~是陀螺王来了[吃瓜]&lt;/code&gt;，直觉上会以为前端只是把某些特殊字符渲染成图片了。&lt;/p&gt;
&lt;p&gt;但真往实现里翻，就会发现事情没有这么简单。B 站这套表情机制，至少在目前 PiliNara 实际碰到的场景里，已经分成了几条完全不同的链路：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;评论区、私信、直播底部弹幕面板里的“方括号表情”&lt;/li&gt;
&lt;li&gt;动态、专栏这种服务端直接下发富文本节点的表情&lt;/li&gt;
&lt;li&gt;直播消息里整条消息就是一张图的 &lt;code&gt;uemote&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;它们表面看起来都叫“表情”，但返回结构、渲染方式、尺寸语义其实都不一样。&lt;/p&gt;
&lt;p&gt;本文主要基于 PiliNara 当前实现和上游PiliPlus的实现和实际抓到的返回数据，整理一下 B 站表情的工作机制，以及直播 &lt;code&gt;uemote&lt;/code&gt; 这条链路里几个比较容易踩坑的地方。&lt;/p&gt;
&lt;p&gt;需要提前说明一点：本文讨论的是&lt;strong&gt;评论区、私信、动态、直播底部弹幕面板&lt;/strong&gt;这些富文本显示链路，不讨论播放器上的 &lt;code&gt;canvas_danmaku&lt;/code&gt; 飘屏弹幕层。后者本身就是另一套渲染体系。&lt;/p&gt;
&lt;h2&gt;B站表情机制的解析与渲染方法&lt;/h2&gt;
&lt;h3&gt;它并不是普通意义上的 emoji&lt;/h3&gt;
&lt;p&gt;像 &lt;code&gt;[吃瓜]&lt;/code&gt;、&lt;code&gt;[doge]&lt;/code&gt; 这种内容，从数据层看通常并不是 Unicode emoji，而是&lt;strong&gt;一段普通文本 token&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;也就是说，后端并不会把它编码成某种特殊字符，而是仍然保留为：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[吃瓜]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;前端真正做的事情，其实是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先拿到原始文本&lt;/li&gt;
&lt;li&gt;再拿到一份“这个 token 对应哪张图片”的映射表&lt;/li&gt;
&lt;li&gt;最后在渲染时把文本中的命中片段替换成图片 span&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;所以这类表情在复制时还能拿到 &lt;code&gt;[吃瓜]&lt;/code&gt; 原文，并不奇怪。因为原始内容本来就是这个字符串，图片只是显示层效果。&lt;/p&gt;
&lt;h3&gt;评论区：&lt;code&gt;message + emotes&lt;/code&gt; 的经典模式&lt;/h3&gt;
&lt;p&gt;评论区这条链最典型。&lt;/p&gt;
&lt;p&gt;在 gRPC 的评论 &lt;code&gt;Content&lt;/code&gt; 结构里，既有原始文本 &lt;code&gt;message&lt;/code&gt;，也有 &lt;code&gt;emotes&lt;/code&gt; 映射表。PiliPlus 的评论渲染逻辑会把：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;content.emotes.keys&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;话题&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@用户名&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些特殊 token 全部拼进正则，然后对 &lt;code&gt;content.message&lt;/code&gt; 做一次扫描，命中 &lt;code&gt;emotes&lt;/code&gt; 时替换成 &lt;code&gt;WidgetSpan&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这一套的好处是很明显的：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;原文可复制&lt;/li&gt;
&lt;li&gt;表情和普通文本可以混排&lt;/li&gt;
&lt;li&gt;同一套扫描逻辑还能顺手处理话题、时间戳、链接、投票&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;评论区表情的尺寸也不是直接看图片原始宽高，而是使用服务端下发的 &lt;code&gt;size&lt;/code&gt; 字段，再乘一个前端基准值。目前 PiliPlus 里评论区的实现是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;final size = emote.size.toInt() * 20.0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;所以评论区里“有些表情大，有些表情小”，本质上是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;服务端决定相对大小等级&lt;/li&gt;
&lt;li&gt;前端决定最终展示基准&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;私信：同样是 token 替换，但结构换成了 &lt;code&gt;eInfos&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;私信并没有复用评论区的 &lt;code&gt;emotes&lt;/code&gt; 字段，而是通过 IM 接口单独返回一组 &lt;code&gt;EmotionInfo&lt;/code&gt; 列表。&lt;/p&gt;
&lt;p&gt;PiliPlus 的做法是把这组 &lt;code&gt;eInfos&lt;/code&gt; 转成：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;text -&amp;gt; url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;text -&amp;gt; size&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这样的本地映射，再对消息正文做扫描。命中后同样替换成图片.&lt;/p&gt;
&lt;p&gt;这一层和评论区很像，只是协议长得不一样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;评论区是 &lt;code&gt;message + emotes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;私信是 &lt;code&gt;content + eInfos&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但渲染思想是一样的，依然是“原文 token + 图片映射”。&lt;/p&gt;
&lt;h3&gt;动态 / 专栏：服务端直接下发表情节点&lt;/h3&gt;
&lt;p&gt;动态、专栏再往前走一步，不再要求前端自己从一长串纯文本里拆 &lt;code&gt;[吃瓜]&lt;/code&gt; 了。&lt;/p&gt;
&lt;p&gt;这类场景里，服务端很多时候直接把内容切成了富文本节点，例如：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;普通文本节点&lt;/li&gt;
&lt;li&gt;话题节点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@&lt;/code&gt; 节点&lt;/li&gt;
&lt;li&gt;链接节点&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RICH_TEXT_NODE_TYPE_EMOJI&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前端拿到这种结构之后，直接按节点类型渲染就行。命中 &lt;code&gt;RICH_TEXT_NODE_TYPE_EMOJI&lt;/code&gt; 时，直接插入图片，不需要再手写一轮 token 扫描。&lt;/p&gt;
&lt;p&gt;这也是为什么动态、专栏这类页面的表情链路通常比评论区更“稳定”：因为服务端已经帮前端把富文本拆好了。&lt;/p&gt;
&lt;p&gt;但是这里有个额外的坑,默认的动态获取接口里面,会将原文所有的链接替换成&quot;网页链接&quot;,导致如果普通的获取就会丢失掉文章中的链接,所以需要使用动态详情接口重新获取,在详情接口中,原文链接会被保留.具体做法就这里不细说了&lt;/p&gt;
&lt;h3&gt;直播底部弹幕面板：&lt;code&gt;emots&lt;/code&gt; 和 &lt;code&gt;uemote&lt;/code&gt; 是两套东西&lt;/h3&gt;
&lt;p&gt;直播底部弹幕面板是最容易让人误判的一块，因为它里面同时存在两种看起来都像“表情”的东西：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;emots&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;uemote&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;它们虽然都显示成图，但语义完全不同。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;emots&lt;/code&gt;：行内表情&lt;/h4&gt;
&lt;p&gt;这类更像评论区那套机制。&lt;/p&gt;
&lt;p&gt;直播消息正文仍然是一段文本，比如：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;啊这[吃瓜]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如图:&lt;img src=&quot;https://r2.170529.xyz/PicList/2026/03/20260328143631032.avif&quot; alt=&quot;普通表情&quot; /&gt;&lt;/p&gt;
&lt;p&gt;同时 &lt;code&gt;extra[&apos;emots&apos;]&lt;/code&gt; 里会给出：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&quot;[吃瓜]&quot; -&amp;gt; { url, width, height, ... }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;前端渲染时扫描正文，命中后把这段方括号 token 换成图片。&lt;/p&gt;
&lt;p&gt;这种情况下，图片是“嵌在文本里”的，所以它属于&lt;strong&gt;行内表情&lt;/strong&gt;。&lt;/p&gt;
&lt;h4&gt;&lt;code&gt;uemote&lt;/code&gt;：整条消息就是一张图&lt;/h4&gt;
&lt;p&gt;而 &lt;code&gt;uemote&lt;/code&gt; 不是行内替换，而是整条消息本身就是一个表情消息。&lt;/p&gt;
&lt;p&gt;一旦消息里存在 &lt;code&gt;uemote&lt;/code&gt;，PiliPlus 当前实现会直接走图片分支，返回一个单独的 &lt;code&gt;WidgetSpan&lt;/code&gt;，不再继续渲染 &lt;code&gt;text&lt;/code&gt; 文本。&lt;/p&gt;
&lt;p&gt;这类消息里 &lt;code&gt;text&lt;/code&gt; 字段很多时候更像是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;兼容字段&lt;/li&gt;
&lt;li&gt;语义文本&lt;/li&gt;
&lt;li&gt;回退文案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但真正显示给用户看的，是 &lt;code&gt;uemote&lt;/code&gt; 里的图片。&lt;/p&gt;
&lt;h2&gt;&lt;code&gt;uemote&lt;/code&gt; 的返回解析&lt;/h2&gt;
&lt;p&gt;目前在 PiliNara 里，直播 &lt;code&gt;uemote&lt;/code&gt; 被解析成了一个很轻量的 &lt;code&gt;BaseEmote&lt;/code&gt;：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class BaseEmote {
  late String url;
  late String emoticonUnique;
  late double width;
  late double height;
  late final isUpower = emoticonUnique.startsWith(&apos;upower_&apos;);
  //以下是PiliNara相较于PiliPlus新增的字段,根据emoticonUnique前缀派生出来的本地分类
  late final isOfficial = emoticonUnique.startsWith(&apos;official_&apos;);
  late final isRoom = emoticonUnique.startsWith(&apos;room_&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也就是说，当前渲染阶段真正依赖的核心字段只有 4 个：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;emoticon_unique&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;width&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;height&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而几类 &lt;code&gt;uemote&lt;/code&gt; 的判断，实际上并不是接口额外给了 &lt;code&gt;isOfficial&lt;/code&gt;、&lt;code&gt;isRoom&lt;/code&gt; 这种字段，而是&lt;strong&gt;根据 &lt;code&gt;emoticon_unique&lt;/code&gt; 的前缀派生出来的本地分类&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;常见类型目前至少有这几种：
房间专属表情&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;房间专属表情 &lt;code&gt;room_{{room_id}}_{{int}}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;通用表情 (包含一般通用表情和个别频道的特殊表情)&lt;code&gt;official_{{int}}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;付费相关表情,比如收藏集,装扮等 &lt;code&gt;upower_[{{emote}}]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;从协议设计上看，这个字段更像是 B 站在直播系统里给表情做的一层“类型命名空间”。而真正让前端头疼的地方在于：&lt;strong&gt;不同前缀下，&lt;code&gt;width/height&lt;/code&gt; 的语义并不统一&lt;/strong&gt;,&lt;s&gt;感觉官方在瞎写?&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;这也是后面踩坑的根源。&lt;/p&gt;
&lt;h2&gt;几类 &lt;code&gt;uemote&lt;/code&gt; 的渲染踩坑与处理方式&lt;/h2&gt;
&lt;p&gt;这一段是本文最有价值的部分。因为 &lt;code&gt;uemote&lt;/code&gt; 真正难的地方，不是“怎么显示一张图”，而是&lt;strong&gt;你到底该不该信接口返回的尺寸&lt;/strong&gt;。&lt;/p&gt;
&lt;h3&gt;1. &lt;code&gt;room_*&lt;/code&gt;：当前策略能正常工作的类型&lt;/h3&gt;
&lt;p&gt;先看一个正常的例子：&lt;/p&gt;
&lt;p&gt;如图:&lt;img src=&quot;https://r2.170529.xyz/PicList/2026/03/20260328144436536.avif&quot; alt=&quot;room_* 表情&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;name&quot;: &quot;xxx&quot;, //用户名
    &quot;text&quot;: &quot;嘤嘤嘤&quot;,  //文本内容,但对于uemote来说通常不显示
    &quot;uemote&quot;: {
        &quot;url&quot;: &quot;http://i0.hdslb.com/bfs/live/904ead78c8c91f5defc18ac506ad6dc5b9cdf694.png&quot;,
        &quot;emoticon_unique&quot;: &quot;room_1370893_24538&quot;,
        &quot;width&quot;: 162.0,
        &quot;height&quot;: 162.0
    },
    &quot;extra&quot;: {
        &quot;id&quot;: &quot;7d0b33b9d07dcef93da32f42ad69c7786684&quot;,
        &quot;mid&quot;: 675119090,
        &quot;dm_type&quot;: 1,
        &quot;ts&quot;: 1774680118,
        &quot;ct&quot;: &quot;2761E891&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这一类在 PiliNara 当前实现里显示正常，和官方一致。&lt;/p&gt;
&lt;p&gt;原因并不是因为代码里专门对 &lt;code&gt;room_*&lt;/code&gt; 做了什么高级处理，而是因为&lt;strong&gt;现有的缩放规则刚好适配了它的返回值语义&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;当前 &lt;code&gt;uemote&lt;/code&gt; 渲染逻辑里，除 &lt;code&gt;upower_*&lt;/code&gt; 和 &lt;code&gt;official_*&lt;/code&gt; 之外，默认会对尺寸做：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;width = uemote.width / devicePixelRatio;
height = uemote.height / devicePixelRatio;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果 &lt;code&gt;room_*&lt;/code&gt; 返回的 &lt;code&gt;162x162&lt;/code&gt; 更接近“物理像素尺寸”，那在 Flutter 里除以设备 DPR 之后，就正好变成了合理的逻辑像素尺寸。&lt;/p&gt;
&lt;p&gt;也就是说，&lt;code&gt;room_*&lt;/code&gt; 之所以没出问题，不是因为协议最规范，而是因为它返回的数据刚好跟当前这套换算规则对上了。&lt;/p&gt;
&lt;p&gt;因此当前策略是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;room_*&lt;/code&gt; 保留现状，不动&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. &lt;code&gt;official_*&lt;/code&gt;：直接按现有规则渲染会偏小&lt;/h3&gt;
&lt;p&gt;再看 &lt;code&gt;official_*&lt;/code&gt; 的实际抓包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;xxxxx&quot;,
  &quot;text&quot;: &quot;一诺行为&quot;,
  &quot;uemote&quot;: {
    &quot;url&quot;: &quot;https://i0.hdslb.com/bfs/live/74049288b8e65ab81ecddfe5d4dc475cd8a56b85.png&quot;,
    &quot;emoticon_unique&quot;: &quot;official_54&quot;,
    &quot;width&quot;: 195.0,
    &quot;height&quot;: 60.0
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这类消息最开始在 PiliNara 里的显示会偏小。原因也很扯淡：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;当前旧逻辑里，它会被当成“普通非 upower 的 uemote”&lt;/li&gt;
&lt;li&gt;然后直接走 &lt;code&gt;width / DPR&lt;/code&gt;、&lt;code&gt;height / DPR&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;如果设备 DPR 比较高，例如 3 左右，那么：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;60 / 3 = 20&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;视觉上就会很接近一行字的高度，这是非常正常的。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;official_*&lt;/code&gt; 的返回尺寸是正确的,处理也是正确的,那为啥会偏小呢?原因是官方客户端里这类表情的实际显示尺寸，比接口返回的尺寸要大一些。&lt;/p&gt;
&lt;p&gt;在当前版本里，PiliNara 对 &lt;code&gt;official_*&lt;/code&gt; 的处理做了一个经验修正：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;仍然先除以 DPR&lt;/li&gt;
&lt;li&gt;再在此基础上额外乘 &lt;code&gt;1.25&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;实现大致是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;width = uemote.width / devicePixelRatio * 1.25;
height = uemote.height / devicePixelRatio * 1.25;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这不是来自官方文档，而是基于实际对比官方客户端效果后的工程修正。它的优点是实现简单、抖动小、成本低；缺点也很明显，就是&lt;strong&gt;没有明确的协议依据&lt;/strong&gt;，完全是个经验值。&lt;/p&gt;
&lt;p&gt;但在没有更完整官方说明的前提下，这已经是比较稳妥的折中。&lt;/p&gt;
&lt;h3&gt;3. &lt;code&gt;upower_[...]&lt;/code&gt;：接口明明给了 &lt;code&gt;20x20&lt;/code&gt;，官方却显示成大图&lt;/h3&gt;
&lt;p&gt;最离谱的坑出现在这类应援装扮、收藏集、装扮赠送相关表情上。&lt;/p&gt;
&lt;p&gt;实际抓到的一个例子如下：
&lt;img src=&quot;https://r2.170529.xyz/PicList/2026/03/20260328144941218.avif&quot; alt=&quot;在PiliNara的显示效果&quot; /&gt;
&lt;img src=&quot;https://r2.170529.xyz/PicList/2026/03/20260328145158306.avif&quot; alt=&quot;官方客户端显示效果&quot; /&gt;&lt;/p&gt;
&lt;p&gt;对应的API返回是这样的：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;珈乐Dollar&quot;,
  &quot;text&quot;: &quot;[星绘·沐春灼华 应援装扮_腹黑]&quot;,
  &quot;uemote&quot;: {
    &quot;url&quot;: &quot;https://i0.hdslb.com/bfs/garb/ea50e865dab1906e04c87fc268e7259be0782292.png&quot;,
    &quot;emoticon_unique&quot;: &quot;upower_[星绘·沐春灼华 应援装扮_腹黑]&quot;,
    &quot;width&quot;: 20.0,
    &quot;height&quot;: 20.0
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果单看这份返回，很自然会认为这是一张小图表情。&lt;/p&gt;
&lt;p&gt;但实际对比官方客户端后会发现，官方根本没有按 &lt;code&gt;20x20&lt;/code&gt; 显示，而是按大图展示，视觉尺寸接近图片原始 &lt;code&gt;162x162&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;这说明了一个非常重要的事实：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;对 &lt;code&gt;upower_[...]&lt;/code&gt; 这类 &lt;code&gt;uemote&lt;/code&gt;，接口返回的 &lt;code&gt;width/height&lt;/code&gt; 不能直接信。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;这类坑和 &lt;code&gt;official_*&lt;/code&gt; 还不一样。&lt;code&gt;official_*&lt;/code&gt; 更像是“返回值能参考，但不能机械照搬”；而 &lt;code&gt;upower_[...]&lt;/code&gt; 在部分场景里是“返回值本身就和官方客户端的最终显示不一致”。&lt;/p&gt;
&lt;p&gt;那怎么办？最开始,一种最直觉的想法是：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;实时去请求图片原始尺寸&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;但这个方案实际并不优雅：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;会增加额外网络请求&lt;/li&gt;
&lt;li&gt;首帧显示容易先小后大&lt;/li&gt;
&lt;li&gt;同一张图会重复查询&lt;/li&gt;
&lt;li&gt;对聊天列表这种高频场景不太友好&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此，PiliNara 目前采取的是最暴力的处理方式：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;对 &lt;code&gt;upower_*&lt;/code&gt; 这类消息不再信返回的 &lt;code&gt;20x20&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;直接按一个经验源尺寸 &lt;code&gt;162px&lt;/code&gt; 处理&lt;/li&gt;
&lt;li&gt;再换算成 Flutter 里的逻辑像素&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;当前实现是：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const upowerDefaultPx = 162.0;
width = upowerDefaultPx / devicePixelRatio;
height = upowerDefaultPx / devicePixelRatio;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这依然不是来自官方文档，而是基于目前抓到的装扮/收藏集表情样本做出的经验规则。&lt;/p&gt;
&lt;p&gt;它的核心思想其实很简单：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;room_*&lt;/code&gt; 可以信接口尺寸&lt;/li&gt;
&lt;li&gt;&lt;code&gt;official_*&lt;/code&gt; 半信半疑，需要修正&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upower_[...]&lt;/code&gt; 的某些子类根本不能信接口尺寸&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4. 为什么不能把所有 &lt;code&gt;uemote&lt;/code&gt; 用一套规则吃掉&lt;/h3&gt;
&lt;p&gt;到这里其实已经能得出一个很明确的结论：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uemote.width/height&lt;/code&gt; 在不同类型下，语义并不统一。&lt;/p&gt;
&lt;p&gt;至少目前已有样本说明：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;room_*&lt;/code&gt;：当前更像“物理像素尺寸”，除以 DPR 后结果正确&lt;/li&gt;
&lt;li&gt;&lt;code&gt;official_*&lt;/code&gt;：单纯除以 DPR 会偏小，需要经验放大&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upower_[...]&lt;/code&gt;：至少部分装扮类消息里，接口尺寸和官方最终显示明显不一致&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;因此如果还坚持“所有 &lt;code&gt;uemote&lt;/code&gt; 都统一按 &lt;code&gt;width/height&lt;/code&gt; 渲染”，基本一定会出错。&lt;/p&gt;
&lt;p&gt;真正稳妥的思路反而是：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;先按 &lt;code&gt;emoticon_unique&lt;/code&gt; 做分类&lt;/li&gt;
&lt;li&gt;再为不同类别选择不同的尺寸策略&lt;/li&gt;
&lt;li&gt;在没有完整协议文档时，允许保留少量经验修正&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这听起来土一点，但在实际逆向和兼容类工作里，往往比“一套优雅大一统公式”更靠谱。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;B 站表情机制并不是一个统一模型，而是几套机制并行存在：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;评论区、私信、直播行内表情，本质上是“文本 token + 映射表”&lt;/li&gt;
&lt;li&gt;动态、专栏更多是“服务端直接下发富文本节点”&lt;/li&gt;
&lt;li&gt;直播 &lt;code&gt;uemote&lt;/code&gt; 则是一类“整条消息就是表情图”的独立消息&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;而真正麻烦的地方不在于“怎么显示图片”，而在于：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;同样叫 &lt;code&gt;uemote&lt;/code&gt;，不同前缀下的尺寸字段并不遵守同一种语义。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;目前基于 PiliNara 的实现和实际抓包，比较稳妥的策略已经收敛成下面这样：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;room_*&lt;/code&gt;：保留现状，继续按 &lt;code&gt;width/height / DPR&lt;/code&gt; 处理&lt;/li&gt;
&lt;li&gt;&lt;code&gt;official_*&lt;/code&gt;：在当前基础上额外放大一档，避免显示过小&lt;/li&gt;
&lt;li&gt;&lt;code&gt;upower_[...]&lt;/code&gt;：对部分装扮/收藏集表情，不再信返回的 &lt;code&gt;20x20&lt;/code&gt;，而是使用经验源尺寸 &lt;code&gt;162px&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这套方案未必是最终答案，但至少它符合一个事实：&lt;strong&gt;官方客户端自己也没有完全“照接口尺寸渲染”&lt;/strong&gt;。既然目标是和官方保持一致，那前端实现就不能只迷信协议字段，还得尊重实际表现。&lt;/p&gt;
&lt;p&gt;如果后面继续往下深挖，这条链路真正值得做的事不是再写一层更复杂的缩放公式，而是继续积累样本，把不同 &lt;code&gt;emoticon_unique&lt;/code&gt; 家族的行为边界摸清楚。等规则足够稳定之后，再把经验判断收敛成更明确的分类逻辑，这样比一开始就追求“绝对通用解”更现实。&lt;/p&gt;
</content:encoded></item><item><title>修复HyperOS 小窗模式下 Flutter viewPadding 异常的临时兼容方案</title><link>https://blog.170529.xyz/posts/hyperos-flutter-bugfix/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/hyperos-flutter-bugfix/</guid><description>本文介绍了在HyperOS小窗模式下，Flutter viewPadding异常的临时兼容方案，解决界面灰屏和布局错位问题。</description><pubDate>Sun, 14 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image &lt;a href=&quot;https://www.pixiv.net/artworks/137277381&quot;&gt;摸了卢西娅 (@Clear water)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;修复HyperOS 小窗模式下 Flutter viewPadding 异常的临时兼容方案&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;最近在调试 dart_simple_live / PiliPlus 时遇到一个烦人的问题：在澎湃OS（HyperOS）的窗口化／小窗模式下，Flutter 会把 MediaQuery 上报的 &lt;code&gt;viewPadding.top&lt;/code&gt; 给出异常值（例如非常大或不合理的数值），导致界面灰屏、只显示底栏或布局错位。官方 issue 已有相关讨论但修复优先级较低，于是我把社区实践的临时兼容方案记录下来，顺便把注意点写清楚，方便后续在项目里复用。&lt;/p&gt;
&lt;h2&gt;省流版结论（直接上结论）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;问题根源：HyperOS 窗口化模式下 Flutter 的 viewPadding 报告异常（参考 issues: https://github.com/flutter/flutter/issues/164092, https://github.com/flutter/flutter/issues/161086）。&lt;/li&gt;
&lt;li&gt;暂时有效的兼容逻辑：当 &lt;code&gt;mediaQuery.viewPadding.top &amp;gt; 50&lt;/code&gt; 时认为异常，并替换为 fallback padding（例如 &lt;code&gt;top:25 bottom:35&lt;/code&gt;）。&lt;/li&gt;
&lt;li&gt;千万不要把 &lt;code&gt;viewPadding.top == 0&lt;/code&gt; 或 &lt;code&gt;&amp;lt;= 0&lt;/code&gt; 一律视为异常：0 在全屏/沉浸模式下是合法值，会被误判导致全屏出现顶部空白。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;实际场景与踩坑（短版）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;复现：在澎湃OS设备上启用小窗/窗口化模式，观察页面是否变成灰屏或只显示底部（布局异常）。&lt;/li&gt;
&lt;li&gt;我看到的错误做法：把 &lt;code&gt;viewPadding.top &amp;lt;= 0&lt;/code&gt; 也当成异常（初版修复中常见）。结果是：全屏/沉浸模式（&lt;code&gt;viewPadding.top == 0&lt;/code&gt;）会被强制套上回退 padding，从而导致顶部空白。&lt;/li&gt;
&lt;li&gt;正确做法：只把明显异常的大值（&amp;gt; 50）当作 Flutter 在小窗模式下的上报 bug 来处理。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;解决方案（代码 + 说明）&lt;/h2&gt;
&lt;p&gt;把下面的临时兼容逻辑放到你的 &lt;code&gt;main.dart&lt;/code&gt;（或创建 &lt;code&gt;MaterialApp&lt;/code&gt; 的地方），通常放在 &lt;code&gt;MaterialApp&lt;/code&gt; 的 &lt;code&gt;builder&lt;/code&gt; 中最合适：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 在 MaterialApp 的 builder 中加入如下逻辑：
builder: (context, child) {
  // Fix for HyperOS windowed-mode Flutter bug:
  // - Values &amp;gt; 50 indicate the bug (windowed mode on HyperOS)
  // - Values == 0 are valid for fullscreen/immersive mode and must NOT be treated as abnormal
  const fallbackPadding = EdgeInsets.only(top: 25, bottom: 35);
  const maxNormalPadding = 50.0;

  final mediaQueryData = MediaQuery.of(context);
  final hasAbnormalPadding = mediaQueryData.viewPadding.top &amp;gt; maxNormalPadding;

  if (hasAbnormalPadding) {
    return MediaQuery(
      data: mediaQueryData.copyWith(
        viewPadding: fallbackPadding,
        padding: fallbackPadding,
      ),
      child: child!,
    );
  }

  return child!;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;要点说明&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;maxNormalPadding = 50&lt;/code&gt; 是一个经验值（正常状态栏高度通常 20–48dp）；根据设备或需要可微调。&lt;/li&gt;
&lt;li&gt;回退 padding 选用 &lt;code&gt;top:25 bottom:35&lt;/code&gt; 是社区常见值，也可根据 UI 需求调整。&lt;/li&gt;
&lt;li&gt;这个改动只做“临时兼容”；一旦 Flutter 官方修复对应 issue，应当移除或用 feature flag 控制。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;测试建议（必做几项）&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;真机验证（HyperOS）：
&lt;ul&gt;
&lt;li&gt;切换到小窗/窗口化模式，确认页面不再灰屏且布局正常。&lt;/li&gt;
&lt;li&gt;切换到全屏/沉浸式，确认没有被误加顶部回退（无顶部空白）。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;在其他厂商系统（Android 原生、MIUI 等）上回归测试，确保该兼容逻辑不会引入副作用。&lt;/li&gt;
&lt;li&gt;如果可能，记录 &lt;code&gt;MediaQuery.of(context).viewPadding.top&lt;/code&gt; 在不同模式下的数值，方便调参和日志定位。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;关联与参考&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;我在修复PiliPlus 的实践 PR（参考）：https://github.com/Starfallan/PiliPlus/pull/2 和 https://github.com/Starfallan/PiliPlus/pull/4
&lt;ul&gt;
&lt;li&gt;初版 PR 对 &lt;code&gt;&amp;lt;= 0&lt;/code&gt; 也判为异常 → 导致全屏空白&lt;/li&gt;
&lt;li&gt;后续 PR 将判定改为仅 &lt;code&gt;&amp;gt; 50&lt;/code&gt; → 解决了全屏误判问题&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Flutter 官方 issue：
&lt;ul&gt;
&lt;li&gt;https://github.com/flutter/flutter/issues/164092&lt;/li&gt;
&lt;li&gt;https://github.com/flutter/flutter/issues/161086&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;总结（一句话）&lt;/h2&gt;
&lt;p&gt;把 &lt;code&gt;viewPadding.top == 0&lt;/code&gt; 视为合法（全屏/沉浸），只把非常大的值（&amp;gt;50）当作 HyperOS 窗口化模式下的 Flutter 报错并进行回退，这样既能修复小窗灰屏问题，又避免破坏全屏体验。&lt;/p&gt;
</content:encoded></item><item><title>listary-hack</title><link>https://blog.170529.xyz/posts/listary-hack/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/listary-hack/</guid><description>转载一下来自吾爱的Listary逆向Pro教程</description><pubDate>Tue, 26 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Listary Pro逆向教程&lt;/h1&gt;
&lt;p&gt;转载自&lt;a href=&quot;https://www.52pojie.cn/forum.php?mod=viewthread&amp;amp;tid=2025340&amp;amp;page=1&amp;amp;authorid=2247330&quot;&gt;手把手教你破解Listary最新版V6.3.2.88Pro无弹窗~ - 吾爱破解 - 52pojie.cn&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;工具准备&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Listary v6.3.2.88&lt;/strong&gt;&lt;br /&gt;
&lt;a href=&quot;https://www.listary.com/&quot;&gt;官网下载地址&lt;/a&gt;&lt;br /&gt;
下载后正常安装，例如安装到 &lt;code&gt;D:\Program Files\Listary&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;dnSpy v6.1.8&lt;/strong&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/dnSpy/dnSpy/releases/tag/v6.1.8&quot;&gt;下载地址&lt;/a&gt;&lt;br /&gt;
下载 &lt;code&gt;dnSpy-net-win64.zip&lt;/code&gt;，解压到任意目录&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;de4dot v3.2.0.0&lt;/strong&gt;&lt;br /&gt;
&lt;a href=&quot;https://github.com/kant2002/de4dot/releases/tag/v3.2.0&quot;&gt;下载地址&lt;/a&gt;&lt;br /&gt;
下载 &lt;code&gt;de4dot-net8.0-winx64.zip&lt;/code&gt;，解压到任意目录&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;反混淆&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;将安装目录下的 &lt;code&gt;D:\Program Files\Listary\Listary.exe&lt;/code&gt; 拖到 &lt;code&gt;de4dot.exe&lt;/code&gt; 上&lt;/li&gt;
&lt;li&gt;安装目录下会生成 &lt;code&gt;Listary-cleaned.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;重命名文件：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Listary.exe&lt;/code&gt; → &lt;code&gt;Listary0.exe&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Listary-cleaned.exe&lt;/code&gt; → &lt;code&gt;Listary.exe&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;修改代码&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;打开 &lt;code&gt;dnSpy.exe&lt;/code&gt;，将新的 &lt;code&gt;Listary.exe&lt;/code&gt; 拖入程序集资源管理器&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;code&gt;Listary.Core.Pro/LicenseChecker&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;右键 &lt;code&gt;CheckLicense&lt;/code&gt; 方法，选择“编辑方法”&lt;/li&gt;
&lt;li&gt;替换方法内容，点击右下角“编译”
修改代码&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;public static bool CheckLicense(string email, string license)
{
    // 替换掉原有的，直接返回true
    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;code&gt;Listary.Core.Pro/LicenseProPageViewModel&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;右键 &lt;code&gt;CheckLicense()&lt;/code&gt; 方法，选择“编辑方法”&lt;/li&gt;
&lt;li&gt;删除第15行，点击右下角“编译”&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;private void CheckLicense()
{
        // 删除下面这行
-       this.dt();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;修改 &lt;code&gt;Listary.Core.Pro/ProService&lt;/code&gt;（共五处）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;右键 &lt;code&gt;ProService&lt;/code&gt;，选择“编辑类”&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;修改1&lt;/strong&gt;：删除指定的两行&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    public bool IsPro
    {
        [CompilerGenerated]
        get
        {
            return this.bool_0;
        }
        [CompilerGenerated]
        private set
        {
            if (this.bool_0 == value)
            {
                return;
            }
            this.bool_0 = value;
-           this.&amp;lt;&amp;gt;OnPropertyChanged(Class373.LicenseEmail);
-           this.&amp;lt;&amp;gt;OnPropertyChanged(Class373.IsPro);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改2&lt;/strong&gt;：跳到210行附近，删除指定内容,如图删除211-221行内容&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/08/20250826180105799.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改3&lt;/strong&gt;：将 &lt;code&gt;ActivateNewLicense&lt;/code&gt; 和 &lt;code&gt;method_2&lt;/code&gt; 函数内容改为&lt;code&gt;return Task.FromResult(new ProService.ActivationResult());&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;以下为单个示例：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Token: 0x06001177 RID: 4471 RVA: 0x0003A6D0 File Offset: 0x000388D0
public Task&amp;lt;ProService.ActivationResult&amp;gt; ActivateNewLicense(string email, string licenseKey)
{
    ProService.Struct84 @struct;
    @struct.asyncTaskMethodBuilder_0 = AsyncTaskMethodBuilder&amp;lt;ProService.-
    ActivationResult&amp;gt;.Create();
    @struct.proService_0 = this;
    @struct.string_0 = email;
    @struct.string_1 = licenseKey;
    @struct.int_0 = -1;
    @struct.asyncTaskMethodBuilder_0.Start&amp;lt;ProService.Struct84&amp;gt;(ref @struct);
    return @struct.asyncTaskMethodBuilder_0.Task;
    return Task.FromResult(new ProService.ActivationResult());
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改4&lt;/strong&gt;：将 &lt;code&gt;DeactivateLicense&lt;/code&gt;、&lt;code&gt;ScheduleAutoCheck&lt;/code&gt;、&lt;code&gt;DebugTestActivateLicense&lt;/code&gt;、&lt;code&gt;method_3&lt;/code&gt; 函数内容改为 &lt;code&gt;return Task.CompletedTask;&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  // Token: 0x06001178 RID: 4472 RVA: 0x0003A724 File Offset: 0x00038924
  public Task DeactivateLicense()
  {
-       ProService.Struct85 @struct;
-       @struct.asyncTaskMethodBuilder_0 = AsyncTaskMethodBuilder.Create();
-       @struct.proService_0 = this;
-       @struct.int_0 = -1;
-       @struct.asyncTaskMethodBuilder_0.Start&amp;lt;ProService.Struct85&amp;gt;(ref @struct);
-       return @struct.asyncTaskMethodBuilder_0.Task;
+       return Task.CompletedTask;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;修改5&lt;/strong&gt;：修改完上述函数后，可能会遇到编译报错的情况。这时候请删除13行和224行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//第13行
- using NLog;
//第224行
- // Token: 0x04000951 RID: 2385
- private static readonly ILogger ilogger_0 = LogManager.GetCurrentClassLogger();
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;点击右下角“编译”&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;点击左上角“文件”，选择“保存模块”，确定&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;运行&lt;/h2&gt;
&lt;p&gt;将修改的&lt;code&gt;Listary.exe&lt;/code&gt;覆盖原有的安装目录下的 &lt;code&gt;Listary.exe&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;双击 &lt;code&gt;Listary.exe&lt;/code&gt;，完成所有步骤，Enjoy 🎉&lt;/p&gt;
</content:encoded></item><item><title>Cloudflare R2 图像压缩</title><link>https://blog.170529.xyz/posts/compress-r2-image/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/compress-r2-image/</guid><description>介绍一款 Cloudflare R2 图像压缩工具，支持多种格式图片批量压缩至 AVIF 格式</description><pubDate>Thu, 31 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image: &lt;a href=&quot;https://www.pixiv.net/artworks/126959381&quot;&gt;「天外大合唱」耀嘉音x知更鸟(@若宫晴月)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Cloudflare R2 图像压缩&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;博客之前上传的图片都是直接上传到基于 Cloudflare R2 的图床的。并且早期都是直接上传的原图。随着时间的推移，发现原图的体积实在是太大了。导致网站加载速度变慢。所以需要对图片进行压缩。&lt;/p&gt;
&lt;h2&gt;工具介绍&lt;/h2&gt;
&lt;p&gt;为了实现对图片的压缩，在互联网上搜了下，看到了这样的一个帖子&lt;a href=&quot;https://www.v2ex.com/t/1128967&quot;&gt;开源一个压缩 Cloudfare R2 图片的工具&lt;/a&gt;。帖子中介绍了他开发的一个工具，支持将多种格式图片进行压缩。实现了全自动图片的下载、压缩、上传到 Cloudflare R2 的功能。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;zhangchenchen/reduce_cloudfare_image&quot;}&lt;/p&gt;
&lt;p&gt;感谢原作者的贡献。&lt;/p&gt;
&lt;p&gt;但是他开发的工具是将各种图片保持原格式分类进行压缩的，而我希望能够将所有图片统一压缩为 AVIF 格式。于是我对他的工具进行了改造，改为将图片转换为 AVIF 格式。并且在压缩过后，支持删除原图。&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Starfallan/reduce_cloudfare_image&quot;}&lt;/p&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;p&gt;具体的使用方法可以参考仓库中的Readme，这里就不在赘述。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;希望这个工具能够帮助到需要压缩 Cloudflare R2 图片的朋友们。如果有任何问题或者建议，欢迎在仓库中提出issue或者pr。实际测试了下，在压缩质量为85的时候，整体压缩率在85%左右。效果还是非常不错的。&lt;/p&gt;
</content:encoded></item><item><title>Pillow的AVIF 支持</title><link>https://blog.170529.xyz/posts/pillow-avif/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/pillow-avif/</guid><description>在折腾 Pillow 的过程中，艰难搞定了 AVIF 格式的支持</description><pubDate>Thu, 31 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image: &lt;a href=&quot;https://www.pixiv.net/artworks/132791587&quot;&gt;Summertime🏝️(@Remmui)&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;Pillow AVIF 支持&lt;/h1&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;之前在使用 Pillow 处理图像时，发现 AVIF 格式的支持并不完善。在查阅最新的 Pillow 文档后，发现Pillow在11.3.0版本中终于在已经内置了对 AVIF 格式的支持。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pillow 11.3.0 has now been released with AVIF support in the wheels.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;但是在实际使用中，仍然遇到了一些问题。经过一番折腾，终于搞定了 AVIF 格式的支持。&lt;/p&gt;
&lt;p&gt;省流版：有问题就尝试手动安装&lt;code&gt;libavif&lt;/code&gt;库。再从pypi源安装Pillow。&lt;/p&gt;
&lt;h2&gt;实际使用&lt;/h2&gt;
&lt;p&gt;:::tip
因为我在前面的文章已经介绍过了我已经将Python切换到了Pixi进行管理。&lt;/p&gt;
&lt;p&gt;所以以下命令，均为Pixi的命令。可以自行替换为pip命令。
:::&lt;/p&gt;
&lt;h3&gt;踩坑&lt;/h3&gt;
&lt;p&gt;首先我直接使用&lt;code&gt;pixi add pillow&lt;/code&gt;安装了Pillow，结果发现 AVIF 格式的支持并没有生效。运行代码直接报错。于是使用检查代码查看Pillow的信息。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi run python -m PIL --report
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;报告内容显示：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi run python -m PIL --report
--------------------------------------------------------------------                                              
Pillow 11.3.0
Python 3.12.11 | packaged by conda-forge | (main, Jun  4 2025, 14:29:09) [MSC v.1943 64 bit (AMD64)]
--------------------------------------------------------------------
Python executable is D:\Code\Python\reduce_cloudfare_image\.pixi\envs\default\python.EXE
System Python files loaded from D:\Code\Python\reduce_cloudfare_image\.pixi\envs\default
--------------------------------------------------------------------
Python Pillow modules loaded from D:\Code\Python\reduce_cloudfare_image\.pixi\envs\default\Lib\site-packages\PIL
Binary Pillow modules loaded from D:\Code\Python\reduce_cloudfare_image\.pixi\envs\default\Lib\site-packages\PIL
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 11.3.0
--- TKINTER support ok, loaded 8.6
--- FREETYPE2 support ok, loaded 2.13.3
--- LITTLECMS2 support ok, loaded 2.17
--- WEBP support ok, loaded 1.6.0
*** AVIF support not installed
--- JPEG support ok, compiled for libjpeg-turbo 3.1.0
--- OPENJPEG (JPEG2000) support ok, loaded 2.5.3
--- ZLIB (PNG/ZIP) support ok, loaded 1.3.1
--- LIBTIFF support ok, loaded 4.7.0
*** RAQM (Bidirectional Text) support not installed
*** LIBIMAGEQUANT (Quantization method) support not installed
*** XCB (X protocol) support not installed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看到 AVIF 的支持并没有安装。&lt;/p&gt;
&lt;p&gt;又因为在默认情况下，Pixi安装是从Conda Forge源获取的。我于是猜测可能是因为Conda Forge源的Pillow的wheel包没有包含 AVIF 的支持。&lt;/p&gt;
&lt;p&gt;于是使用&lt;code&gt;pixi add --pypi pillow&lt;/code&gt;命令从PyPI源安装Pillow。
发现还是不行,这就有点奇怪了。官方明确表示11.3.0版本已经内置了 AVIF 的支持。&lt;/p&gt;
&lt;h3&gt;解决方案&lt;/h3&gt;
&lt;p&gt;经过一番查找，发现是因为Pillow的AVIF支持依赖于&lt;code&gt;libavif&lt;/code&gt;库，而这个库有可能没有被正确安装。于是建议手动安装&lt;code&gt;libavif&lt;/code&gt;库。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi add libavif
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，再次运行报告命令，检查 AVIF 支持是否已安装。
就已经ok了。&lt;/p&gt;
&lt;p&gt;注意，测试了下。从conda-forge源安装的pillow在手动安装libavif后，还是无法工作的。但是pypi源安装的pillow可以正常工作。&lt;/p&gt;
&lt;p&gt;有点奇怪，但至少现在可以正常使用 AVIF 格式了。&lt;s&gt;&lt;strong&gt;开摆&lt;/strong&gt;&lt;/s&gt;。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;--- AVIF support ok, loaded 1.3.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;在使用 Pillow 处理图像时，AVIF 格式的支持虽然在11.3.0版本中已经内置。但从Conda Forge源安装的Pillow可能无法正常工作。建议从PyPI源安装，并手动安装&lt;code&gt;libavif&lt;/code&gt;库以确保 AVIF 格式的支持。&lt;/p&gt;
</content:encoded></item><item><title>远离Notify For系列软件</title><link>https://blog.170529.xyz/posts/away-notifyfor-series/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/away-notifyfor-series/</guid><description>结合自己的使用体验聊聊问题</description><pubDate>Sat, 19 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://space.bilibili.com/7198052/dynamic&quot;&gt;鸦居&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;相信日常有使用过小米手环的朋友，而且之前玩过表盘自定义的朋友都知道Notify For系列软件。例如Notify For Mi Band、Notify For Amazfit等。
这些软件的主要功能是提供手环的通知推送、表盘自定义等功能。当初玩小米手环6的时候，看中了通知的自定义推送和闹钟的自定义唤醒功能，所以就下载了Notify For Mi Band。并购买了Pro版本。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/07/20250719120959264.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;问题&lt;/h2&gt;
&lt;p&gt;只可惜其实Notify For Mi Band看似功能很多，其实大多只是界面上宣传的效果很强，在实际使用中只是比当初本身丑的要命的Zeep Life好看一些。什么闹钟的智能唤醒，自定义提醒，很多功能都是在吹的很强大。实际使用中并没有什么用。&lt;/p&gt;
&lt;p&gt;并且第三方本身app都挺难用的，需要额外的折腾成本已经大于了体验的提升。需要额外的授权码，需要禁用官方app。&lt;/p&gt;
&lt;p&gt;并且在更新到小米运动健康后，可以说对比起来Notify For Mi Band的界面不能说经典吧，只能说是充满历史的厚重感。以下是界面对比，Notify的界面已经是沿用非常多年了。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/07/20250719121931679.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;购买体验&lt;/h2&gt;
&lt;p&gt;此外，重要的是，Notify For Mi Band的购买体验也并不理想。当初在购买Pro订阅时，承诺一次购买，终身解锁功能。但事实上官方总想着出其他的付费方式。
当初还卖过Pro之外的2023订阅，说是要覆盖开发成本。我只能说是玩不起。
&lt;img src=&quot;https://r2.170529.xyz/2025/07/20250719123936297.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;然后，由于小米手环7Pro不再使用原来的华米的Zeep OS，软件需要重新适配，官方直接就放弃了原来的Notify For Mi Band的更新。直接新开了一个Notify For Xiaomi的app。并且在购买时，直接就不支持原来的Pro订阅了。宣布二者是独立的产品，&lt;strong&gt;订阅不通用&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;如果是之前的Notify For Amazfit和Notify For MiBand这两个软件的Pro订阅不互通我是可以理解的，毕竟是两个不同的产品。但是Notify For Xiaomi和Notify For Mi Band是同一个产品线的，直接就不支持Pro订阅了。只能说是非常不合理。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/07/20250719135233072.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/07/20250719135142736.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;后续&lt;/h2&gt;
&lt;p&gt;总的来说，希望这些碎碎念能够让大家对Notify For系列软件有一个更清晰的认识。建议不要使用和购买。表盘自定义可以用米坛的表盘自定义工具（虽然捐赠后，广告还是有点多），其他官方app已经足够使用了。&lt;/p&gt;
</content:encoded></item><item><title>将Vscode添加到Win11右键菜单</title><link>https://blog.170529.xyz/posts/add-vscode-win11-context/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/add-vscode-win11-context/</guid><description>分享下如何将Vscode添加到Windows 11的右键菜单，方便快速打开文件夹或文件。</description><pubDate>Fri, 13 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cover image：&lt;a href=&quot;https://space.bilibili.com/31923261/dynamic&quot;&gt;鱼鹅BABA&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;VScode相信大家都用的，但是由于当初安装的时候没有勾选“将VScode添加到右键菜单”，导致现在想要在文件夹或文件上右键打开VScode时没有这个选项。今天就来分享一下如何将VScode添加到Windows 11的右键菜单。&lt;/p&gt;
&lt;p&gt;目前网上的很多办法都是通过修改注册表来实现的，但是这种方法有点麻烦，而且只能添加到Win10的旧版右键菜单中。今天这个方法可以实现直接添加到Win11的新版右键菜单中。&lt;/p&gt;
&lt;h2&gt;方法来源&lt;/h2&gt;
&lt;p&gt;:::note
以下方法发现于官方 Issue&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/microsoft/vscode/issues/204696&quot;&gt;[Meta] Enable windows 11 context menu by default in Stable&lt;/a&gt;
:::&lt;/p&gt;
&lt;p&gt;BartoszRojek 在评论区分享了其开发的程序 &lt;a href=&quot;https://github.com/BartoszRojek/CodeModernExplorerMenu&quot;&gt;CodeModernExplorerMenu&lt;/a&gt;，用以一键添加在VScode中打开到Win11右键菜单，同时支持Insider版本。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250613234013443.avif&quot; alt=&quot;20250613234013443.png&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;具体使用方法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;下载 &lt;a href=&quot;https://github.com/BartoszRojek/CodeModernExplorerMenu/releases&quot;&gt;CodeModernExplorerMenu&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;运行对应版本的msi文件。（可能需要关闭杀毒软件）&lt;/li&gt;
&lt;li&gt;完成后，重启 Windows资源管理器。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这样，你就可以在 Win11 的右键菜单中看到“在 Visual Studio Code 中打开”选项了。
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250613234405328.avif&quot; alt=&quot;20250613234405328.png&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;补充&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;如果你的Vscode没有安装在默认路径（ C:\Program Files\Microsoft VS Code ），你需要先 Fork 该仓库，Fork 仓库后，编辑 &lt;code&gt;src/explorer_command.cc&lt;/code&gt; 文件中的路径，提交更改后会自动触发构建。在 &quot;Actions&quot; 标签页中打开最新的构建，你可以在 Artifacts 里找到生成的 msi 文件。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; if (!std::filesystem::exists(module_path)) {
    std::filesystem::path fallback_path = std::filesystem::path(&quot;C:\\Program Files&quot;) / DIR_NAME / EXE_NAME;
    if (std::filesystem::exists(fallback_path)) {
        module_path = fallback_path;
    } else {
        return E_FAIL;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;如果你需要自定义安装路径，一样是 Fork 项目后，在 &lt;code&gt;msi/RunOnInstall.ps1&lt;/code&gt; 文件中修改对应文本。之后同样是等待 Actions 重新打包生成 msi 文件即可。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ProductName = &apos;Code Modern Explorer Menu&apos;
$ProductPath = &quot;$Env:LOCALAPPDATA\Programs\$ProductName&quot;
$MenuName = &quot;Open with Code&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以上的内容均来自于之前那个&lt;a href=&quot;https://github.com/microsoft/vscode/issues/204696&quot;&gt;Issue&lt;/a&gt;中软件作者的回答，经过翻译和整理。&lt;/p&gt;
&lt;h2&gt;结语&lt;/h2&gt;
&lt;p&gt;Vscode右键菜单打开貌似还是一笔烂账。但是通过以上的方法，不管是之前跟我一样忘了在安装的时候勾选“将Vscode添加到右键菜单”，还是莫名其妙消失了的用户，都可以方便地将其添加到右键菜单中，提高我们的开发效率。&lt;/p&gt;
</content:encoded></item><item><title>终端挑选日记</title><link>https://blog.170529.xyz/posts/terminal-choose-diary/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/terminal-choose-diary/</guid><description>又到了折腾的时候，选用一些新的终端试试效果。</description><pubDate>Wed, 11 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Cover image：&lt;a href=&quot;https://www.pixiv.net/artworks/131343426&quot;&gt;Fre&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;之前用其实折腾过一段时间了，因为主要开发环境在Windows上，所以一直用的是Windows Terminal。为了增强，使用了Powershell7和Oh My Posh来美化终端。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611160001678.avif&quot; alt=&quot;20250611160001678.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;其实原本的效果已经很不错了，&lt;s&gt;但我这个人就是喜欢折腾&lt;/s&gt;，想尝试一些新的东西，看看能不能带来一些新体验。&lt;/p&gt;
&lt;h2&gt;选择终端&lt;/h2&gt;
&lt;h3&gt;1.Warp&lt;/h3&gt;
&lt;p&gt;Warp还是比较火的一个终端，主要新意对我来说应该是他的AI功能。我的原终端使用copilot-cli也是类似的，但是我觉得copilot-cli的使用步骤比较繁琐，需要先输入命令：gh copilot suggest &quot;xxx&quot;，然后再选择命令。&lt;/p&gt;
&lt;p&gt;Warp的AI功能可以直接在终端中输入命令，自动补全和建议命令，使用起来更方便。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611160546196.avif&quot; alt=&quot;20250611160546196.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;试了一下，觉得整体界面还是挺不错的。但是发现了几个问题。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;需要账号登录，才能使用AI功能。&lt;/li&gt;
&lt;li&gt;Shell Promt的显示有问题
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611160756152.avif&quot; alt=&quot;20250611160756152.png&quot; /&gt;
显示图标有割裂，需要调整字体和字号来解决。比较隔应，因为已经习惯于这个字号和字体了。&lt;/li&gt;
&lt;li&gt;AI功能是收费的，免费版每月只能使用150次。而且不能使用自定义的AI模型，只能使用Warp提供的模型。目前来看Warp团队也不打算提供自定义模型的功能。150次属于是不太够用。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;WaveTerm&lt;/h3&gt;
&lt;p&gt;这个说实话，预期还蛮高。结果下载下来开幕雷击：默认Shell选择了Windows Powershell而不是PowerShell 7。&lt;/p&gt;
&lt;p&gt;想要设置一下默认终端，发现没有GUI设置界面，只能通过配置文件来手动修改。
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611162120372.avif&quot; alt=&quot;20250611162120372.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;设置完更是震惊：设置默认终端后&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;term:localshellpath&quot;: &quot;C:\\Program Files\\PowerShell\\7-preview\\pwsh.exe&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;打开显示找不到Oh My Posh的配置文件。
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611165136297.avif&quot; alt=&quot;20250611165136297.png&quot; /&gt;&lt;/p&gt;
&lt;p&gt;说实话，给我震惊到了。就目前这个程度，属实不够好用。我原本是冲着可以自定义AI模型和自定义主题来的。结果现在连默认终端都不能设置好。那还是算了吧。并且他的AI也只是内置一个对话窗口而已，并没有集成到终端中。&lt;/p&gt;
&lt;h3&gt;WezTerm&lt;/h3&gt;
&lt;p&gt;WezTerm是一个跨平台的终端模拟器，支持GPU加速，性能非常好。它的配置文件是Lua脚本，可以自定义很多功能。折腾到这里，已开摆了。没有什么额外的功能，类似于目前的Windows Terminal，于是没有下载。&lt;/p&gt;
&lt;h2&gt;结论&lt;/h2&gt;
&lt;p&gt;原本换终端的打算就是想要改进目前的AI体验和补全的体验。因为目前终端的补全，我觉得效果都比较差。我理想中应该是类似于IDE那样的补全形式。我看到的方法是通过Amazon Q和Fig来实现的，但是Windows上使用不了。只能希望有更好的终端出现或者Amazon Q能出原生的Windows版本。
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250611170532761.avif&quot; alt=&quot;20250611170532761.png&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>Fuck Pixi</title><link>https://blog.170529.xyz/posts/fuck-pixi/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/fuck-pixi/</guid><description>折腾了3个小时，结果你告诉我Pixi不支持在Global环境下使用Pypi！</description><pubDate>Wed, 04 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image &lt;a href=&quot;https://www.pixiv.net/artworks/82924488&quot;&gt;冷蝉&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Fuck!!!&lt;/strong&gt;
:::tip
温馨提示，本文无任何价值，纯纯输出情绪
:::&lt;/p&gt;
&lt;p&gt;从11点打算随手装个thefuck在Global环境下&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi global add --environment python3-12 thefuck
Error:   × Failed to determine virtual packages for environment python3-12
  ╰─▶ Cannot solve the request because of: No candidates were found for thefuck *.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很好，Conda下没有，那就Pypi吧&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi global add --environment python3-12 thefuck --pypi
error: unexpected argument &apos;--pypi&apos; found

  tip: a similar argument exists: &apos;--pypi-keyring-provider&apos;

Usage: pixi.exe global add --environment &amp;lt;ENVIRONMENT&amp;gt; --pypi-keyring-provider &amp;lt;PYPI_KEYRING_PROVIDER&amp;gt; &amp;lt;PACKAGE&amp;gt;...

For more information, try &apos;--help&apos;.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;报错，提示要使用&lt;code&gt;--pypi-keyring-provider&lt;/code&gt;参数。那就继续研究吧。找到keyring章节，发现要配置keyring。&lt;/p&gt;
&lt;p&gt;虽然对于我这种Python小白来说，还没用过keyring，但还是试着配置了一下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi global install keyring
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;keyring set https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple starfallen
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;结果报错，提示找不到&lt;code&gt;keyring&lt;/code&gt;命令。我真的是艹了。试了各种方法，结果都不行。网上唯一有关的issue上只说了&lt;/p&gt;
&lt;p&gt;With no Pixi changes, this now works. Some dependency of keyring pushed a broken version I guess.&lt;/p&gt;
&lt;p&gt;在没有 Pixi 更改的情况下，这现在可以正常工作。我想是 keyring 的某个依赖推送了一个损坏的版本。&lt;/p&gt;
&lt;p&gt;这就是说了没说。就这样一直尝试配置Keyring，尝试了换源，各种方法重装。反正明明对外暴露了&lt;code&gt;keyring&lt;/code&gt;命令，结果就是找不到。&lt;/p&gt;
&lt;p&gt;最后，又去找issue。我看看别人在Global上是如何安装pypi dependencies的。&lt;/p&gt;
&lt;p&gt;结果就看到了&lt;a href=&quot;https://github.com/prefix-dev/pixi/issues/2261&quot;&gt;Allow pypi dependencies in global envs&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250609172636492.avif&quot; alt=&quot;Issue&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这个issue，里面说了Pixia暂不支持在Global环境下使用Pypi。这一个不支持，就拖到了25年6月。还不支持！&lt;/p&gt;
&lt;p&gt;希望支持的可以到对应Issue上点个赞，激励Pixi团队。&lt;/p&gt;
&lt;p&gt;So，这几个小时就是纯纯的浪费。希望大家引以为戒，同时也希望Pixi能早日支持Global环境下的Pypi依赖。&lt;/p&gt;
&lt;p&gt;最后，有没有人能告诉我，这个Keyring到底怎么配？清华源就没提过要keyring这件事。项目内pixi安装pypi依赖也没提过要配置keyring。来个大佬，救一救！&lt;/p&gt;
</content:encoded></item><item><title>Pixi使用笔记</title><link>https://blog.170529.xyz/posts/pixi-usage-notes/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/pixi-usage-notes/</guid><description>在网上看到了Pixi的推荐，写一篇关于迁移和入门的笔记</description><pubDate>Tue, 03 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image &lt;a href=&quot;https://www.pixiv.net/artworks/114287760&quot;&gt;ASK&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Pixi使用笔记&lt;/h2&gt;
&lt;h3&gt;1. 为何选择Pixi&lt;/h3&gt;
&lt;p&gt;以下是官网介绍：&lt;/p&gt;
&lt;p&gt;Pixi is a &lt;strong&gt;fast, modern, and reproducible&lt;/strong&gt; package management tool for developers of all backgrounds.&lt;/p&gt;
&lt;h4&gt;1.1 性能&lt;/h4&gt;
&lt;p&gt;Pixi 集成了UV，是目前最为强大的Conda包管理工具。比Conda快10倍以上，在Python方面，UV更是比PIP快的不知道多少倍。像我一样厌烦了Conda和pip那蜗牛般速度的用户，Pixi是一个不错的选择。
如图
&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250603190532875.avif&quot; alt=&quot;Pixi性能对比&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;1.2 隔离环境&lt;/h4&gt;
&lt;p&gt;pixi 可以为常用的 CLI 工具全局安装软件包，例如 git 、 bat 、 rg （ripgrep）、 bash 等。每个全局安装的工具都生活在其自己的隔离环境中，因此没有损坏“基础环境”的风险。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ pixi global install git bash ripgrep bat
✔ Installed package bash 5.2.21 h15d410d_0 from conda-forge
✔ Installed package ripgrep 14.1.0 h5ef7bb8_0 from conda-forge
✔ Installed package bat 0.24.0 h5ef7bb8_0 from conda-forge
  These executables are now globally available:
   -  bash
   -  bashbug
   -  rg
   -  bat
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;1.3 不再需要激活&lt;/h4&gt;
&lt;p&gt;相信每个人都被Conda的激活和取消激活搞得头疼不已。Pixi没有“conda activate” / “conda deactivate”功能。相反，项目应该在一个 pixi.toml 中定义所有配置，并在项目文件夹中运行 pixi shell 或 pixi run 。Pixi 会自动发现环境，安装并激活它。再也不用担心conda激活污染终端的配置文件了。&lt;/p&gt;
&lt;p&gt;现在都忘不了powershell折腾了conda activate搞了半天，成功后成功使powershell的启动慢到需要5,6秒。&lt;/p&gt;
&lt;h4&gt;1.4 兼容性&lt;/h4&gt;
&lt;p&gt;Pixi可以一行命令实现从Conda迁移到Pixi&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi init --import ./environment.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2. 安装Pixi&lt;/h3&gt;
&lt;p&gt;Pixi的安装非常简单&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fsSL https://pixi.sh/install.sh | bash
# or on Windows PowerShell
iwr -useb https://pixi.sh/install.ps1 | iex
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3. Pixi入门&lt;/h3&gt;
&lt;p&gt;Pixi的入门非常简单，和Conda类似。Pixi使用&lt;code&gt;pixi&lt;/code&gt;命令来管理包和环境。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;初始化&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//新建hello-world工作空间
//也可以在当前目录直接Pixi init就行

pixi init hello-world
cd hello-world
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加依赖:&lt;/strong&gt;
// 添加Python 3.12基础环境&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi add python=3.12
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加依赖:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi add cowpy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果对应包不在conda-forge中，可以使用pypi源&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;``` bash
pixi add cowpy --pypi
```
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;直接运行:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi run python hello.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;也可以使用pixi shell实现类似于conda activate后的效果&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi shell

python hello.py
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;添加任务:&lt;/strong&gt;
Pixi有独有的任务管理功能，可以方便地添加和运行任务。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi task add start python hello.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;随后可以直接运行任务&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi run start
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;✨ Pixi task (start): python hello.py
 __________________
 &amp;lt; Hello Pixi fans! &amp;gt;
 ------------------
      \   ^__^
       \  (oo)\_______
          (__)\       )\/\
            ||----w |
            ||     ||
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.Pixi的自定义配置&lt;/h3&gt;
&lt;p&gt;以下配置为我个人的使用习惯，参考了官网的文档。&lt;a href=&quot;https://pixi.sh/latest/reference/pixi_configuration/#configuration-options&quot;&gt;Pixi Configuration&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;4.1 自定义Pixi存储位置&lt;/h4&gt;
&lt;p&gt;Pixi默认的存储位置在C盘个人用户目录下的&lt;code&gt;.pixi&lt;/code&gt;文件夹中，可以通过设置环境变量&lt;code&gt;PIXI_HOME&lt;/code&gt;来修改。&lt;/p&gt;
&lt;p&gt;我们可以使用&lt;code&gt;pixi info&lt;/code&gt;命令查看当前的Pixi配置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/06/20250603194351032.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;4.2 修改缓存位置&lt;/h4&gt;
&lt;p&gt;经过上面的配置后，Pixi Global环境会使用新的存储位置，如上图，但是缓存位置还是在C盘&lt;/p&gt;
&lt;p&gt;Pixi的缓存默认位置在C盘的&lt;code&gt;%LOCALAPPDATA%\rattler&lt;/code&gt;文件夹中，可以通过设置环境变量&lt;code&gt;PIXI_CACHE&lt;/code&gt;来修改。&lt;/p&gt;
&lt;h4&gt;4.3 新建Global环境&lt;/h4&gt;
&lt;p&gt;不得不说，拥有一个独立的Global环境是非常有必要的。我们可以使用以下命令来创建一个新的Global环境：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi global install python=3.12 --environment python3-12 --expose python=python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上面的命令会创建一个名为&lt;code&gt;python3-12&lt;/code&gt;的全局环境，并将Python 3.12暴露为&lt;code&gt;python&lt;/code&gt;命令。这样的话，用户可以在任何地方使用&lt;code&gt;python&lt;/code&gt;命令，而不必担心环境问题。&lt;/p&gt;
&lt;p&gt;此外，如果你想要有多个版本的Python，可以使用以下命令来安装不同版本的Python：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pixi global install python=3.10 --environment python3-10 --expose python3.10=python
pixi global install python=3.11 --environment python3-11 --expose python3.11=python
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在运行上述命令后，我们的系统上有 python3.10 和 python3.11 可用。&lt;/p&gt;
&lt;h4&gt;4.4 自定义源&lt;/h4&gt;
&lt;p&gt;可以更改每个单独项目的源，也可以更改全局源。
我这里是更改了全局设置&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[pypi-config]
# Main index url 主镜像设置为清华源
index-url = &quot;https://mirrors.tuna.tsinghua.edu.cn/pypi/web/simple&quot;
# list of additional urls
extra-index-urls = [&quot;https://pypi.org/simple&quot;]


[mirrors]
# 重定向所有对conda-forge的请求到清华源
&quot;https://conda.anaconda.org/conda-forge&quot; = [&quot;https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/&quot;]
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>给Fuwari博客添加密码保护功能（test123）</title><link>https://blog.170529.xyz/posts/password-test/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/password-test/</guid><description>这是一篇受密码保护的测试文章，密码是 test123</description><pubDate>Thu, 29 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;🔒 这是一篇受密码保护的文章&lt;/h1&gt;
&lt;p&gt;恭喜！如果你能看到这段内容，说明密码加密功能工作正常。&lt;/p&gt;
&lt;h2&gt;功能特性&lt;/h2&gt;
&lt;p&gt;这个密码保护系统具有以下特性：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;静态部署友好&lt;/strong&gt;: 完全基于前端加密，不需要后端服务&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;AES-256-CBC 加密&lt;/strong&gt;: 使用现代加密算法保护内容&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;用户友好&lt;/strong&gt;: 简洁的密码输入界面&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;响应式设计&lt;/strong&gt;: 适配各种设备屏幕&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;主题兼容&lt;/strong&gt;: 完美适配明暗主题切换&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;使用方法&lt;/h2&gt;
&lt;p&gt;要创建一篇受密码保护的文章，只需要在文章的 frontmatter 中添加 &lt;code&gt;password&lt;/code&gt; 字段：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: 我的私密文章
published: 2025-05-29
password: yourpassword
---

这里是受保护的内容...
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;测试密码&lt;/h2&gt;
&lt;p&gt;本文的密码是：&lt;code&gt;test123&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;安全说明&lt;/h2&gt;
&lt;p&gt;⚠️ &lt;strong&gt;重要提醒&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;虽然内容经过加密，但这只是基础的内容保护&lt;/li&gt;
&lt;li&gt;不建议用于存储高度敏感的信息&lt;/li&gt;
&lt;li&gt;密码会存储在生成的静态文件中，请选择合适的强度&lt;/li&gt;
&lt;li&gt;这个方案主要适用于博客中需要简单访问控制的场景&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;技术实现&lt;/h2&gt;
&lt;p&gt;该功能基于以下技术栈实现：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Web Crypto API&lt;/strong&gt;: 用于浏览器端的加密解密操作&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AES-CBC 模式&lt;/strong&gt;: 提供对称加密功能&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base64 编码&lt;/strong&gt;: 用于存储加密后的内容&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Astro Slots&lt;/strong&gt;: 用于获取需要加密的HTML内容&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;不用管上面的，其实就是参考大佬的&lt;a href=&quot;https://blog.mmf.moe/post/blog-02-password/&quot;&gt;Astro❤️Password&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;自定义样式&lt;/h2&gt;
&lt;p&gt;密码输入界面完全支持自定义样式，与博客主题保持一致的视觉效果。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;感谢使用Fuwari博客的密码保护功能！🎉&lt;/p&gt;
</content:encoded></item><item><title>记录Vercel的天坑测试环境</title><link>https://blog.170529.xyz/posts/vercel-testpit-diary/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/vercel-testpit-diary/</guid><description>记录Vercel的天坑测试环境，逆天Vercel的Preview环境测试完全不可靠</description><pubDate>Thu, 29 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover Image &lt;a href=&quot;https://space.bilibili.com/7323950/dynamic&quot;&gt;Coria&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;前言&lt;/h2&gt;
&lt;p&gt;因为Fuwari没有自带加密博客的功能，但是觉得这个功能还是挺有用的，所以就想着自己实现一个。
于是就开始了Fuwari的加密博客功能的开发。在网上找到了对应的资料，并尝试着手实现。这就是&lt;a href=&quot;https://blog.170529.xyz/posts/password-test/&quot;&gt;给Fuwari添加加密博客&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;自然，在开发新功能的时候，肯定是创建了一个测试分支，来进行测试和开发。而Vercel在检测到分支有更新时，会自动部署到预览环境。如图
&lt;img src=&quot;https://r2.170529.xyz/2025/05/20250529230208211.avif&quot; alt=&quot;Vercel Preview Environment&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Vercel的坑爹Preview环境&lt;/h2&gt;
&lt;p&gt;因为本地测试没有问题，所以就想着在Vercel的预览环境上进行测试。毕竟这也不敢直接merge到主分支上。于是就开始了Vercel的预览环境测试。&lt;/p&gt;
&lt;p&gt;自信满满，直接打开velcel，选择对应的deployment，点击预览环境的链接，发现加密文章的浏览非常正常。本来想着下班收工，可以直接合并到主分支，部署到生产环境了。&lt;/p&gt;
&lt;p&gt;结果当我访问其他非加密文章时，发现页面直接报500了。
报错&lt;code&gt;Code: MIDDLEWARE_INVOCATION_FAILED&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/05/20250529230823351.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;这下可好了，开始怀疑是不是要开启SSR（服务端渲染）还是哪里配置有问题。&lt;/p&gt;
&lt;p&gt;然后就开始查后面。一开始查Vercel对这个问题的说明，发现没啥帮助，后面又看到Astro文档里说要添加Vercel适配器。就开始添加适配器。（一开始没加过，也是正常跑）&lt;/p&gt;
&lt;p&gt;安装完适配器，发现还是一模一样的问题。继续查文档，发现Astro官网关于适配器的文档里有设置edgemiddleware的设置项，便又是添加对应的配置。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://r2.170529.xyz/2025/05/20250529231258182.avif&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p&gt;添加完配置后，重新部署，还是一样的问题。500错误。
这下我就彻底懵了。明明本地测试没问题，为什么Vercel的预览环境就不行了？难道是Vercel的Preview环境有问题？&lt;/p&gt;
&lt;p&gt;于是随便打开了一个之前main分支的预览环境，发现也是500错误。这个时候我就开始怀疑Vercel的Preview环境是不是有问题。&lt;/p&gt;
&lt;p&gt;那就只能实践出真知了，直接merge到主分支，看看生产环境会不会有问题。结果那就是一遍过。生产环境一切正常。真的是自己吓自己。。&lt;/p&gt;
&lt;h2&gt;总结&lt;/h2&gt;
&lt;p&gt;Vercel的Preview环境真的是一个天坑。完全不可靠，测试完全不靠谱。建议大家在开发新功能时，在本地测试完后做好措施，如果发现测试环境有问题，可以试试直接在生产环境测试。&lt;/p&gt;
&lt;p&gt;毕竟Vercel的Preview环境可能会给你带来意想不到的麻烦。&lt;/p&gt;
&lt;p&gt;当然，如果有哪位大佬知道Vercel的Preview环境到底出了什么问题，欢迎在评论区指导一二。&lt;/p&gt;
</content:encoded></item><item><title>使用Charles在MuMu12上进行抓包</title><link>https://blog.170529.xyz/posts/charles-mumu12-capture/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/charles-mumu12-capture/</guid><description>使用Charles在MuMu12上进行抓包</description><pubDate>Fri, 23 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://space.bilibili.com/355143/dynamic&quot;&gt;ATDAN&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;因为我使用的模拟器是MuMu12，所以需要在MuMu12中配置代理，使其能够通过Charles进行抓包。具体步骤如下：主要参考了&lt;a href=&quot;https://mumu.163.com/help/20240814/40912_1174291.html&quot;&gt;官网教程&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但是还是要额外补充一些要注意的点：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;教程中要使用&lt;code&gt;openssl x509 -inform PEM -subject_hash_old -in charles.pem&lt;/code&gt;命令，win默认自带的OpenSSL版本过低，无法使用该命令。可以手动安装一个新的版本，具体步骤参考另一篇文章&lt;a href=&quot;https://blog.170529.xyz/posts/%E5%8D%87%E7%BA%A7windows%E7%9A%84openssl&quot;&gt;《升级Windows自带的OpenSSL》&lt;/a&gt;。&lt;/li&gt;
&lt;li&gt;文中adb连接地址为 &lt;code&gt;127.0.0.1:7555&lt;/code&gt; ，但mumu12的adb默认端口为16384，故需要将连接地址修改为 &lt;code&gt;127.0.0.1:16384&lt;/code&gt;。&lt;/li&gt;
&lt;li&gt;使用adb root命令时，注意到模拟器界面在弹出的授权框中需要点击“允许”才能获取root权限。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;adb push c98c9e74.0 /system/etc/security/cacerts
.\adb shell &quot;chmod 664 /system/etc/security/cacerts/c98c9e74.0&quot;&lt;/p&gt;
</content:encoded></item><item><title>升级Windows自带的OpenSSL</title><link>https://blog.170529.xyz/posts/upgrade-windows-openssl/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/upgrade-windows-openssl/</guid><description>由于在使用中发现Windows自带的OpenSSL版本过低，导致无法使用某些功能，因此决定升级OpenSSL。但升级过程有些曲折，记录一下。</description><pubDate>Fri, 23 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://space.bilibili.com/7198052/dynamic&quot;&gt;鸦居&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;我在尝试使用抓包工具Charles时，教程中提到有一步需要使用&lt;code&gt;openssl x509 -inform pem -subject_hash_old -in charles.pem&lt;/code&gt;命令来获取证书的指纹。由于Windows自带的OpenSSL版本过低，导致无法使用该命令，因此决定升级OpenSSL。&lt;/p&gt;
&lt;h2&gt;升级过程&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;首先，访问&lt;a href=&quot;https://slproweb.com/products/Win32OpenSSL.html&quot;&gt;Shining Light Productions&lt;/a&gt;下载最新版本的OpenSSL，选择适合你系统的版本（32位或64位），并下载对应的安装包。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;或者使用winget命令安装：&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;winget install -e --id ShiningLight.Light.OpenSSL
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下载完成后，点击对应的安装包进行安装。安装过程中，选择“Copy OpenSSL DLL files to: The Windows system directory”选项，这样可以将OpenSSL的DLL文件复制到Windows系统目录中，方便后续使用。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;接下来，需要将OpenSSL的bin目录添加到系统的环境变量中。右键点击“此电脑”，选择“属性”，然后点击“高级系统设置”，在“系统属性”窗口中点击“环境变量”。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;在“系统变量”中找到“Path”变量，选择后点击“编辑”，然后添加OpenSSL的bin目录，例如&lt;code&gt;C:\Program Files\OpenSSL-Win64\bin&lt;/code&gt;。
:::important
如果你直接点击新增按钮并添加，你会发现还是使用的默认旧版本。&lt;/p&gt;
&lt;p&gt;所以你需要手动将对应的条目放置在最上面。以防止哪里第三方的OpenSSL版本覆盖你新安装的版本。
:::&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;完成后，打开命令提示符，输入&lt;code&gt;openssl version&lt;/code&gt;，如果显示的是最新版本的OpenSSL，则说明升级成功。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>关于EDG事件的杂谈？</title><link>https://blog.170529.xyz/posts/edg-event-talk/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/edg-event-talk/</guid><description>聊聊最近EDG发生的事情</description><pubDate>Mon, 19 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;（非）客观先介绍下事情的经过&lt;/h2&gt;
&lt;p&gt;:::note
以下内容来自Gemini搜集网络资料整理
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;早期 (早于2025年4月):&lt;/strong&gt; Simon加入EDG战队。 EDG在2024年的无畏契约全球冠军赛中夺冠，Simon是其中的关键一员，赢得了粉丝的喜爱。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025年VCT中国赛区第一阶段前两周:&lt;/strong&gt; EDG决定用新选手Jieni7替换Simon。 这一变动引起了粉丝的不满和困惑，因为Simon在之前的比赛中表现出色。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025年4月14日左右:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;EDG经理Aqua在直播中解释换人原因，称Simon训练不努力，与队伍沟通存在问题。 Aqua提到，Simon被下放后曾问是否第二天就不用起床训练，并且在队友训练时玩手机、不参与复盘、不与教练沟通，甚至不去场馆。 Aqua表示，EDG最初希望保护Simon，认为他还有进步空间。&lt;/li&gt;
&lt;li&gt;Simon对此进行回应，声称自己一直在努力训练。 他在直播中分享了与Aqua的私聊截图，质疑为何自己要承担比其他首发选手更多的训练责任，并声称其他队员也存在不职业的行为。 他认为自己受到了不平等的对待。&lt;/li&gt;
&lt;li&gt;EDG的其他关键队员，包括Zmjjkk、Nobody、Smoggy和CHICHOO，在微博上公开表达了对Simon的不满。 他们指责Simon在训练赛中缺乏沟通，与直播时判若两人，并且在比赛表现不佳时会说不想打或要求换人。 队友表示曾试图帮助和包容Simon，但他的职业态度令人无法接受。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2025年4月15日左右:&lt;/strong&gt; Simon与Aqua的微信聊天记录被泄露，进一步公开了双方的矛盾和指责。 聊天记录显示，Simon对被下放感到不满，并质疑队伍的管理和队友的职业性，而Aqua则指责Simon缺乏投入和在训练中的不良行为，并称通过摄像头观察到了他的行为。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;事件发生后至VCT CN第一赛段结束:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;部分Simon的粉丝对EDG俱乐部和队员进行了攻击，甚至泄露了队员Smoggy的个人信息。 EDG俱乐部对此采取了法律行动，并报案处理。&lt;/li&gt;
&lt;li&gt;EDG战队在2025年VCT CN第一赛段的比赛中表现不佳。 作为曾经的世界冠军和CN赛区的成绩担当，EDG未能延续其统治力。&lt;/li&gt;
&lt;li&gt;EDG在季后赛中，于败者组以1-2不敌BLG，止步四强，因此无缘参加多伦多大师赛。 这是EDG自VCT CN赛区成立以来，首次未能获得大师赛资格，也创下了他们在CN联赛的最差战绩。&lt;/li&gt;
&lt;li&gt;Simon在此期间处于非活跃状态，未能参与队伍的比赛。 他的未来去向和职业生涯发展尚未明确。&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;聊聊这件事&lt;/h2&gt;
&lt;p&gt;其实我本人没有玩过很多瓦罗兰特游戏本身，之前也只是搓过几把游戏。&lt;/p&gt;
&lt;p&gt;第一次真正接触其比赛，也就是edg的夺冠的那次冠军赛。偶然在B站有刷到说edg打破历史，于是便点进了比赛看了两眼。&lt;/p&gt;
&lt;p&gt;很幸运，第一次看比赛就看到了edg的夺冠。这是VCTCN赛区的第一次夺冠，作为一个新兴的赛区，能在全球赛上夺冠，确实是很不容易的。EDG在赛前也是不被大家看好，但就是他们，在比赛中打破了所有人的预期，最终夺得了冠军。&lt;/p&gt;
&lt;p&gt;之后也陆陆续续看了一些比赛，EDG的表现也一直很不错。直到曼谷大师赛，EDG回家的那场比赛。我当初也是觉得胜负乃兵家常事，没什么特别遗憾的，有问题慢慢解决就好了。&lt;/p&gt;
&lt;p&gt;但是后续就是第一赛段的换人事件了。我一开始也觉得很意外，冠军五人组的换人，确实是很少见的。虽然说需要调整，但是直接换人对于团队游戏意味着很多东西。毕竟团队游戏，五个人的配合是很重要的。换人意味着重新磨合，重新适应。但是也还是觉得是正常轮换，毕竟是要求变的。&lt;/p&gt;
&lt;p&gt;当时网上也冒出了一些声音质疑战队为什么要换Jieni7。 毕竟确实刚上场，打的不是很行。&lt;/p&gt;
&lt;p&gt;但是后面随着时间的推移，越来越多的事情被曝光出来。 这件事也就变得越来越复杂了。看到了Simon的直播，看到网上的争吵。但我也是默默吃瓜，等待回应，静待发展。&lt;/p&gt;
&lt;p&gt;直到了那天，四个队友集体发声，在微博上指责Simon。我也觉得这件事就是已经盖棺定论了。职业选手的职业态度是很重要的，有天赋的很多，一个人要四个队友哄着玩游戏，这就说不过去了。我记得当初的网上的声音也基本是指责Simion的。&lt;/p&gt;
&lt;p&gt;可是电子竞技就是成绩说话，EDG输了就是该挨骂，但是这时候转过来又开始疯狂指责战队。 说战队不行，战队管理不行，战队换人不行。 这就有点搞心态了。从各种证据来看，Simon的职业态度就是不行。这还没得洗的，在我的主观视角看来，俱乐部的处理没啥问题，正常换人，正常调整。后续交流也表明Simon的主观态度很差。&lt;/p&gt;
&lt;p&gt;我觉得吧，成绩不行，确实该喷。Simon对于战队，对于他自己我觉得都是一个双输的局面，最终的结果也让人感到遗憾。但是我也不认为成绩不行不是反过来疯狂喷战队，喷选手的理由。一个战队不可能一直整整齐齐。既然已经是轮换了，就要坦然接受。如kk所说：从零开始。我真的蛮不理解这些在网上一直喷的人。你们的存在是为了让战队成绩更好？还是只是串子，喷喷EDG过过瘾？&lt;/p&gt;
&lt;p&gt;很喜欢一句话，VCTCN夺冠夺得太早了，导致大家根本不知道一个冠军有多难。后续的期望和压力也太大了。节奏起来以后我也看到很多其实平时不看edg瓦比赛的其他游戏主播，也是开始指责edg管理层不行，可能会干涉到其他同俱乐部比赛的队伍。我真的只能遗憾于舆论状况的恶劣。&lt;/p&gt;
&lt;p&gt;总得来说，菜就多练，电子竞技，成绩说话。&lt;/p&gt;
&lt;p&gt;EDG我上早八，后续支棱点！
&lt;img src=&quot;https://r2.170529.xyz/2025/05/IMG_20250519_231025_690.avif&quot; alt=&quot;冠军！&quot; title=&quot;冠军！&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>在Vercel上部署Fuwari博客</title><link>https://blog.170529.xyz/posts/deploy-fuwari-vercel/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/deploy-fuwari-vercel/</guid><description>在Vercel上部署Fuwari博客的详细步骤和经验分享</description><pubDate>Sun, 04 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. 博客历程回忆&lt;/h1&gt;
&lt;h2&gt;1.1早期的 Hexo&lt;/h2&gt;
&lt;p&gt;其实大概五六年前就开始瞎琢磨博客，当时觉得互联网上拥有博客的人很帅，有一个能够展示自己的地方，就开始琢磨着写第一个博客。没钱吗，就得琢磨着白嫖。最早好像是用的 Hexo 和 Github Pages 搭建的第一个博客。主要是没钱和没域名，就是瞎折腾。现在都忘不了在那配置 Hexo 的 Next 主题的样子。
当初比较流行给博客加一个 Live2D 小人在前面萌娘用，当初折腾了半天样式优化和二次元萌娘，啥代码都看不懂，就是照搬着尝试去做，属于早期的探索。啥也不懂，就会折腾。
感谢遇见西门大佬的 (&lt;a href=&quot;https://www.simon96.online/2018/10/12/hexo-tutorial/&quot;&gt;最全Hexo博客搭建+主题优化+插件配置+常用操作+错误分析 | 遇见西门&lt;/a&gt;), 一个非常详细的攻略，让一位啥具体技术也不懂的萌新，学会了搭建了人生的第一个博客。&lt;/p&gt;
&lt;h2&gt;1.2遇见 Typecho&lt;/h2&gt;
&lt;p&gt;因为说过我是一个很喜欢折腾的人，Hexo 搭建后，因为现实生活比较忙，就暂时没有动博客一年多。但是后续有空折腾的时候，又开始看 Hexo 不爽了。当初又接触到了虚拟面板（就那种没有 cli，就一个提供给界面给你上下传文件之类的），又是因为穷，而这种面板属于一个月几块钱，刚好是可以接受的程度。于是又开始折腾打算做新的博客。当初在 WordPress 和 Typecho 中纠结。最后选择了 Typecho（我觉得这玩意儿更新鲜），并选择了 handsome 主题。属于是年轻人的第一个动态博客了。
于是又是一番折腾，什么 css 美化，添加什么网页名称变换，添加博客音乐播放器之类的。&lt;/p&gt;
&lt;h2&gt;1.3归于 Astro&lt;/h2&gt;
&lt;h3&gt;1.3.1原因&lt;/h3&gt;
&lt;p&gt;其实主要的原因是，又开始心血来潮想部署博客，看到很多人在推荐 Astro，于是便选择了 Astro，我这个人还是比较喜欢花里胡哨的东西，不是很喜欢极简的的类似 Paper 一样的主题，于是最终挑选之下选择了 Fuwari。
&lt;img src=&quot;https://r2.170529.xyz/2025/05/20250519220337443.avif&quot; alt=&quot;Preview&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;2.安装 Fuwari&lt;/h1&gt;
&lt;h2&gt;2.1 Fuwari功能特性&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;[x]  基于 Astro 和 Tailwind CSS 开发&lt;/li&gt;
&lt;li&gt;[x]  流畅的动画和页面过渡&lt;/li&gt;
&lt;li&gt;[x]  亮色 / 暗色模式&lt;/li&gt;
&lt;li&gt;[x]  自定义主题色和横幅图片&lt;/li&gt;
&lt;li&gt;[x]  响应式设计&lt;/li&gt;
&lt;li&gt;[x]  评论（作者没实现，自行选择了 Giscus）&lt;/li&gt;
&lt;li&gt;[x]  搜索&lt;/li&gt;
&lt;li&gt;[x]  文内目录（已经实现）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.2 安装方法&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;使用此模板&lt;a href=&quot;https://github.com/saicaca/fuwari/generate&quot;&gt;生成新仓库&lt;/a&gt;或 Fork 此仓库&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
Vercel 支持私有仓库，所以为了隐私，建议使用私有仓库。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;进行本地开发，Clone 新的仓库，执行 &lt;code&gt;pnpm install&lt;/code&gt; 和 &lt;code&gt;pnpm add sharp&lt;/code&gt; 以安装依赖
&lt;ul&gt;
&lt;li&gt;若未安装 &lt;a href=&quot;https://pnpm.io/&quot;&gt;pnpm&lt;/a&gt;，执行 &lt;code&gt;npm install -g pnpm&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;通过配置文件 &lt;code&gt;src/config.ts&lt;/code&gt; 自定义博客&lt;/li&gt;
&lt;li&gt;执行 &lt;code&gt;pnpm new-post &amp;lt;filename&amp;gt;&lt;/code&gt; 创建新文章，并在 &lt;code&gt;src/content/posts/&lt;/code&gt; 目录中编辑&lt;/li&gt;
&lt;li&gt;参考&lt;a href=&quot;https://docs.astro.build/zh-cn/guides/deploy/&quot;&gt;官方指南&lt;/a&gt;将博客部署至 Vercel, Netlify, GitHub Pages 等。就打开 Vercel 导入即可&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;3.博客初始化&lt;/h1&gt;
&lt;p&gt;在本地 Clone 下载后，本地搭建后环境后，便可以开始配置自定义设置了。&lt;/p&gt;
&lt;h2&gt;3.1配置 src/config.ts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;export const siteConfig: SiteConfig = {
 title: &quot;Elysiam&quot;, //修改网站主标题
 subtitle: &quot;May you, the beauty of this world, always shine&quot;,//修改网站副标题
 lang: &quot;zh_CN&quot;, // 选择语言&apos;en&apos;, &apos;zh_CN&apos;, &apos;zh_TW&apos;, &apos;ja&apos;, &apos;ko&apos;, &apos;es&apos;, &apos;th&apos;
 themeColor: {
  hue: 250, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
  fixed: false, // Hide the theme color picker for visitors
 },
 banner: {
  enable: true,// 启用banner
  src: &quot;assets/images/custom1.png&quot;, // Relative to the /src directory. Relative to the /public directory if it starts with &apos;/&apos;
  position: &quot;center&quot;, // Equivalent to object-position, only supports &apos;top&apos;, &apos;center&apos;, &apos;bottom&apos;. &apos;center&apos; by default
  credit: {
   enable: true, // Display the credit text of the banner image
   text: &quot;Honkai 3rd&quot;, // Credit text to be displayed
   url: &quot;&quot;, // (Optional) URL link to the original artwork or artist&apos;s page
  },
 },
 toc: {
  //启用目录（需要后续优化，见后面章节）
  enable: true, // Display the table of contents on the right side of the post
  depth: 3, // Maximum heading depth to show in the table, from 1 to 3
 },

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;接下来是修改个人信息，默认的类似 Twitter 和 Steam 我都删掉了，然后添加了网易云音乐和邮箱，使用了 tabler 图标库，无法加载出图标的可以使用下面的命令。
&lt;code&gt;pnpm add -D @iconify-json/tabler&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const profileConfig: ProfileConfig = {
 avatar: &quot;assets/images/demo-avatar.jpg&quot;, // 修改头像 Relative to the /src directory. Relative to the /public directory if it starts with &apos;/&apos;  
 name: &quot;Starfallen&quot;, // 修改名称
 bio: &quot;May you, the beauty of this world, always shine&quot;, // 修改个性签名
 links: [
  {
   name: &apos;NetEaseMusic&apos;,
   icon: &apos;tabler:brand-netease-music&apos;,
   url: &apos;https://music.163.com/#/user/home?id=331341358&apos;,
  },
  // 添加Email
  {
   name: &quot;Email&quot;,
   icon: &quot;tabler:mail&quot;,
   url: &quot;mailto:solihuan@foxmail.com&quot;,
  },
  {
   name: &quot;GitHub&quot;,
   icon: &quot;fa6-brands:github&quot;,
   url: &quot;https://github.com/Starfallan&quot;, // Updated GitHub URL
  },
 ],
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.2修改关于界面&lt;/h2&gt;
&lt;p&gt;这我觉得没啥好说的，就是修改 &lt;code&gt;src\content\spec\about.md&lt;/code&gt;，写上自己想要的关于界面就行&lt;/p&gt;
&lt;h1&gt;4.博客优化配置&lt;/h1&gt;
&lt;h2&gt;4.1优化目录显示&lt;/h2&gt;
&lt;p&gt;打开目录后，在我的设备上，目录是无法正常显示的。一开始是以为目录配置出错，查询了代码后，才发现目录会在小屏幕上默认隐藏，但是在我的 16:9 的 1920 x 1080 的笔记本屏幕上，目录仍然是无法正常显示。尝试修改显示代码，进行优化。方法来自于 &lt;a href=&quot;https://github.com/saicaca/fuwari/issues/311&quot;&gt;Disappear TOC · Issue #311 · saicaca/fuwari&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- 将原有的2xl:block改为xl:block，即修改触发宽度 --&amp;gt;
&amp;lt;div class=&quot;absolute w-full z-0 hidden 2xl:block&quot;&amp;gt;
&amp;lt;div class=&quot;absolute w-full z-0 hidden xl:block&quot;&amp;gt; 
    &amp;lt;div class=&quot;relative max-w-[var(--page-width)] mx-auto&quot;&amp;gt;
        &amp;lt;!-- TOC component --&amp;gt;
        {siteConfig.toc.enable &amp;amp;&amp;amp; &amp;lt;div id=&quot;toc-wrapper&quot; class:list={[&quot;hidden lg:block transition absolute top-0 -right-[var(--toc-width)] w-[var(--toc-width)] flex items-center&quot;,
        {siteConfig.toc.enable &amp;amp;&amp;amp; &amp;lt;div id=&quot;toc-wrapper&quot; class:list={[&quot;block lg:block transition absolute top-0 right-0 lg:-right-[var(--toc-width)] w-[var(--toc-width)] flex items-center&quot;,
            {&quot;toc-hide&quot;: siteConfig.banner.enable}]}
        &amp;gt; 

&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
由于改成了 xl: block 显示效果不是很好，有大神可以指教下怎么后续优化&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;效果如图所示：&lt;img src=&quot;https://picr.zz.ac/UM1qPgD2bEd4B2jCVRmzATT9qyM-_5gBgwSYkKDJOpQ=&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;4.2 添加评论系统&lt;/h2&gt;
&lt;p&gt;由于 Fuwari主题尚未实现自有的评论系统，我选择了 Giscus 作为自己博客的评论系统。&lt;/p&gt;
&lt;p&gt;:::important
2025年 7 月 22 日更新&lt;/p&gt;
&lt;p&gt;在源仓库PR中闲逛时发现官方在comment分支中已经实现了Giucus，Twikoo和Disqus的支持，只是未合并到主分支。
建议使用对应PR手动配置：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari/pull/37&quot;&gt;✨ feat: Add comment component and comment configuration #37&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;上面的PR基本上实现了 Giscus 的配置&lt;/p&gt;
&lt;p&gt;接着可以结合下面的PR&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari/pull/405&quot;&gt;feat: make giscus component reactive #405&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;该PR使得 Giscus 组件可以响应主题变化（不就实现了暗黑模式？）。&lt;/p&gt;
&lt;p&gt;建议使用上述两个 PR 进行配置。以下过时内容仅供参考。
:::&lt;/p&gt;
&lt;h3&gt;4.2.1 创建 Gicus 用仓库&lt;/h3&gt;
&lt;p&gt;因为 Giscus 是利用仓库的 discussions 构建的，所以你需要在你的 Github 中创建一个新的用以存储的仓库。仓库需要保证：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;该仓库是&lt;a href=&quot;https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/setting-repository-visibility#making-a-repository-public&quot;&gt;公开的&lt;/a&gt;&lt;/strong&gt;，否则访客将无法查看 discussion。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href=&quot;https://github.com/apps/giscus&quot;&gt;giscus&lt;/a&gt; app 已安装&lt;/strong&gt;，否则访客将无法评论和回应。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Discussions&lt;/strong&gt; 功能已&lt;a href=&quot;https://docs.github.com/en/github/administering-a-repository/managing-repository-settings/enabling-or-disabling-github-discussions-for-a-repository&quot;&gt;在你的仓库中启用&lt;/a&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.2.2 配置 Giscus&lt;/h3&gt;
&lt;p&gt;打开giscus官方网站&lt;a href=&quot;https://giscus.app/zh-CN&quot;&gt;https://giscus.app/&lt;/a&gt;，进行配置：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;语言：选择你的使用的语言&lt;/li&gt;
&lt;li&gt;仓库：填写你刚刚创建的仓库&lt;/li&gt;
&lt;li&gt;映射关系：保持默认即可，即第一项&lt;/li&gt;
&lt;li&gt;Discussion 分类：保持默认即可&lt;/li&gt;
&lt;li&gt;特性：保持默认即可&lt;/li&gt;
&lt;li&gt;主题：保持默认，即用户偏好的色彩方案&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;按照顺序配置好之后，下方会自动生成对应代码，暂时不用复制，我们需要进行一定的修改以适应暗色模式。
这里暗色模式的配置来自于&lt;a href=&quot;https://ikamusume7.org/posts/frontend/comments_with_darkmode/&quot;&gt;在Fuwari中添加评论功能(带黑暗模式) - 伊卡的记事本&lt;/a&gt;，感谢大佬的思路。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;在 &lt;code&gt;src\components\widget&lt;/code&gt; 目录下新建 &lt;code&gt;Comments.svelte&lt;/code&gt; 文件，将上面 Giscus生成的代码替换掉data-themen 对应行就行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;section&amp;gt;
    &amp;lt;script src=&quot;https://giscus.app/client.js&quot;
        data-repo=&quot;[在此输入仓库]&quot;
        data-repo-id=&quot;[在此输入仓库 ID]&quot;
        data-category=&quot;[在此输入分类名]&quot;
        data-category-id=&quot;[在此输入分类 ID]&quot;
        data-mapping=&quot;pathname&quot;
        data-strict=&quot;0&quot;
        data-reactions-enabled=&quot;1&quot;
        data-emit-metadata=&quot;0&quot;
        data-input-position=&quot;bottom&quot;
        data-theme={$mode === DARK_MODE ? &apos;dark&apos; : &apos;light&apos;}
         //其实就改了这一行，复制giscus网站上的，然后替换这一行即可
        data-lang=&quot;zh-CN&quot;
        crossorigin=&quot;anonymous&quot;
        async&amp;gt;
    &amp;lt;/script&amp;gt;
&amp;lt;/section&amp;gt;

&amp;lt;script&amp;gt;
import { AUTO_MODE, DARK_MODE } from &apos;@constants/constants.ts&apos;
import { onMount } from &apos;svelte&apos;
import { writable } from &apos;svelte/store&apos;;
import { getStoredTheme } from &apos;@utils/setting-utils.ts&apos;
const mode = writable(AUTO_MODE)
onMount(() =&amp;gt; {
  mode.set(getStoredTheme())
})

function updateGiscusTheme() {
  const theme = document.documentElement.classList.contains(&apos;dark&apos;) ? &apos;dark&apos; : &apos;light&apos;
  const iframe = document.querySelector(&apos;iframe.giscus-frame&apos;)
  if (!iframe) return
  iframe.contentWindow.postMessage({ giscus: { setConfig: { theme } } }, &apos;https://giscus.app&apos;)
}

const observer = new MutationObserver(updateGiscusTheme)
observer.observe(document.documentElement, { attributes: true, attributeFilter: [&apos;class&apos;] })

window.onload = () =&amp;gt; {
  updateGiscusTheme()
}
&amp;lt;/script&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改文件 &lt;code&gt;src\pages\posts\[...slug].astro&lt;/code&gt; 中的代码
首先引入 &lt;code&gt;Comments&lt;/code&gt; 组件&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import MainGridLayout from &apos;../layouts/MainGridLayout.astro&apos;

import { getEntry } from &apos;astro:content&apos;
import { i18n } from &apos;../i18n/translation&apos;
import I18nKey from &apos;../i18n/i18nKey&apos;
import Markdown from &apos;@components/misc/Markdown.astro&apos;
+ import Comments from &apos;@components/widget/Comments.svelte&apos;

const friendsPost = await getEntry(&apos;spec&apos;, &apos;friends&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;之后用组件代码代替原先的代码
将末尾的原 Giscus 代码换成对应组件&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;script src=&quot;https://giscus.app/client.js&quot;
        data-repo=&quot;[在此输入仓库]&quot;
        data-repo-id=&quot;[在此输入仓库 ID]&quot;
        data-category=&quot;[在此输入分类名]&quot;
        data-category-id=&quot;[在此输入分类 ID]&quot;
        data-mapping=&quot;pathname&quot;
        data-strict=&quot;0&quot;
        data-reactions-enabled=&quot;1&quot;
        data-emit-metadata=&quot;0&quot;
        data-input-position=&quot;bottom&quot;
        data-theme=&quot;preferred_color_scheme&quot;
        data-lang=&quot;zh-CN&quot;
        crossorigin=&quot;anonymous&quot;
        async&amp;gt;
&amp;lt;/script&amp;gt;
&amp;lt;Comments client:only=&quot;svelte&quot;&amp;gt;&amp;lt;/Comments&amp;gt;

&amp;lt;/MainGridLayout&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.3 使用自定义字体&lt;/h2&gt;
&lt;p&gt;这里修改自定义字体的部分参考于这篇文章&lt;a href=&quot;https://blog.aulypc0x0.online/posts/use_custom_fonts_in_fuwari/&quot;&gt;在Fuwari使用自定义字体 - AULyPc&lt;/a&gt;，感谢大佬&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;添加你的字体文件到 &lt;code&gt;public/fonts/&lt;/code&gt; 目录，或者使用在线 cdn 字体，见下 css 文件&lt;/li&gt;
&lt;li&gt;在 &lt;code&gt;src\styles&lt;/code&gt; 文件夹下新建 &lt;code&gt;global.css&lt;/code&gt;
这里引用了两个字体，Maple Mono 用于代码块，霞鹜文楷作为网站主字体。&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
LXGW WenKai Screen 是屏幕优化版，整体加粗了一点，但是我觉得在 Fuwari 太粗了，所以使用了 GBLite 版&lt;/p&gt;
&lt;p&gt;以下css使用了cdn加载字体。在访问正常的情况下，由于CDN本身会对字体进行子集化和多级缓存，加载速度会更快。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;:::warning[警告]&lt;/p&gt;
&lt;p&gt;但是，网上很多地方推荐的LXGW的字体连接是BootCDN和Staticfile。这两个CDN有投毒情况，请勿使用。
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
// 引入字体Maple Mono NF CN
@import url(&quot;https://fontsapi.zeoseven.com/442/main/result.css&quot;);

// 引入字体LXGW WenKai Screen
@import url(&quot;https://s4.zstatic.net/ajax/libs/lxgw-wenkai-screen-webfont/1.7.0/style.min.css&quot;);

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;随后修改 &lt;code&gt;tailwind.config.cjs&lt;/code&gt;，应用页面主字体, 将 Roboto 改为你的字体：&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/** @type {import(&apos;tailwindcss&apos;).Config} */
const defaultTheme = require(&quot;tailwindcss/defaultTheme&quot;)
module.exports = {
  content: [&quot;./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue,mjs}&quot;],
  darkMode: &quot;class&quot;, // allows toggling dark mode manually
  theme: {
    extend: {
      fontFamily: {
        sans: [&quot;LXGW WenKai Screen&quot;, &quot;sans-serif&quot;, ...defaultTheme.fontFamily.sans],
      },
    },
  },
  plugins: [require(&quot;@tailwindcss/typography&quot;)],
}

&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;修改代码块字体
如果你没有使用Expressive Code 替换掉原有的代码块显示，就是在 &lt;code&gt;src\styles\markdown.css&lt;/code&gt; 修改对应 code 的样式文件，更改 font-family 即可&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;.....省略前面内容
code {
    @apply bg-[var(--inline-code-bg)] text-[var(--inline-code-color)] px-1 py-0.5 rounded-md overflow-hidden;
    font-family: &apos;Maple Mono Normal CN&apos;, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace;
    &amp;amp;:before {
        content:none;
    }
    &amp;amp;:after {
        content:none;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;但是如果你和我一样使用了 &lt;code&gt;Expressive Code&lt;/code&gt; 替换了原有的代码块显示见[4.4 增强代码块](# 4.4 增强代码块)，则需要修改对应配置文件，参考了官方文档 &lt;a href=&quot;https://expressive-code.com/reference/style-overrides/&quot;&gt;Style Overrides | Expressive Code&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;.....省略前面内容
codeFontFamily:
     &quot;&apos;Maple Mono NF CN&apos;, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, &apos;Liberation Mono&apos;, &apos;Courier New&apos;, monospace&quot;,
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4.4 增强代码块&lt;/h2&gt;
&lt;p&gt;:::note
注意，官方已经在新版本中支持并默认开启了 Expressive Code，无须进行任何配置。&lt;/p&gt;
&lt;p&gt;以下教程无实际意义，仅供参考。
:::&lt;/p&gt;
&lt;p&gt;偷懒，不想写了，具体请继续参考伊卡大佬的&lt;a href=&quot;https://ikamusume7.org/posts/frontend/code_block_ex/&quot;&gt;增强Fuwari的代码块功能 - 伊卡的记事本&lt;/a&gt;，记得自己选个喜欢的主题就行。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
补充一下最后的显示行号的问题，官方文档推荐修改 &lt;code&gt;ec.config.js&lt;/code&gt;，我懒得新建，直接在原 &lt;code&gt;astro.config.mjs&lt;/code&gt; 上添加了，具体看上面 4.3 的完整配置&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>新服务器上手配置优化</title><link>https://blog.170529.xyz/posts/new-server-first-use/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/new-server-first-use/</guid><pubDate>Sat, 03 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
以下内容主要来自于Linux.do的 &lt;a href=&quot;https://linux.do/t/topic/160305&quot;&gt;我拿到VPS服务器必做的那些事&lt;/a&gt;，感谢原作者的分享。
:::&lt;/p&gt;
&lt;h1&gt;一、系统设置&lt;/h1&gt;
&lt;h2&gt;更新软件库&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;apt update -y &amp;amp;&amp;amp; apt upgrade -y&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;更新、安装必备软件&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;apt install sudo curl wget nano&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;校正系统时间&lt;/h2&gt;
&lt;p&gt;将时区改成上海&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo timedatectl set-timezone Asia/Shanghai&lt;/code&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;查看当前时区：&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;timedatectl&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;系统参数调优&lt;/h2&gt;
&lt;p&gt;这个功能通过调整各种系统和网络参数来优化服务器的性能。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;实现方法&lt;/strong&gt;：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;内核参数调整&lt;/strong&gt;：例如，增加TCP缓冲区大小、修改系统队列长度等，这些改变有助于提高网络吞吐量和减少延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;性能优化&lt;/strong&gt;：安装和配置&lt;code&gt;Tuned&lt;/code&gt;和其他系统性能优化工具来自动调整和优化服务器的运行状态。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;资源限制&lt;/strong&gt;：例如，设置文件打开数量的限制，这可以防止某些类型的资源耗尽攻击。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通过这些功能，你的服务器不仅能够更有效地管理资源，还能提高对外部威胁的防护能力，保障系统稳定运行。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bash &amp;lt;(wget -qO- https://raw.githubusercontent.com/jerry048/Tune/main/tune.sh) -t&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;二、BBR&lt;/h1&gt;
&lt;p&gt;BBR 是 Google 提出的一种新型拥塞控制算法（Bottleneck Bandwidth and RTT），全称为瓶颈带宽和往返传播时间。&lt;/p&gt;
&lt;p&gt;在 Linux 系统中，BBR 主要有以下特点和作用：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;提高网络性能&lt;/strong&gt;：它可以显著提高吞吐量和降低 TCP 连接的延迟，使数据传输更加高效。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;适应不同网络环境&lt;/strong&gt;：适合高延迟、高带宽的网络链路，以及慢速接入网络的用户，能在一定丢包率的网络链路上充分利用带宽，并降低网络链路上的缓冲区占用率从而降低延迟。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;优化拥塞控制&lt;/strong&gt;：BBR 改变了传统基于丢包反馈的拥塞控制机制，通过精确测量往返传播时间（RTT）和瓶颈带宽等参数来更有效地控制数据发送速率，避免了传统算法中因单纯丢包判断拥塞而导致的带宽利用率不高和端到端延迟大等问题。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;提升网络稳定性&lt;/strong&gt;：有助于减少网络拥塞和数据包丢失，提高网络的稳定性和可靠性。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;补充：Azure 服务器似乎是默认开启 BBR 的。&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;开启BBRX加速&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;bash &amp;lt;(wget -qO- https://raw.githubusercontent.com/jerry048/Tune/main/tune.sh) -x&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重启 VPS、使内核更新和BBR设置都生效&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo reboot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;确认BBR开启&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;如果你想确认 BBR 是否正确开启，可以使用下面的命令：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lsmod | grep bbr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此时应该返回这样的结果：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tcp_bbrx tcp_bbr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;再次重启 VPS&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo reboot&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;如果只有tcp_bbr则再等几分钟reboot&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此时再进行查询：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;lsmod | grep bbr&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;此时应该返回这样的结果：&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;tcp_bbrx&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;&lt;strong&gt;开启BBR加速（备选）&lt;/strong&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh &amp;amp;&amp;amp; chmod +x bbr.sh &amp;amp;&amp;amp; ./bbr.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;也是重启VPS生效&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;三、添加SWAP&lt;/strong&gt;&lt;/h1&gt;
&lt;p&gt;在 Linux 系统中，&lt;code&gt;SWAP&lt;/code&gt;（交换空间）是指一块磁盘空间，用于在物理内存（RAM）不足时，作为临时的扩展内存来使用。当系统的物理内存使用量接近饱和，Linux 内核会将一些不常使用的内存页交换到 &lt;code&gt;SWAP&lt;/code&gt; 分区中，从而为当前运行的程序腾出更多的物理内存。当这些被交换出去的内存页再次被需要时，它们会被重新换回到物理内存中。&lt;code&gt;SWAP&lt;/code&gt; 分区的存在可以在一定程度上避免由于物理内存不足导致系统性能严重下降或进程被强制终止的情况。&lt;/p&gt;
&lt;p&gt;因此，&lt;code&gt;SWAP&lt;/code&gt;对于内存小的VPS非常有必要，可以提高我们的运行效率。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;这里我们用脚本来添加。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wget -O swap.sh https://raw.githubusercontent.com/yuju520/Script/main/swap.sh &amp;amp;&amp;amp; chmod +x swap.sh &amp;amp;&amp;amp; clear &amp;amp;&amp;amp; ./swap.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;注意：脚本默认单位为MB&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看当前内存&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;free -h&lt;/code&gt;&lt;/p&gt;
&lt;h1&gt;&lt;strong&gt;四、安装Docker、Docker-compose&lt;/strong&gt;以及修改配置&lt;/h1&gt;
&lt;h2&gt;Docker&lt;/h2&gt;
&lt;h3&gt;Docker安装&lt;/h3&gt;
&lt;h4&gt;非大陆服务器&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;wget -qO- get.docker.com | bash&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;或&lt;/p&gt;
&lt;p&gt;&lt;code&gt;curl -fsSL https://get.docker.com -o get-docker.sh &amp;amp;&amp;amp; sh get-docker.sh&lt;/code&gt;&lt;/p&gt;
&lt;h4&gt;大陆服务器Docker安装&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;curl https://install.1panel.live/docker-install -o docker-install &amp;amp;&amp;amp; sudo bash ./docker-install &amp;amp;&amp;amp; rm -f ./docker-install&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;查看Docker版本&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;docker -v&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;开机自动启动&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl enable docker&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;卸载Docker&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;sudo apt-get purge docker-ce docker-ce-cli containerd.io sudo apt-get remove docker docker-engine sudo rm -rf /var/lib/docker sudo rm -rf /var/lib/containerd&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;五、修改SSH端口&lt;/h2&gt;
&lt;p&gt;修改 SSH 端口通常有以下几个主要原因：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;增强安全性&lt;/strong&gt;：SSH 服务默认使用的 22 端口是攻击者经常扫描和尝试攻击的目标。通过将端口修改为一个不常见的数值，可以减少自动攻击和暴力破解的风险，因为攻击者通常会首先针对常见的默认端口进行攻击。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;例如，如果攻击者使用自动化工具扫描大量服务器，这些工具可能主要集中在 22 端口。而修改了端口后，就降低了被这类工具轻易发现和攻击的可能性。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;减少误连接和非法访问尝试&lt;/strong&gt;：一些网络环境中可能存在大量的随机连接请求或非法访问尝试，针对默认的 22 端口。更改端口可以减少这类无意义的连接请求。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;假设您的服务器处于一个公共网络环境中，经常会收到大量的随机连接尝试，其中很多是针对常见端口的。修改 SSH 端口可以减少这类不必要的干扰。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;将默认的22端口修改为53486&lt;/strong&gt;（&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo sed -i &apos;s/^#\?Port 22.*/Port 53486/g&apos; /etc/ssh/sshd_config&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重启sshd服务&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl restart sshd&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;六、使用密钥登录&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;一键生成你的密钥&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wget -O key.sh https://raw.githubusercontent.com/yuju520/Script/main/key.sh &amp;amp;&amp;amp; chmod +x key.sh &amp;amp;&amp;amp; clear &amp;amp;&amp;amp; ./key.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;！！！注意：请牢记你生成的密钥，否则会有无法连接SSH的后果。！！！&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;七、安装fail2ban&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;安装fail2ban&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt install fail2ban&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;配置fail2ban&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;fail2ban的配置文件通常位于 &lt;code&gt;/etc/fail2ban/&lt;/code&gt; 目录下，fail2ban的.conf配置文件都是可以被.local覆盖，所以配置方式建议是添加.local文件，不修改原来的配置文件。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;nano /etc/fail2ban/jail.local&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;配置文件如下：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[DEFAULT]
#忽略的IP列表,不受设置限制（白名单）
ignoreip = 127.0.0.1

#允许ipv6
allowipv6 = auto

#日志修改检测机制（gamin、polling和auto这三种）
backend = systemd

#针对各服务的检查配置，如设置bantime、findtime、maxretry和全局冲突，服务优先级大于全局设置

[sshd]

#是否激活此项（true/false）
enabled = true

#过滤规则filter的名字，对应filter.d目录下的sshd.conf
filter = sshd

#ssh端口
port = ssh

#动作的相关参数
action = iptables[name=SSH, port=ssh, protocol=tcp]

#检测的系统的登陆日志文件
logpath = /var/log/secure

#屏蔽时间，单位：秒
bantime = 86400

#这个时间段内超过规定次数会被ban掉
findtime = 86400

#最大尝试次数
maxretry = 3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Ctrl+S保存并退出&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;设置开机自动启动fail2ban&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl enable fail2ban&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;重新启动fail2ban&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl restart fail2ban&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看fail2ban的状态&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo systemctl status fail2ban&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;查看所有可用jail的状态&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fail2ban-client status&lt;/code&gt;&lt;/p&gt;
</content:encoded></item><item><title>OpenWebui部署</title><link>https://blog.170529.xyz/posts/openwebui-deployment/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/openwebui-deployment/</guid><pubDate>Sat, 03 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;OpenWebui部署&lt;/h1&gt;
&lt;h2&gt;1. 镜像部署（使用二开版）&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;services:
  core:
    environment:
      - ENABLE_OPENAI_API=False
      - OFFLINE_MODE=True
      - TZ=Asia/Shanghai
      - AZURE_AI_API_KEY=&quot;***REMOVED***&quot;
      - AZURE_AI_ENDPOINT=&quot;***REMOVED***&quot;
      - EZFP_ENDPOINT=&quot;https://ezfp.cn/&quot;
      - EZFP_CALLBACK_HOST=&quot;https://ai.170529.xyz&quot;
      - EZFP_KEY=&quot;xxx&quot;
      - EZFP_PID=&quot;1&quot;
      - CREDIT_NO_CREDIT_MSG=&quot;余额不足，请在 &apos;设置-积分&apos; 中充值&quot;
      - USAGE_CALCULATE_MODEL_PREFIX_TO_REMOVE=&quot;&quot;
      - USAGE_DEFAULT_ENCODING_MODEL=&quot;gpt-4o&quot;
    image: ghcr.io/u8f69/open-webui:v0.6.5.16
    ports:
      - 3000:8080
    restart: unless-stopped
    volumes:
      - ./data:/app/backend/data
      - ./open-webui-assets-custom:/app/build/assets/custom
    command:
      - /bin/bash
      - -c
      - |
          cp /app/build/assets/custom/fonts/* /app/build/assets/fonts/
          cp /app/build/assets/custom/custom.css /app/build/assets/
          cp /app/build/assets/custom/custom.js /app/build/assets/
          sed -i &apos;s|&amp;lt;/head&amp;gt;|&amp;lt;link rel=&quot;stylesheet&quot; href=&quot;/assets/custom.css&quot;&amp;gt;&amp;lt;/head&amp;gt;|&apos; /app/build/index.html
          sed -i &apos;s|&amp;lt;/body&amp;gt;|&amp;lt;script src=&quot;/assets/custom.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&amp;lt;/body&amp;gt;|&apos; /app/build/index.html
          bash /app/backend/start.sh
    networks:
      - newapi

networks:
  newapi:
    external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 配置 Nginx （含有 websocket 和缓存加速）&lt;/h2&gt;
&lt;p&gt;:::note
以下配置文件来自于&lt;a href=&quot;https://linux.do/t/topic/677664/&quot;&gt;&lt;/a&gt;的讨论，经过测试可以正常使用。
:::&lt;/p&gt;
&lt;h3&gt;子配置文件 &lt;code&gt;/etc/nginx/conf.d/openwebui.conf&lt;/code&gt;&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80 ; 
    listen 443 ssl; 
    http2 on;
    server_name chat.170529.xyz;  # 改域名
    access_log   /var/log/nginx/nginx.openwebui.access.log main;
    error_log    /var/log/nginx/nginx.openwebui.error.log;
    ssl_certificate      /etc/letsencrypt/live/chat.170529.xyz/fullchain.pem;
    ssl_certificate_key  /etc/letsencrypt/live/chat.170529.xyz/privkey.pem;
    # ==================== 性能優化配置 ====================
    client_max_body_size 1024M; 
    tcp_nopush on; 
    tcp_nodelay on; 
    keepalive_timeout 65s; 
    keepalive_requests 1000; 
    sendfile on; 
    sendfile_max_chunk 1m; 
    # ==================== 全域代理緩衝優化 ====================
    proxy_buffering on; 
    proxy_buffer_size 128k; 
    proxy_buffers 4 256k; 
    proxy_temp_file_write_size 512k; 
    proxy_busy_buffers_size 256k; 
    proxy_connect_timeout 60s; 
    proxy_send_timeout 60s; 
    proxy_read_timeout 60s; 
    # Gzip 壓縮
    gzip on; 
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss application/vnd.ms-fontobject font/ttf font/otf font/woff font/woff2 image/svg+xml; 
    gzip_min_length 1024; 
    gzip_comp_level 5; 
    gzip_http_version 1.1; 
    gzip_vary on; 
    gzip_proxied any; 
    # ==================== 品牌設置變數 ====================
    set $custom_logo_url https://r2.170529.xyz/2025/05/favicon.avif; 
    set $brand_name &quot;Prometheus&quot;; 
    set $brand_short_name &quot;Prometheus&quot;; 
    set $static_proxy_host img.170529.xyz; 
    set $icon_replacement_url &apos;&quot;https://r2.170529.xyz/2025/05/favicon.avif&quot;&apos;; 
    set $backend_host http://127.0.0.1:3000;     # port
    # 分層緩存策略變數
    set $immutable_cache &quot;public, immutable, max-age=31536000&quot;; # 1年永久緩存
    set $static_cache &quot;public, max-age=86400&quot;; # 24小時緩存
    set $static_cache_control &quot;public, max-age=86400&quot;; # 24小時緩存（兼容性變數）
    set $font_cache &quot;public, max-age=604800&quot;; # 7天緩存（字體可以久一點）
    set $icon_cache &quot;no-cache, no-store, must-revalidate&quot;; # 圖標無緩存，立即顯示
    set $html_cache &quot;public, max-age=86400&quot;; # 24小時緩存（OI的index.html較大7KB）
    # 強制 HTTPS 重定向
    if ($scheme = http) {
        return 301 https://$host$request_uri; 
    }
    # ==================== 優化緩存策略  ====================
    # 1. /_app/immutable/ - hash 文件名，永久緩存
    location ~ ^/_app/immutable/ {
        proxy_pass $backend_host; 
        expires max; 
        access_log off; 
        add_header Cache-Control $immutable_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
    }
    # 2. 字型文件 - 較長緩存 (7天，字體變化不大)
    location ~* ^/(assets|static)/.*\.(woff|woff2|ttf|eot)$ {
        proxy_pass $backend_host; 
        expires 7d; 
        access_log off; 
        add_header Cache-Control $font_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
    }
    # 3. /assets/ 和 /static/ 其他文件 - 24小時緩存 (文件名不唯一，不建議永久緩存)
    location ~* ^/(assets|static)/.*\.(js|css|jpg|jpeg|gif|webp)$ {
        proxy_pass $backend_host; 
        expires 1d; 
        access_log off; 
        add_header Cache-Control $static_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
    }
    # ==================== 圖標處理配置 ====================
    # 精確匹配重要圖標路徑 - 最高優先級（避免與正則匹配衝突）
    location = /static/favicon.png {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
    }
    location = /static/favicon-96x96.png {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
    }
    location = /static/apple-touch-icon.png {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/png; 
    }
    location = /favicon.ico {
        return 301 /static/favicon.png; 
    }
    # iOS Safari 根目錄 apple-touch-icon 處理（關鍵！）
    location = /apple-touch-icon.png {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/png; 
    }
    # iOS Safari 優先查找的 precomposed 圖標（關鍵！）- 精確匹配優先
    location = /apple-touch-icon-precomposed.png {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/png; 
    }
    # iOS 會自動查找的各種尺寸 apple-touch-icon
    location ~ ^/apple-touch-icon-([0-9]+x[0-9]+)\.png$ {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/png; 
    }
    # iOS 其他格式的 apple-touch-icon（兜底方案）
    location ~ ^/apple-touch-icon-.*\.png$ {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/png; 
    }
    location = /static/favicon.ico {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
        types {
        }
        default_type image/x-icon; 
    }
    location = /static/favicon.svg {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
    }
    # ==================== 圖標處理配置 ====================
    # 正則匹配其他圖標（排除已精確匹配的）
    location ~ ^/static/(logo|logo-dark|favicon-dark|android-chrome-192x192|android-chrome-512x512)\.png$ {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
    }
    location ~ ^/static/(splash|splash-dark)\.png$ {
        resolver 1.1.1.1 valid=30s ipv6=off; 
        proxy_pass $custom_logo_url; 
        add_header Cache-Control $icon_cache; 
        proxy_ssl_server_name on; 
        proxy_ssl_name $static_proxy_host; 
        proxy_set_header Host $static_proxy_host; 
        proxy_set_header Referer &quot;&quot;; 
    }
    # 4. manifest.json - 無緩存確保圖標立即顯示
    location = /manifest.json {
        proxy_pass $backend_host; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header Accept-Encoding &quot;&quot;; 
        # 圖標相關配置無緩存，確保立即顯示
        add_header Cache-Control $icon_cache always; 
        add_header Pragma &quot;no-cache&quot; always; 
        add_header Expires &quot;Thu, 01 Jan 1970 00:00:00 GMT&quot; always; 
        proxy_hide_header ETag; 
        proxy_hide_header Last-Modified; 
        sub_filter_types application/json application/manifest+json; 
        sub_filter_once off; 
        sub_filter &apos;&quot;name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;name&quot;:&quot;$brand_name&quot;&apos;; 
        sub_filter &apos;&quot;short_name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;short_name&quot;:&quot;$brand_short_name&quot;&apos;; 
        sub_filter &apos;&quot;/static/logo.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/logo-dark.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/favicon.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/favicon-96x96.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/apple-touch-icon.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/android-chrome-192x192.png&quot;&apos; $icon_replacement_url; 
        sub_filter &apos;&quot;/static/android-chrome-512x512.png&quot;&apos; $icon_replacement_url; 
    }
    # API 配置端點
    location = /api/config {
        proxy_pass $backend_host; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header Accept-Encoding &quot;&quot;; 
        sub_filter_types application/json; 
        sub_filter_once off; 
        sub_filter &apos;&quot;name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;name&quot;:&quot;$brand_name&quot;&apos;; 
        sub_filter &apos;&quot;short_name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;short_name&quot;:&quot;$brand_short_name&quot;&apos;; 
    }
    # 認證相關 API
    location ~ ^/api/(v1/)?auths?/?$ {
        proxy_pass $backend_host; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header Accept-Encoding &quot;&quot;; 
        sub_filter_types application/json; 
        sub_filter_once off; 
        sub_filter &apos;&quot;name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;name&quot;:&quot;$brand_name&quot;&apos;; 
        sub_filter &apos;&quot;short_name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;short_name&quot;:&quot;$brand_short_name&quot;&apos;; 
    }
    # 其他靜態資源 - 24小時緩存
    location ~* ^/static/.*\.(html|json|txt|xml)$ {
        proxy_pass $backend_host; 
        expires 1d; 
        access_log off; 
        add_header Cache-Control $static_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
    }
    # 通用靜態文件處理（排除圖標文件，避免衝突）
    location ~ ^/static/(?!.*\.(png|ico|svg)$).*$ {
        proxy_pass $backend_host; 
        expires 1d; 
        access_log off; 
        add_header Cache-Control $static_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
    }
    # WebSocket 代理
    location /ws/ {
        proxy_pass $backend_host; 
        proxy_http_version 1.1; 
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection &quot;upgrade&quot;; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        # WebSocket 特殊優化 - 覆蓋全域緩衝設置
        proxy_buffering off; 
        proxy_cache off; 
        proxy_connect_timeout 7d; 
        proxy_send_timeout 7d; 
        proxy_read_timeout 7d; 
        proxy_set_header X-Forwarded-Host $server_name; 
        proxy_set_header X-Forwarded-Server $host; 
        tcp_nodelay on; 
    }
    # 其他 API 端點
    location /api/ {
        proxy_pass $backend_host; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header X-Forwarded-Host $server_name; 
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection $http_connection; 
        proxy_http_version 1.1; 
        # API 代理優化設置
        proxy_buffering on; 
        proxy_buffer_size 128k; 
        proxy_buffers 4 256k; 
        proxy_temp_file_write_size 512k; 
        proxy_busy_buffers_size 256k; 
        proxy_connect_timeout 60s; 
        proxy_send_timeout 60s; 
        proxy_read_timeout 60s; 
    }
    # ==================== SPA 路由處理 ====================
    # 5. SPA 特定路由 - 24小時緩存 (index.html較大7KB，使用適度緩存)
    location ~ ^/(auth|error|c/.+)$ {
        proxy_pass $backend_host; 
        add_header Cache-Control $html_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header X-Forwarded-Host $server_name; 
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection $http_connection; 
        proxy_http_version 1.1; 
        proxy_set_header Accept-Encoding &quot;&quot;; 
        sub_filter_types text/html application/json application/manifest+json; 
        sub_filter_once off; 
        sub_filter &apos;&amp;lt;title&amp;gt;Open WebUI&amp;lt;/title&amp;gt;&apos; &apos;&amp;lt;title&amp;gt;$brand_name&amp;lt;/title&amp;gt;&apos;; 
        sub_filter &apos;&quot;name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;name&quot;:&quot;$brand_name&quot;&apos;; 
        sub_filter &apos;&quot;short_name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;short_name&quot;:&quot;$brand_short_name&quot;&apos;; 
        sub_filter &apos;&amp;lt;head&amp;gt;&apos; &apos;&amp;lt;head&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;default&quot;&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;$brand_name&quot;&amp;gt;&amp;lt;link rel=&quot;apple-touch-icon&quot; sizes=&quot;180x180&quot; href=&quot;/apple-touch-icon.png&quot;&amp;gt;&amp;lt;link rel=&quot;apple-touch-icon-precomposed&quot; href=&quot;/apple-touch-icon-precomposed.png&quot;&amp;gt;&amp;lt;link rel=&quot;manifest&quot; href=&quot;/manifest.json?v=$date_gmt&quot;&amp;gt;&apos;; 
        sub_filter &apos;&amp;lt;/body&amp;gt;&apos; &apos;&amp;lt;script&amp;gt;document.addEventListener(&quot;DOMContentLoaded&quot;,function(){var s=document.createElement(&quot;style&quot;);s.textContent=&quot;#sidebar{transition:all 0.5s cubic-bezier(0.3,0.3,0.2,1)!important;--tw-duration:0.5s;--tw-ease:cubic-bezier(0.3,0.3,0.2,1);}#sidebar[data-state=true]{animation:sidebarFade 0.4s ease-in forwards!important;}@keyframes sidebarFade{0%,30%{opacity:0;}100%{opacity:1;}}&quot;;document.head.appendChild(s);});&amp;lt;/script&amp;gt;&amp;lt;/body&amp;gt;&apos;; 
    }
    # 主要頁面代理 - index.html (24小時緩存，考慮到文件較大7KB)
    location / {
        proxy_pass $backend_host; 
        add_header Cache-Control $html_cache; 
        proxy_set_header Host $host; 
        proxy_set_header X-Real-IP $remote_addr; 
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; 
        proxy_set_header X-Forwarded-Proto https; 
        proxy_set_header X-Forwarded-Host $server_name; 
        proxy_set_header Upgrade $http_upgrade; 
        proxy_set_header Connection $http_connection; 
        proxy_http_version 1.1; 
        proxy_set_header Accept-Encoding &quot;&quot;; 
        sub_filter_types text/html application/json application/manifest+json; 
        sub_filter_once off; 
        sub_filter &apos;&amp;lt;title&amp;gt;Open WebUI&amp;lt;/title&amp;gt;&apos; &apos;&amp;lt;title&amp;gt;$brand_name&amp;lt;/title&amp;gt;&apos;; 
        sub_filter &apos;&quot;name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;name&quot;:&quot;$brand_name&quot;&apos;; 
        sub_filter &apos;&quot;short_name&quot;:&quot;Open WebUI&quot;&apos; &apos;&quot;short_name&quot;:&quot;$brand_short_name&quot;&apos;; 
        sub_filter &apos;&amp;lt;head&amp;gt;&apos; &apos;&amp;lt;head&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-capable&quot; content=&quot;yes&quot;&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-status-bar-style&quot; content=&quot;default&quot;&amp;gt;&amp;lt;meta name=&quot;apple-mobile-web-app-title&quot; content=&quot;$brand_name&quot;&amp;gt;&amp;lt;link rel=&quot;apple-touch-icon&quot; sizes=&quot;180x180&quot; href=&quot;/apple-touch-icon.png&quot;&amp;gt;&amp;lt;link rel=&quot;apple-touch-icon-precomposed&quot; href=&quot;/apple-touch-icon-precomposed.png&quot;&amp;gt;&amp;lt;link rel=&quot;manifest&quot; href=&quot;/manifest.json?v=$date_gmt&quot;&amp;gt;&apos;; 
        sub_filter &apos;&amp;lt;/body&amp;gt;&apos; &apos;&amp;lt;script&amp;gt;document.addEventListener(&quot;DOMContentLoaded&quot;,function(){var s=document.createElement(&quot;style&quot;);s.textContent=&quot;#sidebar{transition:all 0.5s cubic-bezier(0.3,0.3,0.2,1)!important;--tw-duration:0.5s;--tw-ease:cubic-bezier(0.3,0.3,0.2,1);}#sidebar[data-state=true]{animation:sidebarFade 0.4s ease-in forwards!important;}@keyframes sidebarFade{0%,30%{opacity:0;}100%{opacity:1;}}&quot;;document.head.appendChild(s);});&amp;lt;/script&amp;gt;&amp;lt;/body&amp;gt;&apos;; 
    }
    # ==================== 完美的安全標頭設定 ====================
    add_header Strict-Transport-Security &quot;max-age=31536000&quot;; 
    add_header X-Frame-Options DENY always; 
    add_header X-Content-Type-Options nosniff always; 
    add_header X-XSS-Protection &quot;1; mode=block&quot; always; 
    add_header Referrer-Policy &quot;strict-origin-when-cross-origin&quot; always; 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 美化配置&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
参考来源 &lt;a href=&quot;https://linux.do/t/topic/440439&quot;&gt;https://linux.do/t/topic/440439&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;建立自定义文件夹 &lt;code&gt;open-webui-assets-custom&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;对应目录下建立 fonts 文件夹，文件夹中放自定义字体&lt;/li&gt;
&lt;li&gt;目录下建立 &lt;code&gt;custom.js&lt;/code&gt; &lt;code&gt;custom.css&lt;/code&gt;，具体配置看参考文献，仅修改字体配置&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;目录结构&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;open-webui-assets-custom/
    - fonts/
        - DankMono-Italic.woff2
        - DankMono-Regular.woff2
        - DingTalk-JinBuTi.woff2 
    - custom.css
    - custom.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;字体配置的修改&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@font-face {
    font-family: &apos;Maple Mono Normal&apos;;
    src: url(&apos;../assets/fonts/MapleMonoNormal-Regular.woff2&apos;) format(&apos;woff2&apos;);
    font-display: swap;
    font-style: normal;
}
@font-face {
    font-family: &apos;Maple Mono Normal&apos;;
    src: url(&apos;../assets/fonts/MapleMonoNormal-Italic.woff2&apos;) format(&apos;woff2&apos;);
    font-display: swap;
    font-style: italic;
}

@font-face {
    font-family: &apos;LXGW&apos;;
    src: url(&apos;../assets/fonts/LXGWWenKaiMonoGBScreen.woff2&apos;) format(&apos;woff2&apos;);
    font-display: swap;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 测试 Nginx 缓存是否生效&lt;/h2&gt;
&lt;p&gt;在配置完成后，可以通过以下几种方法测试缓存是否生效：&lt;/p&gt;
&lt;h3&gt;4.1 查看响应头中的缓存状态&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;使用浏览器开发者工具（F12）打开网络面板&lt;/li&gt;
&lt;li&gt;刷新页面并查找对 &lt;code&gt;/api/models&lt;/code&gt; 的请求&lt;/li&gt;
&lt;li&gt;在响应头中查看 &lt;code&gt;X-Cache-Status&lt;/code&gt; 字段：
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MISS&lt;/code&gt;: 表示请求未命中缓存，从上游服务器获取&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HIT&lt;/code&gt;: 表示请求命中缓存，直接从缓存返回&lt;/li&gt;
&lt;li&gt;&lt;code&gt;BYPASS&lt;/code&gt;: 表示请求绕过了缓存&lt;/li&gt;
&lt;li&gt;&lt;code&gt;EXPIRED&lt;/code&gt;: 表示缓存已过期&lt;/li&gt;
&lt;li&gt;&lt;code&gt;UPDATING&lt;/code&gt;: 表示缓存正在更新&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.2 使用 curl 命令行工具测试&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 第一次请求，应该显示 MISS
curl -I -H &quot;Host: chat.170529.xyz&quot; https://chat.170529.xyz/api/models | grep X-Cache-Status

# 第二次请求，如果缓存有效，应该显示 HIT
curl -I -H &quot;Host: chat.170529.xyz&quot; https://chat.170529.xyz/api/models | grep X-Cache-Status
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.3 检查缓存文件&lt;/h3&gt;
&lt;p&gt;查看 Nginx 缓存目录中是否有文件生成：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看缓存目录
ls -la /var/cache/nginx

# 检查缓存目录大小
du -sh /var/cache/nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 查看 Nginx 缓存日志&lt;/h3&gt;
&lt;p&gt;如果你配置了缓存日志，可以查看日志来确认缓存状态：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 查看 Nginx 访问日志
grep &quot;X-Cache-Status&quot; /var/log/nginx/nginx.openwebui.access.log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;缓存成功的特征是：当你第一次访问某个资源时，&lt;code&gt;X-Cache-Status&lt;/code&gt; 显示 &lt;code&gt;MISS&lt;/code&gt;，而后续访问同一资源时显示 &lt;code&gt;HIT&lt;/code&gt;，并且你会注意到第二次请求的响应时间明显缩短。&lt;/p&gt;
</content:encoded></item><item><title>NewAPi 部署</title><link>https://blog.170529.xyz/posts/newapi-deployment/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/newapi-deployment/</guid><description>New API部署，使用了Veloera修改版</description><pubDate>Tue, 29 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;NewAPi 部署&lt;/h1&gt;
&lt;h2&gt;Docker 配置，使用bridge网络，使openwebui和new api直接连接&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;

services:
  new-api: 
    image: ghcr.io/veloera/veloera:latest
    container_name: new-api
    restart: always
    command: --log-dir /app/logs
    ports:
      - &quot;3001:3000&quot;  # 修改外部端口为3001避免冲突
    volumes:
      - ./new-api-data:/data
      - ./new-api-logs:/app/logs
    environment:
      - SQL_DSN=root:123456@tcp(mysql:3306)/new-api
      - REDIS_CONN_STRING=redis://redis
      - TZ=Asia/Shanghai
    depends_on:
      - redis
      - mysql
    networks:
      - newapi
    healthcheck:
      test: [&quot;CMD-SHELL&quot;, &quot;wget -q -O - http://localhost:3000/api/status | grep -o &apos;\&quot;success\&quot;:\\s*true&apos; | awk -F: &apos;{print $$2}&apos;&quot;]
      interval: 30s
      timeout: 10s
      retries: 3

  redis:
    image: redis:latest
    container_name: redis
    restart: always
    networks:
      - newapi

  mysql:
    image: mysql:8.2
    container_name: mysql
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: 123456
      MYSQL_DATABASE: new-api
    volumes:
      - mysql_data:/var/lib/mysql
    networks:
      - newapi

volumes:
  mysql_data:

networks:
  newapi:
    external: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Nginx 配置&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;server{
   server_name api.170529.xyz;  # 请根据实际情况修改你的域名
   location / {
          client_max_body_size  64m;
          proxy_http_version 1.1;
          proxy_pass http://localhost:4233;  # 请根据实际情况修改你的端口
          proxy_set_header Host $host;
          proxy_set_header X-Forwarded-For $remote_addr;
          proxy_cache_bypass $http_upgrade;
          proxy_set_header Accept-Encoding gzip;
          proxy_read_timeout 300s;  # GPT-4 需要较长的超时时间，请自行调整
   }
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/api.170529.xyz/fullchain.pem; # Cloudflare证书
    ssl_certificate_key  /etc/letsencrypt/live/api.170529.xyz/privkey.pem; # Cloudflare证书密钥
    
    # SSL配置参数（可选，但建议保留以确保安全性）
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Certbot自动配置SSL证书</title><link>https://blog.170529.xyz/posts/ssl-auto-config/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/ssl-auto-config/</guid><description>使用Certbot自动生成Let’s Encrypt证书</description><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;用Certbot来自动生成Let’s Encrypt证书这件事儿，虽然官方和网上已经有了很详实的文档了，但是我还是想自己记录下，留作以后备查吧。&lt;/p&gt;
&lt;p&gt;本文用的操作系统是ubuntu 20.04，DNS的话用的是Cloudflare的服务，web服务使用nginx来提供。然后，按照以下步骤进行就可以了。&lt;/p&gt;
&lt;h2&gt;安装Certbot&lt;/h2&gt;
&lt;p&gt;用以下命令安装就可以了，某些时候snap install的时候可能比较慢，有时候还可能需要重试几次：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;apt install snapd  
snap install core  
snap refresh core  
snap install --classic certbot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;安装完成后，运行下&lt;code&gt;certbot --version&lt;/code&gt;命令，如果有版本号输出，就表明安装成功了。&lt;/p&gt;
&lt;h2&gt;安装Cloudflare插件&lt;/h2&gt;
&lt;p&gt;为了实现自动化地续签证书，需要安装下对应DNS服务商的插件才可以。我使用Cloudflare来做DNS解析，所以可以使用以下的命令来安装Cloudflare插件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;snap set certbot trust-plugin-with-root=ok  
  
snap install certbot-dns-cloudflare
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;设置Cloudflare API令牌&lt;/h2&gt;
&lt;p&gt;要使用Cloudflare插件，需要先登录到Cloudflare控制台来生成一个仅可操作DNS解析的API 令牌。可在登录Cloudflare后，跳转到这个地址来进行创建：&lt;a href=&quot;https://dash.cloudflare.com/profile/api-tokens&quot;&gt;https://dash.cloudflare.com/profile/api-tokens&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;以下是操作过程的简单演示：&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://imgtu.com/i/XHvupd&quot;&gt;&lt;img src=&quot;https://s1.ax1x.com/2022/06/16/XHvupd.png&quot; alt=&quot;XHvupd.png|700&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://imgtu.com/i/XHvmfH&quot;&gt;&lt;img src=&quot;https://s1.ax1x.com/2022/06/16/XHvmfH.png&quot; alt=&quot;XHvmfH.png|700&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://imgtu.com/i/XHvete&quot;&gt;&lt;img src=&quot;https://s1.ax1x.com/2022/06/16/XHvete.png&quot; alt=&quot;XHvete.png|700&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;创建完成后记得将出现的令牌复制下来。然后使用以下命令来创建配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p ~/.secrets/certbot  
  
vim ~/.secrets/certbot/cloudflare.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;填入如下格式的配置就可以了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;dns_cloudflare_api_token = 刚才获取到的API令牌
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;编辑完成后，可以修改下这个文件的权限，否则执行certbot命令时会报一个警告说文件权限不安全：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod 600 ~/.secrets/certbot/cloudflare.ini
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;获取证书&lt;/h2&gt;
&lt;p&gt;现在通过运行以下的命令就可以生成nginx所需的证书了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;certbot certonly --dns-cloudflare --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini --dns-cloudflare-propagation-seconds 60 -d api.170529.xyz
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意，这里用了&lt;code&gt;--dns-cloudflare-propagation-seconds 60&lt;/code&gt;把等待时间延长到了60秒，否则默认的10秒有些时候会导致校验失败。&lt;/p&gt;
&lt;p&gt;生成的证书和私钥会放到/etc/letsencrypt/live/YOUR_HOST目录下。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssl_certificate /etc/letsencrypt/live/knktc.com/fullchain.pem; # managed by Certbot  
ssl_certificate_key /etc/letsencrypt/live/knktc.com/privkey.pem; # managed by Certbot  
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;定时任务&lt;/h2&gt;
&lt;p&gt;通过以下命令可以查看certbot的定时任务是否添加成功了：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl list-timers
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果要在每次生成新的证书后自动reload下nginx，可以加个hook脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;vim /etc/letsencrypt/renewal-hooks/post/reload-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;加入以下的脚本：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh  
  
systemctl reload nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后给这个脚本文件加上权限：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod +x /etc/letsencrypt/renewal-hooks/post/reload-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这样的话，每次生成新的证书后，就会自动reload下nginx让新的证书生效了。&lt;/p&gt;
</content:encoded></item><item><title>Nginx安装</title><link>https://blog.170529.xyz/posts/nginx-installation/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/nginx-installation/</guid><description>Nginx安装指南，避免直接apt安装到旧版本</description><pubDate>Sun, 13 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1 清除旧版 Nginx（可选）&lt;/h2&gt;
&lt;p&gt;如果你的系统中之前安装过 Nginx，为了避免新旧版本冲突，建议先将其删除：&lt;/p&gt;
&lt;p&gt;1备份现有的 Nginx 配置文件：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo mv /etc/nginx/ /etc/nginx.old/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2停止 Nginx 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl stop nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3清除系统中所有旧的 Nginx 软件包：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt autoremove nginx* --purge 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成这些准备工作后，我们就可以开始安装 Nginx 的新版本了。&lt;/p&gt;
&lt;h2&gt;2. 安装 Nginx&lt;/h2&gt;
&lt;p&gt;我们选择直接从 Nginx.org 的官方软件源来安装，这样可以立即获得 Nginx 的最新版本。&lt;/p&gt;
&lt;h3&gt;2.1 导入 GPG 密钥&lt;/h3&gt;
&lt;p&gt;1为了确保软件包的安全性，需要先下载并添加 Nginx 的 GPG 密钥：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl -fSsL https://nginx.org/keys/nginx_signing.key | sudo gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg &amp;gt;/dev/null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2执行以下命令验证 GPG 密钥：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gpg --dry-run --quiet --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p3.png&quot; alt=&quot;导入 Nginx 官方源 GPG 密钥&quot; /&gt;&lt;/p&gt;
&lt;p&gt;导入 Nginx 官方源 GPG 密钥&lt;/p&gt;
&lt;h3&gt;2.2 添加 Nginx 官方软件源&lt;/h3&gt;
&lt;p&gt;根据你的需要，为 Ubuntu 添加 Nginx 官方软件源（2 选 1）：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;stable 稳定版&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/ubuntu `lsb_release -cs` nginx&quot; | sudo tee /etc/apt/sources.list.d/nginx.list
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;mainline 主线版&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu `lsb_release -cs` nginx&quot; | sudo tee /etc/apt/sources.list.d/nginx.list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p4.png&quot; alt=&quot;为 Ubuntu 添加 Nginx 官方软件源&quot; /&gt;&lt;/p&gt;
&lt;p&gt;为 Ubuntu 添加 Nginx 官方软件源&lt;/p&gt;
&lt;h3&gt;2.3 设置 APT 优先使用 Nginx 官方源&lt;/h3&gt;
&lt;p&gt;为了确保 Nginx.org 的软件包优先级高于 Ubuntu 默认源或其他 PPA，还需要设置 APT Pin：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo -e &quot;Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n&quot; | sudo tee /etc/apt/preferences.d/99nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;你将看到以下输出：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Package: *
Pin: origin nginx.org
Pin: release o=nginx
Pin-Priority: 900
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p5.png&quot; alt=&quot;设置 nginx.org 源优先级&quot; /&gt;&lt;/p&gt;
&lt;p&gt;设置 nginx.org 源优先级&lt;/p&gt;
&lt;h3&gt;2.4 安装 Nginx&lt;/h3&gt;
&lt;p&gt;在「终端」中执行以下命令更新软件包列表，并安装 Nginx：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
sudo apt install nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p6.png&quot; alt=&quot;安装 Nginx&quot; /&gt;&lt;/p&gt;
&lt;p&gt;安装 Nginx&lt;/p&gt;
&lt;h3&gt;2.5 验证安装结果&lt;/h3&gt;
&lt;p&gt;安装完成后，可以通过以下命令查看 Nginx 版本，验证是否正确安装：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nginx -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;如果安装成功，你将看到类似以下的输出信息：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nginx version: nginx/1.26.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p7.png&quot; alt=&quot;查看 Nginx 版本&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看 Nginx 版本&lt;/p&gt;
&lt;h2&gt;3. 管理 Nginx 服务&lt;/h2&gt;
&lt;p&gt;安装完成后，我们需要对 Nginx 服务进行管理：&lt;/p&gt;
&lt;h3&gt;3.1 检查服务状态&lt;/h3&gt;
&lt;p&gt;验证 Nginx 服务是否正常运行：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://img.sysgeek.cn/img/2024/06/install-nginx-ubuntu-p8.png&quot; alt=&quot;查看 Nginx 服务状态&quot; /&gt;&lt;/p&gt;
&lt;p&gt;查看 Nginx 服务状态&lt;/p&gt;
&lt;p&gt;查看 Nginx 服务状态&lt;/p&gt;
&lt;h3&gt;3.2 启动、停止和重启服务&lt;/h3&gt;
&lt;p&gt;你可以根据需要，通过以下命令手动启动、停止或重启 Nginx 服务：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl start nginx    # 启动服务
sudo systemctl stop nginx     # 停止服务
sudo systemctl reload nginx   # 重新加载配置文件，不中断服务
sudo systemctl restart nginx  # 重启服务
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 设置开机自启动&lt;/h3&gt;
&lt;p&gt;为了方便使用，你可以设置 Nginx 服务随系统启动时自动启动：&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo systemctl enable nginx   # 启用开机自启动
sudo systemctl disable nginx  # 禁止开机自启动
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Image Captions Example</title><link>https://blog.170529.xyz/posts/image-captions/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/image-captions/</guid><description>Guide to using the image caption feature.</description><pubDate>Thu, 13 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;About Image Captions&lt;/h2&gt;
&lt;p&gt;This feature wraps images in a &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; tag only when the image carries independent meaning. If the image is part of the content and does not stand alone, it will not be enclosed in a &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; tag. Only &lt;a href=&quot;https://commonmark.org/&quot;&gt;CommonMark&lt;/a&gt; syntax is supported.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;h3&gt;Single Image&lt;/h3&gt;
&lt;h4&gt;With Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![A description of the image](url &quot;An image title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;url&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;An image title&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Without Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![A description of the image](url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;url&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Multiple Images&lt;/h3&gt;
&lt;p&gt;When you include only images within a paragraph, they will be grouped together. Hard or soft breaks can also be used.&lt;/p&gt;
&lt;h4&gt;With Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Image 1 description](url1 &quot;Image Title 1&quot;)  
![Image 2 description](url2 &quot;Image Title 2&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;url1&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 1&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;url2&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 2&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Without Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Image 1 description](url1)  
![Image 2 description](url2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;url1&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;url2&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;With Shared Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![Image 1 description](url1 &quot;This becomes the caption&quot;)  
![Image 2 description](url2)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;div&amp;gt;
    &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;url1&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;url2&quot;&amp;gt;
  &amp;lt;/div&amp;gt;
  &amp;lt;figcaption&amp;gt;This becomes the caption&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;/p&gt;
&lt;h3&gt;Single Image Link&lt;/h3&gt;
&lt;h4&gt;With Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![A description of the image](image-url &quot;An image title&quot;)](link-url &quot;A link title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;image-url&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;An image title&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption&amp;gt;A link title&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![A description of the image](image-url)](link-url &quot;A link title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;image-url&quot;&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption&amp;gt;A link title&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![A description of the image](image-url &quot;An image title&quot;)](link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;image-url&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;An image title&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Without Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![A description of the image](image-url)](link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;img alt=&quot;A description of the image&quot; src=&quot;image-url&quot;&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;A description of the image&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Multiple Image Link&lt;/h3&gt;
&lt;p&gt;When you include only a multiple-image link within a paragraph, the images will be grouped together. Hard or soft breaks can also be used.&lt;/p&gt;
&lt;h4&gt;With Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![Image 1 description](image-url1 &quot;Image Title 1&quot;)  
![Image 2 description](image-url2 &quot;Image Title 2&quot;)](link-url &quot;A link title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;image-url1&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 1&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;image-url2&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 2&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption&amp;gt;A link title&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 1 description&quot; title=&quot;Image Title 1&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 2 description&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![Image 1 description](image-url1)  
![Image 2 description](image-url2)](link-url &quot;A link title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;image-url1&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;image-url2&quot;&amp;gt;
  &amp;lt;/a&amp;gt;
  &amp;lt;figcaption&amp;gt;A link title&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 1 description&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 2 description&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![Image 1 description](image-url1 &quot;Image Title 1&quot;)  
![Image 2 description](image-url2 &quot;Image Title 2&quot;)](link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;image-url1&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 1&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
    &amp;lt;figure&amp;gt;
      &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;image-url2&quot;&amp;gt;
      &amp;lt;figcaption&amp;gt;Image Title 2&amp;lt;/figcaption&amp;gt;
    &amp;lt;/figure&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 1 description&quot; title=&quot;Image Title 1&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 2 description&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;Without Caption&lt;/h4&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[![Image 1 description](image-url1)  
![Image 2 description](image-url2)](link-url)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;HTML:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;figure&amp;gt;
  &amp;lt;a href=&quot;link-url&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 1 description&quot; src=&quot;image-url1&quot;&amp;gt;
    &amp;lt;img alt=&quot;Image 2 description&quot; src=&quot;image-url2&quot;&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari&quot;&gt;&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 1 description&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=640/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot; alt=&quot;Image 2 description&quot; /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Root Relative Path and Relative Path&lt;/h3&gt;
&lt;h4&gt;Root Relative Path&lt;/h4&gt;
&lt;p&gt;A root relative path refers to assets located in the &lt;code&gt;public&lt;/code&gt; directory of your project.&lt;/p&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![A description of the image](/favicon/favicon-dark-128.png &quot;An image title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;/favicon/favicon-dark-128.png&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;Relative Path&lt;/h4&gt;
&lt;p&gt;A relative path refers to assets inside the &lt;code&gt;src&lt;/code&gt; directory, and it is relative to the location of the current file.&lt;/p&gt;
&lt;p&gt;Markdown:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;![A description of the image](../../assets/images/demo-avatar.png &quot;An image title&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;../../assets/images/demo-avatar.png&quot; alt=&quot;A description of the image&quot; title=&quot;Image Title 1&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;Options&lt;/h2&gt;
&lt;p&gt;You can specify the options in the &lt;code&gt;astro.config.mjs&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
import remarkImageCaption from &quot;./src/plugins/remark-image-caption.ts&quot;
...
export default defineConfig({
  ...
  markdown: {
    ...
    remarkPlugins: [
      ...
      remarkImageCaption, // Plugin here
      ...
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;className&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&quot;&quot;&lt;/td&gt;
&lt;td&gt;The class name to apply to the outer &lt;code&gt;&amp;lt;figure&amp;gt;&lt;/code&gt; element.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;excludedPaths&lt;/td&gt;
&lt;td&gt;(string | RegExp)[]&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;An array of image paths that should be excluded from transformation. This can also be used to exclude paths like those under the &lt;code&gt;src&lt;/code&gt; folder in Astro projects.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lazyLoad&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Set the &lt;code&gt;loading&lt;/code&gt; attribute on &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; elements.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;linkAttributes&lt;/td&gt;
&lt;td&gt;LinkAttributes&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Set the target and relationship attributes for external links. These attributes can also be left unset to delegate handling to other plugins.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;linkAttributes&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;target&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Specify where to open linked documents. The default (empty) does not set a target on links.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rel&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Define the relationship between the current document and the linked document. The default (empty) does not set any relationship.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>Link Cards Example</title><link>https://blog.170529.xyz/posts/link-cards/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/link-cards/</guid><description>Guide to using the link card feature.</description><pubDate>Sun, 23 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;This post is an example of how to use the link card feature in Fuwari. By Hasenpfote in the PR &lt;a href=&quot;https://github.com/saicaca/fuwari/pull/324&quot;&gt;#324&lt;/a&gt;。Thanks for the contribution!&lt;/p&gt;
&lt;h2&gt;About Link Cards&lt;/h2&gt;
&lt;p&gt;Link Cards are similar to the &lt;code&gt;&amp;lt;LinkCard&amp;gt;&lt;/code&gt; component in &lt;a href=&quot;https://starlight.astro.build&quot;&gt;Starlight&lt;/a&gt;, displaying links in a card format.&lt;/p&gt;
&lt;h2&gt;Usage&lt;/h2&gt;
&lt;p&gt;Include only a single &quot;bare&quot; link (a link without descriptive text), or something similar, within a paragraph in Markdown, and it will automatically be converted into a Link Card.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
**External Links**

https://astro.build/

&amp;lt;https://github.com/saicaca/fuwari/&amp;gt;

[https://fuwari.vercel.app/](https://fuwari.vercel.app/)

**Internal Links**

[/posts/guide/](/posts/guide/)

For more details, see the internalLink option section.

**IDN (Internationalized Domain Name)**

https://はじめよう.みんな/

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;https://astro.build/&quot;&gt;https://astro.build/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/saicaca/fuwari/&quot;&gt;https://github.com/saicaca/fuwari/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://fuwari.vercel.app/&quot;&gt;https://fuwari.vercel.app/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/posts/guide/&quot;&gt;/posts/guide/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://xn--p8j9a0d9c9a.xn--q9jyb4c/&quot;&gt;https://はじめよう.みんな/&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[!NOTE]
Once the cards are displayed, try changing the theme color or enabling dark mode!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Options&lt;/h2&gt;
&lt;p&gt;You can specify the options in the &lt;code&gt;astro.config.mjs&lt;/code&gt; file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
import fuwariLinkCard from &quot;./src/plugins/fuwari-link-card.ts&quot;
...
export default defineConfig({
  ...
  integrations: [
    ...
    fuwariLinkCard(), // Plugin here
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the order of plugins is complex, you can also specify it as a remark plugin.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;...
import remarkLinkCard from &quot;./src/plugins/remark-link-card.ts&quot;
...
export default defineConfig({
  ...
  markdown: {
    ...
    remarkPlugins: [
      ...
      remarkLinkCard, // Plugin here
      ...
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;devMode&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;&lt;a href=&quot;https://docs.astro.build/en/guides/environment-variables/#default-environment-variables&quot;&gt;import.meta.env.DEV&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Enable or disable development mode.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;excludedUrls&lt;/td&gt;
&lt;td&gt;Array&amp;lt;string | RegExp&amp;gt;&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;A list of strings or regular expressions to exclude specific URLs from processing. This can also help prevent conflicts with other plugins.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;linkAttributes&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;{ target: &apos;&apos;, rel: &apos;&apos; }&lt;/td&gt;
&lt;td&gt;Set the target and relationship attributes for external links. These attributes can also be left unset to delegate handling to other plugins.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rewriteRules&lt;/td&gt;
&lt;td&gt;Array&amp;lt;Object&amp;gt;&lt;/td&gt;
&lt;td&gt;[]&lt;/td&gt;
&lt;td&gt;Rewrite specific metadata attributes fetched from links, such as the title and description.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;base&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;/&apos;&lt;/td&gt;
&lt;td&gt;Specify the same base path as Astro&apos;s. For details, refer &lt;a href=&quot;https://docs.astro.build/en/reference/configuration-reference/#base&quot;&gt;here&lt;/a&gt;. &lt;strong&gt;When used as an integration, if not specified, this option will be determined automatically.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;defaultThumbnail&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Path to the default thumbnail image to use when the metadata does not include image data. It should be relative to the public directory. For example, set &lt;code&gt;defaultThumbnail&lt;/code&gt; to &apos;images/default-thumbnail.jpg&apos; if the image is located at public/images/default-thumbnail.jpg.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;internalLink&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;{ enabled: false, site: &apos;&apos; }&lt;/td&gt;
&lt;td&gt;Enable internal link processing within your site.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cache&lt;/td&gt;
&lt;td&gt;Object&lt;/td&gt;
&lt;td&gt;See detailed options below.&lt;/td&gt;
&lt;td&gt;Download and cache images during the build process.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;linkAttributes&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;target&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Specify where to open linked documents. The default (empty) does not set a target on links.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rel&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Define the relationship between the current document and the linked document. The default (empty) does not set any relationship.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;rewriteRules&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;url&lt;/td&gt;
&lt;td&gt;RegExp&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;A regular expression pattern is used to match a specific URL.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rewriteSteps&lt;/td&gt;
&lt;td&gt;Array&amp;lt;Object&amp;gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Defines rewrite rules for specific metadata attributes.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Below is an example that shows how to rewrite the &quot;title&quot; and &quot;description&quot; for metadata fetched from links pointing to a GitHub repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;rewriteRules: [
  {
    url: /^https:\/\/github\.com\/[^\/]+\/[^\/]+\/?$/,
    rewriteSteps: [
      { key: &quot;title&quot;, pattern: /:.*/, replacement: &quot;&quot; },
      {
        key: &quot;description&quot;,
        pattern: /(?: (?:\. )?Contribute to (?:.+\/.+) .+\.?)|(?: - (?:.+\/.+))$/,
        replacement: &quot;&quot;,
      },
      {
        key: &quot;description&quot;,
        pattern: /^Contribute to (?:.+\/.+) .+\.?$/,
        replacement: &quot;No description provided.&quot;,
      },
    ],
  },
],
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;key&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Metadata attribute key to be rewritten.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pattern&lt;/td&gt;
&lt;td&gt;RegExp&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Regular expression pattern used to match the current value of the metadata attribute. The part of the value that matches this pattern will be replaced.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;replacement&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;String to replace the matched pattern in the metadata attribute.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;internalLink&lt;/h3&gt;
&lt;p&gt;Set &lt;code&gt;enabled&lt;/code&gt; to &lt;code&gt;true&lt;/code&gt; to enable internal link processing within your site. The &lt;code&gt;site&lt;/code&gt; and &lt;code&gt;base&lt;/code&gt; options resolve internal links to absolute URLs. &lt;strong&gt;Internal links must point to files that exist on the server.&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;enabled&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Enable or disable internal link processing.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;site&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;&apos;&lt;/td&gt;
&lt;td&gt;Specify the same deployed URL as Astro&apos;s. For details, refer &lt;a href=&quot;https://docs.astro.build/en/reference/configuration-reference/#site&quot;&gt;here&lt;/a&gt;. &lt;strong&gt;When used as an integration, if not specified, this option will be determined automatically.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;cache&lt;/h3&gt;
&lt;p&gt;The following related options allow you to easily control the caching behavior.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;enabled&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;Enable or disable caching.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;outDir&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;./dist/&apos;&lt;/td&gt;
&lt;td&gt;Output directory path.  For details, refer &lt;a href=&quot;https://docs.astro.build/en/reference/configuration-reference/#outdir&quot;&gt;here&lt;/a&gt;. &lt;strong&gt;Aligning with Astro allows you to benefit from features like image optimization.&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cacheDir&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;./link-card/&apos;&lt;/td&gt;
&lt;td&gt;Cache directory path. If &lt;code&gt;devMode&lt;/code&gt; is set to true, the final URL to the cached images will be &lt;code&gt;base + outDir + cacheDir&lt;/code&gt;. Otherwise, it will be &lt;code&gt;base + cacheDir&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maxFileSize&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Maximum file size (in bytes) to cache. Set to 0 for no limit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;maxCacheSize&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Maximum total cache size (in bytes). Set to 0 for no limit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;userAgent&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;&apos;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36&apos;&lt;/td&gt;
&lt;td&gt;Identifier included in HTTP request headers to specify the client.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;Quick and Easy Options Setup&lt;/h3&gt;
&lt;p&gt;This plugin uses &lt;code&gt;@fastify/deepmerge&lt;/code&gt; to simplify options setup.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/@fastify/deepmerge&quot;&gt;https://www.npmjs.com/package/@fastify/deepmerge&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;HTML Structure for Styling&lt;/h2&gt;
&lt;p&gt;The styles are specified in &lt;code&gt;src/styles/link-card.css&lt;/code&gt;, and the HTML is automatically generated. Below is an example structure to guide you when customizing the styles:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;link-card__container&quot;&amp;gt;
  &amp;lt;a href=&quot;https://astro.build/&quot; class=&quot;link-card&quot;&amp;gt;
    &amp;lt;div class=&quot;link-card__info&quot;&amp;gt;
      &amp;lt;div class=&quot;link-card__title&quot;&amp;gt;Astro&amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;link-card__description&quot;&amp;gt;Astro builds fast content sites, powerful web applications, dynamic server APIs, and everything in-between.&amp;lt;/div&amp;gt;
      &amp;lt;div class=&quot;link-card__metadata&quot;&amp;gt;
        &amp;lt;div class=&quot;link-card__domain&quot;&amp;gt;
          &amp;lt;img alt=&quot;favicon&quot; class=&quot;link-card__favicon&quot; src=&quot;https://www.google.com/s2/favicons?domain=astro.build&quot;&amp;gt;
          &amp;lt;span class=&quot;link-card__domain-name&quot;&amp;gt;astro.build&amp;lt;/span&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
    &amp;lt;div class=&quot;link-card__thumbnail&quot;&amp;gt;
      &amp;lt;img alt=&quot;Astro - Build the web you want.&quot; class=&quot;link-card__image&quot; src=&quot;https://astro.build/og/astro.jpg&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
  &amp;lt;/a&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Markdown Extended Features</title><link>https://blog.170529.xyz/posts/markdown-extended/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/markdown-extended/</guid><description>Read more about Markdown features in Fuwari</description><pubDate>Wed, 01 May 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;GitHub Repository Cards&lt;/h2&gt;
&lt;p&gt;You can add dynamic cards that link to GitHub repositories, on page load, the repository information is pulled from the GitHub API.&lt;/p&gt;
&lt;p&gt;::github{repo=&quot;Fabrizz/MMM-OnSpotify&quot;}&lt;/p&gt;
&lt;p&gt;Create a GitHub repository card with the code &lt;code&gt;::github{repo=&quot;&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;&quot;}&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;::github{repo=&quot;saicaca/fuwari&quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Admonitions&lt;/h2&gt;
&lt;p&gt;Following types of admonitions are supported: &lt;code&gt;note&lt;/code&gt; &lt;code&gt;tip&lt;/code&gt; &lt;code&gt;important&lt;/code&gt; &lt;code&gt;warning&lt;/code&gt; &lt;code&gt;caution&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;:::note
Highlights information that users should take into account, even when skimming.
:::&lt;/p&gt;
&lt;p&gt;:::tip
Optional information to help a user be more successful.
:::&lt;/p&gt;
&lt;p&gt;:::important
Crucial information necessary for users to succeed.
:::&lt;/p&gt;
&lt;p&gt;:::warning
Critical content demanding immediate user attention due to potential risks.
:::&lt;/p&gt;
&lt;p&gt;:::caution
Negative potential consequences of an action.
:::&lt;/p&gt;
&lt;h3&gt;Basic Syntax&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;:::note
Highlights information that users should take into account, even when skimming.
:::

:::tip
Optional information to help a user be more successful.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Custom Titles&lt;/h3&gt;
&lt;p&gt;The title of the admonition can be customized.&lt;/p&gt;
&lt;p&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;:::note[MY CUSTOM TITLE]
This is a note with a custom title.
:::
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Syntax&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;[!TIP]
&lt;a href=&quot;https://github.com/orgs/community/discussions/16925&quot;&gt;The GitHub syntax&lt;/a&gt; is also supported.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; [!NOTE]
&amp;gt; The GitHub syntax is also supported.

&amp;gt; [!TIP]
&amp;gt; The GitHub syntax is also supported.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Spoiler&lt;/h3&gt;
&lt;p&gt;You can add spoilers to your text. The text also supports &lt;strong&gt;Markdown&lt;/strong&gt; syntax.&lt;/p&gt;
&lt;p&gt;The content :spoiler[is hidden &lt;strong&gt;ayyy&lt;/strong&gt;]!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;The content :spoiler[is hidden **ayyy**]!

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Expressive Code 示例</title><link>https://blog.170529.xyz/posts/expressive-code/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/expressive-code/</guid><description>使用 Expressive Code 在 Markdown 中展示代码块效果。</description><pubDate>Wed, 10 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;这里我们将探索如何使用 &lt;a href=&quot;https://expressive-code.com/&quot;&gt;Expressive Code&lt;/a&gt; 展示代码块。以下示例基于官方文档，详情可参考官方说明。&lt;/p&gt;
&lt;h2&gt;Expressive Code&lt;/h2&gt;
&lt;h3&gt;语法高亮&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/syntax-highlighting/&quot;&gt;语法高亮&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;常规语法高亮&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;这段代码已高亮显示！&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;渲染 ANSI 转义序列&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;ANSI 颜色：
- 普通：[31m红[0m [32m绿[0m [33m黄[0m [34m蓝[0m [35m洋红[0m [36m青[0m
- 加粗： [1;31m红[0m [1;32m绿[0m [1;33m黄[0m [1;34m蓝[0m [1;35m洋红[0m [1;36m青[0m
- 变暗： [2;31m红[0m [2;32m绿[0m [2;33m黄[0m [2;34m蓝[0m [2;35m洋红[0m [2;36m青[0m

256 色（显示 160-177）：
[38;5;160m160 [38;5;161m161 [38;5;162m162 [38;5;163m163 [38;5;164m164 [38;5;165m165[0m
[38;5;166m166 [38;5;167m167 [38;5;168m168 [38;5;169m169 [38;5;170m170 [38;5;171m171[0m
[38;5;172m172 [38;5;173m173 [38;5;174m174 [38;5;175m175 [38;5;176m176 [38;5;177m177[0m

全 RGB 颜色：
[38;2;34;139;34m森林绿 - RGB(34, 139, 34)[0m

文本格式：[1m加粗[0m [2m变暗[0m [3m斜体[0m [4m下划线[0m
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;编辑器与终端框&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/frames/&quot;&gt;编辑器与终端框&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;代码编辑器框&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;Title 属性示例&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!-- src/content/index.html --&amp;gt;
&amp;lt;div&amp;gt;文件名注释示例&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;终端框&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;这个终端框没有标题&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;Write-Output &quot;这个终端有标题！&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;覆盖框类型&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;看！没有框！&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;# 默认会是终端框，这里覆盖为代码框
function Watch-Tail { Get-Content -Tail 20 -Wait $args }
New-Alias tail Watch-Tail
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文本与行标记&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/text-markers/&quot;&gt;文本与行标记&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;标记整行与行范围&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 第 1 行 - 按行号标记
// 第 2 行
// 第 3 行
// 第 4 行 - 按行号标记
// 第 5 行
// 第 6 行
// 第 7 行 - 按范围 &quot;7-8&quot; 标记
// 第 8 行 - 按范围 &quot;7-8&quot; 标记
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;选择行标记类型（mark, ins, del）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;这一行被标记为删除&apos;)
  // 这一行和下一行被标记为插入
  console.log(&apos;这是第二个插入的行&apos;)

  return &apos;这一行使用默认的中性标记类型&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;为行标记添加标签&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}
  value={value}
  className={buttonClassName}
  disabled={disabled}
  active={active}
&amp;gt;
  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;在独立行添加长标签&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// labeled-line-markers.jsx
&amp;lt;button
  role=&quot;button&quot;
  {...props}

  value={value}
  className={buttonClassName}

  disabled={disabled}
  active={active}
&amp;gt;

  {children &amp;amp;&amp;amp;
    !active &amp;amp;&amp;amp;
    (typeof children === &apos;string&apos; ? &amp;lt;span&amp;gt;{children}&amp;lt;/span&amp;gt; : children)}
&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;使用 diff 风格语法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;+这一行将被标记为插入
-这一行将被标记为删除
这是一行普通内容
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;--- a/README.md
+++ b/README.md
@@ -1,3 +1,4 @@
+这是真实的 diff 文件
-所有内容都不会被修改
 没有空白会被移除
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;结合语法高亮与 diff 风格语法&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;  function thisIsJavaScript() {
    // 整个代码块会以 JavaScript 高亮，
    // 同时可以添加 diff 标记！
-   console.log(&apos;旧代码将被移除&apos;)
+   console.log(&apos;新代码上线！&apos;)
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;标记行内指定文本&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  // 标记行内任意指定文本
  return &apos;支持多次匹配指定文本&apos;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;正则表达式&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;yes 和 yep 都会被标记。&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;转义斜杠&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;Test&quot; &amp;gt; /home/test.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;选择行内标记类型（mark, ins, del）&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;function demo() {
  console.log(&apos;这些是插入和删除标记类型&apos;);
  // return 语句使用默认标记类型
  return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;自动换行&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/key-features/word-wrap/&quot;&gt;自动换行&lt;/a&gt;&lt;/p&gt;
&lt;h4&gt;每个代码块配置自动换行&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// 自动换行示例
function getLongString() {
  return &apos;这是一个很长的字符串，除非容器非常宽，否则很可能无法完全显示&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// wrap=false 示例
function getLongString() {
  return &apos;这是一个很长的字符串，除非容器非常宽，否则很可能无法完全显示&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;配置换行缩进&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// preserveIndent 示例（默认启用）
function getLongString() {
  return &apos;这是一个很长的字符串，除非容器非常宽，否则很可能无法完全显示&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// preserveIndent=false 示例
function getLongString() {
  return &apos;这是一个很长的字符串，除非容器非常宽，否则很可能无法完全显示&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;可折叠区块&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/collapsible-sections/&quot;&gt;可折叠区块&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 这些样板代码会被折叠
import { someBoilerplateEngine } from &apos;@example/some-boilerplate&apos;
import { evenMoreBoilerplate } from &apos;@example/even-more-boilerplate&apos;

const engine = someBoilerplateEngine(evenMoreBoilerplate())

// 这部分代码默认可见
engine.doSomething(1, 2, 3, calcFn)

function calcFn() {
  // 可以有多个折叠区块
  const a = 1
  const b = 2
  const c = a + b

  // 这部分会保持可见
  console.log(`计算结果: ${a} + ${b} = ${c}`)
  return c
}

// 这些代码会再次被折叠
engine.closeConnection()
engine.freeMemory()
engine.shutdown({ reason: &apos;示例样板代码结束&apos; })
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;行号显示&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://expressive-code.com/plugins/line-numbers/&quot;&gt;行号显示&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;每个代码块显示行号&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 此代码块会显示行号
console.log(&apos;来自第 2 行的问候！&apos;)
console.log(&apos;我在第 3 行&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;pre&gt;&lt;code&gt;// 此代码块不显示行号
console.log(&apos;你好？&apos;)
console.log(&apos;你知道我在第几行吗？&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;更改起始行号&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;console.log(&apos;来自第 5 行的问候！&apos;)
console.log(&apos;我在第 6 行&apos;)
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Simple Guides for Fuwari</title><link>https://blog.170529.xyz/posts/guide/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/guide/</guid><description>How to use this blog template.</description><pubDate>Mon, 01 Apr 2024 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;Cover image source: &lt;a href=&quot;https://image.civitai.com/xG1nkqKTMzGDvpLrqFT7WA/208fc754-890d-4adb-9753-2c963332675d/width=2048/01651-1456859105-(colour_1.5),girl,_Blue,yellow,green,cyan,purple,red,pink,_best,8k,UHD,masterpiece,male%20focus,%201boy,gloves,%20ponytail,%20long%20hair,.jpeg&quot;&gt;Source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This blog template is built with &lt;a href=&quot;https://astro.build/&quot;&gt;Astro&lt;/a&gt;. For the things that are not mentioned in this guide, you may find the answers in the &lt;a href=&quot;https://docs.astro.build/&quot;&gt;Astro Docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Front-matter of Posts&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attribute&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The title of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;published&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The date the post was published.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A short description of the post. Displayed on index page.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The cover image path of the post.&amp;lt;br/&amp;gt;1. Start with &lt;code&gt;http://&lt;/code&gt; or &lt;code&gt;https://&lt;/code&gt;: Use web image&amp;lt;br/&amp;gt;2. Start with &lt;code&gt;/&lt;/code&gt;: For image in &lt;code&gt;public&lt;/code&gt; dir&amp;lt;br/&amp;gt;3. With none of the prefixes: Relative to the markdown file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tags&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The tags of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;category&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category of the post.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;draft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;If this post is still a draft, which won&apos;t be displayed.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Where to Place the Post Files&lt;/h2&gt;
&lt;p&gt;Your post files should be placed in &lt;code&gt;src/content/posts/&lt;/code&gt; directory. You can also create sub-directories to better organize your posts and assets.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;src/content/posts/
├── post-1.md
└── post-2/
    ├── cover.png
    └── index.md
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>Markdown Example</title><link>https://blog.170529.xyz/posts/markdown/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/markdown/</guid><description>A simple example of a Markdown blog post.</description><pubDate>Sun, 01 Oct 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;An h1 header&lt;/h1&gt;
&lt;p&gt;Paragraphs are separated by a blank line.&lt;/p&gt;
&lt;p&gt;2nd paragraph. &lt;em&gt;Italic&lt;/em&gt;, &lt;strong&gt;bold&lt;/strong&gt;, and &lt;code&gt;monospace&lt;/code&gt;. Itemized lists
look like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;this one&lt;/li&gt;
&lt;li&gt;that one&lt;/li&gt;
&lt;li&gt;the other one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Block quotes are
written like so.&lt;/p&gt;
&lt;p&gt;They can span multiple paragraphs,
if you like.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., &quot;it&apos;s all
in chapters 12--14&quot;). Three dots ... will be converted to an ellipsis.
Unicode is supported. ☺&lt;/p&gt;
&lt;h2&gt;An h2 header&lt;/h2&gt;
&lt;p&gt;Here&apos;s a numbered list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;first item&lt;/li&gt;
&lt;li&gt;second item&lt;/li&gt;
&lt;li&gt;third item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here&apos;s a code sample:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;define foobar() {
    print &quot;Welcome to flavor country!&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(which makes copying &amp;amp; pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import time
# Quick, count to ten!
for i in range(10):
    # (but not *too* quick)
    time.sleep(0.5)
    print i
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;An h3 header&lt;/h3&gt;
&lt;p&gt;Now a nested list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, get these ingredients:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;carrots&lt;/li&gt;
&lt;li&gt;celery&lt;/li&gt;
&lt;li&gt;lentils&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Boil some water.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Dump everything in the pot and follow
this algorithm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; find wooden spoon
 uncover pot
 stir
 cover pot
 balance wooden spoon precariously on pot handle
 wait 10 minutes
 goto first step (or shut off burner when done)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not bump wooden spoon or it will fall.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).&lt;/p&gt;
&lt;p&gt;Here&apos;s a link to &lt;a href=&quot;http://foo.bar&quot;&gt;a website&lt;/a&gt;, to a &lt;a href=&quot;local-doc.html&quot;&gt;local
doc&lt;/a&gt;, and to a &lt;a href=&quot;#an-h2-header&quot;&gt;section heading in the current
doc&lt;/a&gt;. Here&apos;s a footnote [^1].&lt;/p&gt;
&lt;p&gt;[^1]: Footnote text goes here.&lt;/p&gt;
&lt;p&gt;Tables can look like this:&lt;/p&gt;
&lt;p&gt;size material color&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;9 leather brown
10 hemp canvas natural
11 glass transparent&lt;/p&gt;
&lt;p&gt;Table: Shoes, their sizes, and what they&apos;re made of&lt;/p&gt;
&lt;p&gt;(The above is the caption for the table.) Pandoc also supports
multi-line tables:&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;keyword text&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;red Sunsets, apples, and
other red or reddish
things.&lt;/p&gt;
&lt;p&gt;green Leaves, grass, frogs
and other things it&apos;s
not easy being.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;A horizontal rule follows.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Here&apos;s a definition list:&lt;/p&gt;
&lt;p&gt;apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There&apos;s no &quot;e&quot; in tomatoe.&lt;/p&gt;
&lt;p&gt;Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)&lt;/p&gt;
&lt;p&gt;Here&apos;s a &quot;line block&quot;:&lt;/p&gt;
&lt;p&gt;| Line one
| Line too
| Line tree&lt;/p&gt;
&lt;p&gt;and images can be specified like so:&lt;/p&gt;
&lt;p&gt;Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:&lt;/p&gt;
&lt;p&gt;$$I = \int \rho R^{2} dV$$&lt;/p&gt;
&lt;p&gt;$$
\begin{equation*}
\pi
=3.1415926535
;8979323846;2643383279;5028841971;6939937510;5820974944
;5923078164;0628620899;8628034825;3421170679;\ldots
\end{equation*}
$$&lt;/p&gt;
&lt;p&gt;And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: `foo`, *bar*, etc.&lt;/p&gt;
</content:encoded></item><item><title>Include Video in the Posts</title><link>https://blog.170529.xyz/posts/video/</link><guid isPermaLink="true">https://blog.170529.xyz/posts/video/</guid><description>This post demonstrates how to include embedded video in a blog post.</description><pubDate>Tue, 01 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Just copy the embed code from YouTube or other platforms, and paste it in the markdown file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
title: Include Video in the Post
published: 2023-10-19
// ...
---

&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;YouTube&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;https://www.youtube.com/embed/5gIf0_xpFPI?si=N1WTorLKL0uwLsU_&quot; title=&quot;YouTube video player&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen&amp;gt;&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;h2&gt;Bilibili&lt;/h2&gt;
&lt;p&gt;&amp;lt;iframe width=&quot;100%&quot; height=&quot;468&quot; src=&quot;//player.bilibili.com/player.html?bvid=BV1fK4y1s7Qf&amp;amp;p=1&quot; scrolling=&quot;no&quot; border=&quot;0&quot; frameborder=&quot;no&quot; framespacing=&quot;0&quot; allowfullscreen=&quot;true&quot;&amp;gt; &amp;lt;/iframe&amp;gt;&lt;/p&gt;
</content:encoded></item></channel></rss>