<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/feeds/rss-style.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Noctis</title>
        <link>https://aojunhao.com</link>
        <description>A place for words, written by someone.</description>
        <lastBuildDate>Tue, 31 Mar 2026 08:51:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>Astro Chiri Feed Generator</generator>
        <language>zh-CN</language>
        <copyright>Copyright © 2026 aojunhao123</copyright>
        <atom:link href="https://aojunhao.com/rss.xml" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[随笔——新的旅程]]></title>
            <link>https://aojunhao.com/next-journey</link>
            <guid isPermaLink="false">https://aojunhao.com/next-journey</guid>
            <pubDate>Sat, 14 Mar 2026 12:00:40 GMT</pubDate>
            <content:encoded><![CDATA[<p>每当有什么想法的时候就该赶紧记录下来，嗯，对。</p>
<p>马上就要步入下一段工作了，世俗意义上来说我还真是个不稳定分子呢（笑</p>
<p>换工作的原因其实很简单，主要是夹杂了一些个人比较功利的考量。毕竟还是想在这个 AI 高速发展的时代能够去拼抢一些红利。而误打误撞中也确实获得了一些比较好的机会，于是思考再三后决定跳出当下的舒适圈。</p>
<p>目前这份工作其实给我的体验很棒：同事们能力在线，老板也很有自己的想法，也不会给我们施加压力（至少没怎么传导到我身上）；作为研发我也有一定的自由度去探索我感兴趣的方面。我个人层面上认为的好工作有一个很大的特点，就是它能够激发我的热情。我在写浏览器插件的时候就经常有这种时刻：这个 bug 很有趣我想修修看，这个 feature 听着还挺好玩的我来做一下…诸如此类的。当然，工作也并不总是有趣的，也总有那么些繁琐无聊但是又不得不做的部分，不过这才是常态不是吗~只要有那么一些时刻是能够给自己带来满足与成就感的，就已经弥足珍贵了啊。</p>
<p>下一份工作依旧还是远程办公，做的产品也很有趣，虽然我目前似乎还不是这类产品的受众，但谁又说得准呢~倒是有一个我比较担心的点就是害怕因为自己的二次元浓度不够而无法融入大伙（万一大家在聊赛马娘而我只听过小马宝莉怎么办</p>
<p>远程办公一年了，但我也并没有把生活过成互联网上数字游民们所展现的那样。对我来说无非就是省去了通勤的烦恼、搬到了郊区租了个性价比不错的房子、不用按部就班过着打卡上下班的日子，能够按照自己的喜好来布置生活/工作的环境。除了由于我的不自律带来的作息昼夜颠倒之外，总体上还是非常舒服的。不过对我这种低精力人群来说，边旅游边上班什么的是不存在的，只会让自己疲惫不堪好吗！</p>
<p>总之，我似乎又来到了人生的下一个节点。那么，我对自己的期许就是：保持进步，保持热情，不要被 AI 取代啊！</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[消失的 Blob | 记一次问题排查]]></title>
            <link>https://aojunhao.com/missing-blob</link>
            <guid isPermaLink="false">https://aojunhao.com/missing-blob</guid>
            <pubDate>Fri, 06 Mar 2026 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>写在前面</h2>
<p>前段时间处理了一个我觉得挺有意思的问题，于是有了想要记录下来一整个过程的冲动。趁着这股子劲儿还没散，得赶紧动笔写下来。因为我的草稿箱里已经囤积了不少想写的东西，但都是些零碎的文字片段，最终无限搁置成为了废稿（</p>
<p>回到正题，我的工作内容有一部分是处理用户反馈的工单（aka 客服）。要面对的反馈诸如：这个地方不翻译、那个功能怎么没生效、我想要你们实现 xxx…有些是用户的使用姿势不对（当然，这也可能跟我们的引导或文档不够完善有关），有些是 bug 或 Nice-to-have 的功能需求。</p>
<p>而今天我想聊的，是在适配某个漫画网站时遇到的一个 bug。</p>
<h2>漫画翻译背后</h2>
<p>某个下午，我正在处理漫画翻译相关的用户工单。一个用户反馈某个漫画网站无法翻译。于是我便点进该网站，着手相应的适配工作。在进入正题前，我想先简单聊聊浏览器扩展的漫画翻译是如何工作的。</p>
<p>从前端角度看，实现漫画翻译的逻辑并不复杂（这里特指服务端翻译，如果放在端侧去做的话会麻烦一些）。本质上我们要做的是：</p>
<ol>
<li><strong>获取原图</strong>：拿到网页中渲染的漫画图片（根据网站实现不同，可能是 <code>&lt;img&gt;</code>、<code>background-image</code> 或 <code>&lt;canvas&gt;</code>）</li>
<li><strong>图像处理</strong>：进行一系列处理流程：<code>OCR</code>（识别） → <code>Inpainting</code>（擦除） → <code>Translate</code>（翻译） → <code>Layout</code>（排版） → <code>Rendering</code>（渲染）</li>
<li><strong>回填替换</strong>：用处理好的新图片替换掉网站原图</li>
</ol>
<p>但正如我在上面提到的，仅考虑漫画翻译这个场景，不同网站之间就有不少差异。图片渲染方式的不同只是其中之一。面对着茫茫多的网站，怎么抽象出一套合理的方案来兼容各种奇奇怪怪的 case 也就成了无法绕过的一环。而我们采用的是<strong>配置驱动</strong>的方案：在浏览器扩展中内置一套基础配置，并针对特定网站编写专属配置，这些配置相互独立。如此既可以对某些热门网站进行深度的适配和优化，又能兼容不同网站间的差异。若用户有一定的前端基础，还可自行定制来完善翻译体验。并且这份配置是通过云端下发给扩展，使我们能够在<s>改坏配置</s>有需要的时候快速调整，无需发版。</p>
<h2>排查过程</h2>
<p>好了，至此需要提供的上下文已经完整。接下来讲讲我一般是怎么适配漫画网站的。我一般会先观察网页的 HTML 结构：</p>
<p><img src="https://aojunhao.com/_astro/image-20260305025752367.6cW0ZgT-_1EzGl7.webp" alt="" /></p>
<p>如上图所示，可以看到图片有一个特点：<code>src</code> 的前缀是 <code>blob:</code>。这说明网站通过 <code>URL.createObjectURL</code> 来展示图片，也意味着用户无法通过右键菜单另存/复制来获取原图。这么做主要是为了防盗链和反爬，算是一种保护内容的手段。不过浏览器扩展的 content script 与宿主页面共享同一上下文，因此这个限制对扩展来说形同虚设，直接 <code>fetch(blobUrl)</code> 即可…吗？</p>
<p>果然，不出意外还是出意外了。触发翻译时，得到了如下报错：</p>
<p><img src="https://aojunhao.com/_astro/image-20260306000403716.DmMmOy9S_Z2cVjXf.webp" alt="" /></p>
<p>嗯…fetch 失败了，会是什么原因呢？初步怀疑可能是 blob 资源被提前释放了。在 Web API 中，<code>URL.createObjectURL()</code> 创建的 URL 是临时的，一旦执行了 <code>URL.revokeObjectURL()</code>，该 URL 就会失效，对应的内存资源也会被释放。</p>
<p>但是如何验证猜想呢？大胆假设，小心求证。既然网站很大概率对图片资源进行了清理，那么就说明该网站的脚本中必然会存在执行相关逻辑的代码。这意味着只要我们能够利用 Debugger 断点到执行该操作的时刻，便能够一探究竟。现在，让我们进入分析。</p>
<p><img src="https://aojunhao.com/_astro/image-20260305182308068.Cv6vUkG5_1tByQp.webp" alt="" /></p>
<p>上图中我们可以看到，该网站的图片是做了懒加载的。视口外的 img 元素并没有 src 属性。那么就很简单了，我们可以通过 Devtool 对该图片加上 DOM 断点，触发后看 call stack。</p>
<p><img src="https://aojunhao.com/_astro/image-20260305182629816.C1scmkW__2fQflR.webp" alt="" /></p>
<p><img src="https://aojunhao.com/_astro/2bdfa60b7f3b8de2c059a7cfb2dc15b2.Cg8Pb0_t_Z1N3dMU.webp" alt="2bdfa60b7f3b8de2c059a7cfb2dc15b2" /></p>
<p>很好，答案已经出现：在网站的 JavaScript 脚本中，<code>loadImg</code> 函数是图片加载的逻辑。它通过调用 <code>URL.createObjectURL</code>来创建 blob 对象，并且在图片的 <code>onload</code> 事件中（即图片加载完成时），通过 <code>URL.revokeObjectURL</code> 释放资源。于是导致了我们漫画翻译在获取图片这一步失败了。</p>
<p>那么，我们现在已经明确了造成图片翻译失败的根因，下一步就该着手解决了（其实似乎略过这一步也行，但请原谅我的好奇心）。</p>
<p>既然图片的 blob 已经被释放，那该怎么获取原图呢？可以明确的一点是：图片本身已经被浏览器解码，渲染在 DOM 中了。所以理论上我们能够通过某种手段来获取到它的像素。于是乎，canvas 登场了。在 canvas 中有一个 <a href="https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage">drawImage 方法</a>，它提供了多种将图像绘制到画布上的方式。那我们只需要 <code>drawImage(img)</code> 到离屏 canvas，再通过 <code>canvas.toBlob()</code>，把数据喂给后端翻译接口即可。核心代码大概是这样：</p>
<pre><code class="language-typescript">function getBlobByImageElement(imgEle: HTMLImageElement): Promise&lt;Blob | null&gt; {
  return new Promise((resolve, reject) =&gt; {
    try {
      const canvas = document.createElement('canvas')
      canvas.width = imgEle.naturalWidth
      canvas.height = imgEle.naturalHeight

      const ctx = canvas.getContext('2d')
      if (!ctx) {
        reject('can not get canvas context')
        return
      }
      ctx.drawImage(imgEle, 0, 0)

      canvas.toBlob((blob) =&gt; resolve(blob), 'image/png')
    } catch (error) {
      reject(error)
    }
  })
}
</code></pre>
<p>最后让我们验证一下效果，everything works well~</p>
<p><img src="https://aojunhao.com/_astro/844c0c76-69e0-4cb5-8121-97a5472c4b41.rRMWkz_v_ZBE2T5.webp" alt="" /></p>
<h2>总结</h2>
<p>这便是我工作日常中的一个小小片段，在各种各样的坑里摸爬滚打，顺带着收获了不少知识、技巧。希望我的排查思路和过程能够对你起到一点帮助。</p>
<h2>一点题外话</h2>
<blockquote>
<p>人类的思想就是没有经过整理的无数杂念的混合。—— 《黑客与画家》</p>
</blockquote>
<p>时隔近两年，我终于再次提笔写下了一篇文章。整个过程还是挺坎坷的：从整理想法到完整表述，再到最后调整行文结构、优化措辞表述，花了几天的时间。
我重新捡起写作很大程度上是因为 AI：它正试图全面介入我们的生活，代替我们执行，甚至想替我们思考、决策。在这种大环境下，我该如何保持独立思考，培养自己的品味和判断力呢？写作是我认为的最佳手段。它迫使着我深入思考，这个过程也会催生出新的想法。</p>
<hr />
<p>最后，感谢阅读。对我来说这是一个新的开始，后续我的写作方向可能主要还是技术相关的。日常碎碎念的话<a href="/thoughts">在这里</a>。</p>
<p>如果此文也能够唤起你写作的念头，那么真是我莫大的荣幸了。</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[一文讲透 peerDependencies]]></title>
            <link>https://aojunhao.com/peerdependencies</link>
            <guid isPermaLink="false">https://aojunhao.com/peerdependencies</guid>
            <pubDate>Sat, 01 Jun 2024 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<h2>初识 <code>--legacy-peer-deps</code></h2>
<p>在前端开发中，我们肯定遇到过如下场景：</p>
<p>某一天，小豪接手了一个同事的项目，他将项目代码从gitlab上拉了下来，然后执行<code>npm install</code>大法，等待着依赖安装完毕。但是，没过多久，控制台便出现了一大片刺眼的红色：</p>
<p><img src="https://aojunhao.com/_astro/image.BwxpPjUf_12AXmd.webp" alt="_image-20240526200003496.png" /></p>
<p>小豪很清楚，这是由于版本问题导致的<strong>依赖冲突</strong>。不过，控制台的报错也给了对应的解决方案：<code>npm i --force</code>或是<code>npm i --legacy-peer-deps</code>。</p>
<p>但是，我们都知道，<code>npm i --force</code>并不是一个很好的解决方案，它会忽略冲突和警告，以及版本锁定策略（<code>package-lock.json</code>）,重新生成依赖树。并且这份新的依赖关系很可能<strong>只在你的开发环境下可以运行</strong>，既不利于团队协作，也可能影响CI/CD环境的版本不一致。从长远来看，这并不是推荐的解法。因此，小豪选择了另一个：<code>npm i --legacy-peer-deps</code>。果不其然，在执行完这行命令后，依赖被顺利的安装了下来，也没有造成依赖版本的大范围重写。</p>
<p>那么，为什么使用<code>--legacy-peer-deps</code>就可以解决上述的依赖冲突呢？它是否会引入其它的潜在问题呢？它又与我们本文要聊的<code>peerDependencies</code>又有什么关系呢？以及为什么npm要有peerDependencies这个字段呢？我们将在下文中一个个揭晓。</p>
<h2>什么是peerDependencies（同级依赖）</h2>
<p>用比较官方的话术来讲，<code>peerDependencies</code>是一种在npm包中声明的依赖关系。它用来表示当前包的消费者（即依赖该包的另一个包/项目）应该安装的依赖包版本。而当前包与当前包的消费者即构成了“同级依赖”：两个包必须共享同一个依赖版本。</p>
<p>抽象的概念并不便于理解，我们直接上代码示例：</p>
<pre><code class="language-json">{
  "name": "my-package",
  "version": "1.0.0",
  "peerDependencies": {
    "vue": "&gt;=3.0.0"
  }
}
</code></pre>
<p>如上代码意味着，使用<code>my-package</code>的项目也必须安装兼容版本的vue（版本大于等于3.0.0）。而此时vue也就是我们说的<strong>同级依赖</strong>。</p>
<p>当我们在安装项目依赖时，npm会检查<code>peerDependencies</code>与当前的项目依赖是否符合，若不符合且无法兼容则会抛出错误，并终止此次依赖安装。</p>
<h3>具体作用</h3>
<p>业务开发的前端同学可能很少会去指定<code>peerDependencies</code>字段。而<code>peerDependencies</code>也主要是为库/插件作者提供帮助。</p>
<p>我们来看如下场景：</p>
<p>某一天小豪接到了一个需求，需求的主要内容是开发一个webpack插件（我们暂且将其命名为<code>awsome-webpack-plugin</code>）。而这个插件依赖了webpack5的一些特性，因此需要确保使用<code>awsome-webpack-plugin</code>的项目安装了webpack5。于是小豪在插件包的<code>package.json</code>中添加了如下代码：</p>
<pre><code class="language-json">// awesome-webpack-plugin/package.json
{
  "name": "awesome-webpack-plugin",
  "version": "1.0.0",
  "peerDependencies": {
    "webpack": "^5.0.0"
  }
}
</code></pre>
<p>这样就避免了插件在不兼容的版本中运行时可能出现的问题。</p>
<h2>再遇<code>--legacy-peer-deps</code></h2>
<p>但是，插件的作者有时候或许不是方方面面都能考虑到的。接下来再看另一种有点特殊的情况：</p>
<p>小豪这次又接到了一个需求，同样是做webpack插件。但这次插件的功能十分简单，只是做一些简单的文本复制功能（我们暂且将其命名为<code>simple-copy-plugin</code>）。可以看出该插件其实是与webpack版本无关的。但小豪作为插件作者，深知该插件当前仅被使用在一个技术栈较老的项目中，而该项目的webpack版本是4.x。不过小豪出于稳妥起见，还是指定了同级依赖：</p>
<pre><code class="language-json">// simple-copy-plugin/package.json
{
  "name": "simple-copy-plugin",
  "version": "1.0.0",
  "peerDependencies": {
    "webpack": "^4.0.0"
  }
}
</code></pre>
<p>这样导致的后果就是，非兼容版本的项目无法安装该插件。但该插件本不应被指定版本（因为在webpack4和5中都可以正常运行）。</p>
<p>那么，怎么解决这种情况呢？回到本文的开头：<code>--legacy-peer-deps</code>！</p>
<h3>peerDependencies的处理策略</h3>
<p>在此之前，先介绍一下不同的npm版本对于<code>peerDependencies</code>的处理策略。</p>
<h4>npm 7 之前</h4>
<ul>
<li><strong>声明但不自动安装</strong>：<code>peerDependencies</code> 仅作为一种声明，npm 不会自动安装这些依赖。开发者需要手动安装并管理这些依赖。</li>
<li><strong>安装警告</strong>：如果 <code>peerDependencies</code> 没有满足要求，npm 会在安装时发出警告，但不会阻止安装过程。</li>
</ul>
<p>这种方式的优点是避免了自动安装引起的冲突，但需要开发者手动管理依赖，增加了管理的复杂性。</p>
<h4>npm 7 之后</h4>
<ul>
<li><strong>自动安装</strong>：npm 7 开始，<code>peerDependencies</code> 会自动安装。这简化了依赖管理，减少了开发者的手动操作。</li>
<li><strong>严格验证</strong>：npm 7 会严格验证 <code>peerDependencies</code> 的版本要求。如果版本不兼容，会导致安装失败，从而确保所有依赖的版本一致性。</li>
</ul>
<p>自动安装和严格验证虽然简化了依赖管理，但在遇到版本冲突时也更容易导致安装失败。</p>
<p>而<code>--legacy-peer-deps</code>参数的作用就是：告诉npm在处理 <code>peerDependencies</code> 时，采用旧版本（npm6及之前）的行为方式，忽略掉这些冲突，让依赖安装可以正常进行。</p>
<h2>依赖冲突与包管理器</h2>
<p>其实我们在日常开发中可以发现，大部分依赖报错都是在使用npm时引起的。而为什么使用yarn和pnpm则不太会发生类似的情况呢？这就不得不讲讲不同包管理器的依赖管理策略了。</p>
<h3>npm</h3>
<ul>
<li><strong>npm</strong> 尝试将所有的依赖安装在项目的根目录下，这种方式叫做“依赖扁平化”。</li>
<li>当不同的依赖需要同一个库的不同版本时，npm 可能会将某些版本安装在子节点的 <code>node_modules</code> 中。这种处理方式可能会导致依赖冲突和版本不一致的问题。</li>
<li><strong>npm v7+</strong> 引入了自动安装 <code>peerDependencies</code> 的特性，这在某些情况下会导致更多的依赖冲突和安装问题。</li>
</ul>
<h3>yarn</h3>
<h4>锁文件与一致性</h4>
<ul>
<li><strong>Yarn</strong> 强调确定性和一致性，它会创建一个 <code>yarn.lock</code> 文件来精确记录每个依赖的版本。这样确保了每次安装的依赖版本都一致。</li>
<li>Yarn 在处理 <code>peerDependencies</code> 时更为宽松，不会自动安装它们，而是要求用户在主项目中明确指定这些依赖，减少了冲突的发生。</li>
</ul>
<h4><code>package-lock.json</code></h4>
<p>这时或许有的小伙伴会说了，npm安装依赖时不是也会生成 <code>package-lock.json</code>吗？这个文件就是用来锁定依赖版本的呀！但其实很多时候我们发现，重新执行<code>npm i</code>命令时，也可能引起<code>package-lock.json</code>文件的更新。</p>
<p>因为npm是遵循语义化版本规范的，在安装依赖时，会尽可能去满足规范约束的范围内的最新版本。例如，如果 <code>package.json</code> 中指定了某个依赖的版本为 <code>^1.0.0</code>，而该依赖在更新后推出了 <code>1.1.0</code>，则 <code>npm install</code> 可能会更新到 <code>1.1.0</code>。</p>
<p>如果想让npm完全按照 <code>package-lock.json</code>去安装依赖，可以选择<code>npm ci</code>，但该命令一般多用于CI/CD环境。本地开发拉取依赖时不推荐该做法。</p>
<p><strong>yarn在这方面做的就比npm好很多，完全按照 <code>yarn.lock</code> 文件记录的版本去安装对应依赖。</strong></p>
<h4>依赖树优化</h4>
<ul>
<li>Yarn 的依赖树结构优化较好，通过更好的算法来处理依赖关系，减少版本冲突和重复安装的问题。</li>
</ul>
<h3>pnpm</h3>
<h4>全局存储（store）+ 硬链接 + 符号链接：</h4>
<ul>
<li><strong>全局存储（内容寻址）</strong>：pnpm 会把下载到的包文件放进全局的 store（按内容哈希组织）。多个项目/多个版本范围只要最终解析到同一份文件内容，就可以复用这份 store 文件，节省磁盘空间。</li>
<li><strong>硬链接（或 reflink）复用文件</strong>：安装时，pnpm 通常不会把 store 里的文件“symlink 到项目里”，而是把 store 里的文件**硬链接（hardlink）**到项目的“虚拟仓库”目录（一般是 <code>node_modules/.pnpm/</code>）里；因此你在项目里看到的是真实文件（硬链接指向同一个 inode/内容），读取性能好、也更符合很多工具对“真实文件”的预期。</li>
<li><strong>最外层用符号链接（symlink）拼装依赖图</strong>：项目最外层的 <code>node_modules/&lt;pkg&gt;</code> 往往是一个 <strong>symlink</strong>，它指向 <code>node_modules/.pnpm/&lt;pkg&gt;@&lt;version&gt;/node_modules/&lt;pkg&gt;</code>。pnpm 用这种方式把“包本体”放在 <code>.pnpm</code> 里，再用 symlink 在顶层把依赖关系“接出来”。</li>
</ul>
<h4>严格的模块隔离</h4>
<ul>
<li><strong>模块隔离（更接近 Node 的真实解析规则）</strong>：pnpm 的 <code>node_modules/.pnpm/&lt;pkg&gt;@&lt;version&gt;/node_modules/</code> 下，只会放该包的<strong>直接依赖</strong>（以及它们继续向下的依赖）。这会让“没声明在 dependencies 里却能被 require/import 到”的情况更容易暴露出来。</li>
<li><strong>减少幽灵依赖与版本冲突</strong>：因为依赖不会被无差别提升（hoist）到顶层，项目里不太容易出现“某个包意外用到了别的包的依赖版本”的幽灵依赖问题；同一个依赖的不同版本也能更清晰地共存。</li>
</ul>
<h2>总结</h2>
<p>对于大型项目和复杂依赖，还是推荐使用<code>pnpm</code>和<code>yarn</code>进行管理。尤其是<code>pnpm</code>，不仅仅在规避依赖冲突上有着很大的优势，在节省磁盘空间、依赖安装速度、天然支持Monorepo等方面都有得天独厚的优势。</p>
]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Hello World]]></title>
            <link>https://aojunhao.com/hello-world</link>
            <guid isPermaLink="false">https://aojunhao.com/hello-world</guid>
            <pubDate>Sat, 01 Jan 2000 00:00:00 GMT</pubDate>
            <content:encoded><![CDATA[<p>import Console from ‘@/components/ui/Console.astro’</p>
<pre><code class="language-javascript">const compose =
  (...fns) =&gt;
  (x) =&gt;
    fns.reduceRight((v, f) =&gt; f(v), x)

const formatMessage = (target) =&gt; `Hello, ${target}! 🚀`

const genStyle = () =&gt;
  [
    'color: #fff',
    'background: linear-gradient(45deg, #ff00cc, #3333ff)',
    'padding: 5px 10px',
    'border-radius: 4px',
    'font-weight: bold'
  ].join(';')

const printStyled = (message) =&gt; console.log(`%c${message}`, genStyle())

const greet = compose(printStyled, formatMessage)

greet('World')
</code></pre>

  <div class="prompt">
    <span class="styled-output">Hello, World! 🚀</span>
  </div>
]]></content:encoded>
        </item>
    </channel>
</rss>