<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet href="/_style/default.xsl" type="text/xsl"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0">
<channel>
<generator><![CDATA[Typlog (https://typlog.com/)]]></generator><pubDate>Sun, 12 Apr 2026 01:32:26 +0000</pubDate><atom:link href="https://pubsubhubbub.appspot.com/" rel="hub"/><atom:link href="https://lepture.com/feed.xml" rel="self" type="application/rss+xml"/>
<title><![CDATA[Just lepture]]></title><description><![CDATA[Love its people, but never trust its government.]]></description><link>https://lepture.com/</link><copyright><![CDATA[Copyright 2017 Just lepture]]></copyright><image><url>https://i.typlog.com/lepture/8443372661_918495.png?x-oss-process=style/sl</url><title><![CDATA[Just lepture]]></title><link>https://lepture.com/</link></image><item><title><![CDATA[我用 AI 造新語]]></title><guid>https://lepture.com/zh/2026/new-language-by-ai</guid><link>https://lepture.com/zh/2026/new-language-by-ai</link><pubDate>Sat, 17 Jan 2026 09:53:21 +0000</pubDate><content:encoded><![CDATA[<p>我想，应该有很多人和我一样，都尝试过让 AI 来创建一门新语言吧。今天来分享一下我和 AI 深度对话后的产物——<strong>Nusila</strong>。你可以把它当作无聊时的戏作，亦可以用这门语言来创作自己的科幻小说。</p>
<p>起因是我给 AI 投喂了这样一个设定：</p>
<div class="blockquote"><blockquote><p>假设人类殖民外星，这个外星上的人类打算独立，他们想要统一用同一门语言，但是各个民族都支持用自己民族的语言，大家没有谈拢，于是他们打算设计一门新语言来解决争端。这门语言应该没有各种语言的历史包袱，它应该是现代的、成年人易学的、无发音特权阶层的语言。同时考虑到各民族的情感，借词时可以考虑从各种语言里面参考一下。</p>
</blockquote></div>
<p>经过多轮逻辑审计和“语言宪法”的约束，我们最终确定了一套逻辑严密、类似编程代码的粘着语架构。</p>
<p>这个语言名字也是 AI 命名的，根据构词法来理解，<code>Nu-</code> 是新的意思，<code>-sila</code> 是语言，所以 Nusila 的意思就是新语言。</p>
<hr />
<h2>一、 语言宪法：新世界共识架构</h2>
<p>《Nusila 简明宪法》不仅是语法书，更是这个虚构社会的“底层协议”。</p>
<div class="blockquote"><blockquote><p><strong>立宪宣言</strong>：在 Nusila 中，没有含糊其辞，没有阶级枷锁。每一个音节都是为了让思想更自由地在星际间传递。</p>
</blockquote></div>
<h3>第一章：核心哲学</h3>
<ul>
<li><strong>中立性</strong>：Nusila 旨在消除历史包袱，不偏袒任何地球母语。</li>
<li><strong>易学权</strong>：成年人必须能在 3 个月内掌握交流，<strong>逻辑性永远优于习惯性</strong>。</li>
</ul>
<h3>第二章：语音铁律</h3>
<ul>
<li><strong>极简音位</strong>：全语仅限 <strong>11 个辅音</strong> (p, t, k, m, n, s, f, h, l, w, y) 与 <strong>5 个元音</strong> (a, e, i, o, u)。</li>
<li><strong>辅元(CV)结构</strong>：每个音节必须是“一辅一元”（如 Mi, Sa）。严禁辅音堆叠，严禁以辅音收尾，词根内部也必须严格遵守 CV-CV。</li>
<li><strong>一符一音</strong>：拼写与发音绝对 100% 对应。</li>
</ul>
<h3>第三章：语法逻辑</h3>
<ul>
<li><strong>零例外原则</strong>：100% 规则化，不存在任何“不规则变位”。</li>
<li><strong>词性激活接口</strong>：词根必须通过后缀 <code>-a</code> (名), <code>-i</code> (动), <code>-o</code> (形) 激活。</li>
<li><strong>双元音防火墙</strong>：第一个出现的双元音（如 <code>ai</code>, <code>oi</code>）是词根与语法的分界线，实现流式解析。</li>
</ul>
<h3>第四章：词汇与平权</h3>
<ul>
<li><strong>绝对平权</strong>：取消性别、阶级代词。</li>
<li><strong>生物/技术界限</strong>：代词强制区分生物智能 <code>Lo</code> (或 Lona) 与机器智能 <code>Ma</code> (或 Loma)，确保生存指令安全。</li>
<li><strong>群体边界</strong>：区分包含式“咱们”(<code>Miyu</code>) 与排除式“我们”(<code>Milo</code>)。</li>
</ul>
<h3>第五章：多模态输出</h3>
<p>Nusila 天生支持四种接口：<strong>拉丁接口</strong>（速记）、<strong>音节方块</strong>（几何视觉文字）、<strong>线性特征码</strong>（二进制流）和<strong>战术手势</strong>（真空/无声通讯）。</p>
<hr />
<h2>二、 语言细节：积木式构词</h2>
<h3>词根与后缀</h3>
<p>Nusila 的词根是静态的，必须通过接口“激活”：</p>
<ul>
<li><strong>-a</strong>：名词化标记 -&gt; <code>Mita-a</code> (眼睛/视线)</li>
<li><strong>-i</strong>：动词化标记 -&gt; <code>Mita-i</code> (看)</li>
<li><strong>-o</strong>：形容词化标记 -&gt; <code>Mita-o</code> (视觉的)</li>
<li><strong>-u</strong>：副词化标记 -&gt; <code>Mita-u</code> (视觉上地)</li>
</ul>
<h3>动词详解</h3>
<p>Nusila 的动词后缀挂载顺序严格固定，像函数调用一样精准：</p>
<p><strong><code>[词根] + [接口 -i] + [语态] + [体] + [语气] + [时态]</code></strong></p>
<div class="block-table"><table><thead>
<tr>
  <th style="text-align:left">功能分类</th>
  <th style="text-align:left">后缀 (CV)</th>
  <th style="text-align:left">含义</th>
</tr>
</thead>
<tbody>
<tr>
  <td style="text-align:left"><strong>语态 Voice</strong></td>
  <td style="text-align:left"><code>-lo</code> / <code>-ki</code> / <code>-ye</code></td>
  <td style="text-align:left">被动 / 使动 / <strong>相互(自复)</strong></td>
</tr>
<tr>
  <td style="text-align:left"><strong>体 Aspect</strong></td>
  <td style="text-align:left"><code>-sa</code> / <code>-se</code></td>
  <td style="text-align:left">完成 (■) / 进行 (≈)</td>
</tr>
<tr>
  <td style="text-align:left"><strong>语气 Mood</strong></td>
  <td style="text-align:left"><code>-pi</code> / <code>-mu</code> / <code>-ka</code></td>
  <td style="text-align:left">可能 (?) / 想要 (♥) / <strong>必须 (!)</strong></td>
</tr>
<tr>
  <td style="text-align:left"><strong>时态 Tense</strong></td>
  <td style="text-align:left"><code>-pa</code> / <code>-nu</code> / <code>-fi</code></td>
  <td style="text-align:left">过去 («) / 现在 (·) / 将来 (»)</td>
</tr>
</tbody>
</table></div><p><strong>极限压力测试：</strong></p>
<p><code>Mitailoyekapinu</code>：意为“（咱们）可能不得不被迫互相观察了”。</p>
<div class="blockquote"><blockquote><p><code>Mita</code> (看) - <code>i</code> (动) - <code>lo</code> (被) - <code>ye</code> (互相) - <code>ka</code> (必须) - <code>pi</code> (可能) - <code>nu</code> (现在)</p>
</blockquote></div>
<hr />
<h2>三、 句法：核心信息优先</h2>
<h3>定语后置</h3>
<p>名词永远在前，形容词在后（如：<code>Wafaa samao</code>，水 洁净的）。</p>
<ul>
<li><strong>抗噪性</strong>：在极端环境下，先听到“水”比先听到“洁净的”更能让大脑快速定位。</li>
<li><strong>流式处理</strong>：大脑不需要缓存形容词，读到哪，理解到哪。</li>
</ul>
<h3>状语的前置与后置</h3>
<p>副词接口 <code>-u</code> 的位置决定了语义侧重：</p>
<ul>
<li><strong>前置</strong>（动词前）：强调动作的<strong>方式或性质</strong>。<code>Mi fusu-u sapai-nu</code> (我快速地宣告)。</li>
<li><strong>后置</strong>（句尾）：强调动作的<strong>结果或程度</strong>。<code>Mi sapai-nu fusu-u</code> (我宣告得很快)。</li>
</ul>
<h3>逻辑连接词 (H-族)</h3>
<p>为了避免和语法后缀冲突，逻辑词统一由 <code>H</code> 开头：</p>
<ul>
<li><strong>Ha</strong>(和), <strong>He</strong>(但), <strong>Ho</strong>(因), <strong>Hu</strong>(或), <strong>Hi</strong>(若)。</li>
<li><strong>Te / Tu</strong>：逻辑括号。用 <code>Te</code> 开启从句，<code>Tu</code> 关闭，彻底解决长句嵌套歧义。</li>
</ul>
<hr />
<h2>四、 数字与疑问系统</h2>
<h3>数字系统 (0-9)</h3>
<p>单音节设计，兼顾全球权重与抗噪辨识度：</p>
<div class="blockquote"><blockquote><p><strong>Ne</strong>(0), <strong>Mo</strong>(1), <strong>Wo</strong>(2), <strong>Ta</strong>(3), <strong>Fo</strong>(4), <strong>Wu</strong>(5), <strong>Lu</strong>(6), <strong>Si</strong>(7), <strong>Ti</strong>(8), <strong>Ku</strong>(9)。</p>
</blockquote></div>
<h3>疑问体系 (Wa- 通配符)</h3>
<p>采用“通配符原位原则”，不改变语序：</p>
<ul>
<li><strong>Wapona</strong> (谁 = 疑问+人)</li>
<li><strong>Wamota</strong> (什么 = 疑问+物)</li>
<li><strong>Wanuta</strong> (何时 = 疑问+时间)</li>
<li><strong>We</strong> (是否 = 句首是非问助词)</li>
</ul>
<hr />
<h2>五、 AI 沟通时产生的词汇</h2>
<div class="block-table"><table><thead>
<tr>
  <th style="text-align:left">类别</th>
  <th style="text-align:left">成员</th>
</tr>
</thead>
<tbody>
<tr>
  <td style="text-align:left"><strong>代词</strong></td>
  <td style="text-align:left">Mi (我), Yu (你), Lo (人), Ma (机)</td>
</tr>
<tr>
  <td style="text-align:left"><strong>逻辑</strong></td>
  <td style="text-align:left">Ha (和), He (但), Ho (因), Hi (若), Ke/So (所以/那么)</td>
</tr>
<tr>
  <td style="text-align:left"><strong>词根</strong></td>
  <td style="text-align:left">Pona (人), Mosa (机), Sila (语), Nuta (时)</td>
</tr>
<tr>
  <td style="text-align:left"><strong>词根</strong></td>
  <td style="text-align:left">Wafa (水), Foha (能), Loka (位), Lifa (自), Nufa (独), Falo (错)</td>
</tr>
</tbody>
</table></div><hr />
<h2>结语：Yu Faloinu!</h2>
<p>事实上，在 AI 设计语言的过程中，他自己经常不遵守我们定下的宪法，总是要我提醒他。对于 AI 犯错，我们在 Nusila 中就可以说：Yu Faloinu。Yu 是指你，falo 是词根错误，i 表示动词，nu 表示正在进行，合起来就是你正在犯错。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8231356869_029484.svg" alt="独立宣言" /></figure></div><div class="blockquote"><blockquote><p>Milo sapai-nu Nufaa
我们宣告独立</p>
</blockquote></div>
<hr />
<p>后续如果我还有兴趣的话，可能需要建立一个 GitHub 仓库来记录了。</p>
]]></content:encoded></item><item><title><![CDATA[丟失的表達欲]]></title><guid>https://lepture.com/zh/2025/loss-of-self-expression</guid><link>https://lepture.com/zh/2025/loss-of-self-expression</link><pubDate>Fri, 15 Aug 2025 15:19:41 +0000</pubDate><content:encoded><![CDATA[<p>錢鍾書說我們常把自己的寫作衝動誤認為自己的寫作才能，我倒以為這很好，因為我自己連寫作的衝動都沒有了。不談寫作，只是隨便說點什麼亦鮮矣。博客有兩年沒有更新了，社交網絡經常好幾個月一言不發，也不怎麼與人聊天，越發自閉了。</p>
<p>這當然很不好。可是究竟要說點什麼呢，又感覺沒什麼好說的。我也試過寫點週記，談談一週見聞或所做之事，卻往往不見下一週。大抵因為下一週乏善可陳，於是不了了之。不然嘗試寫一下月記？</p>
<p>細細思索，這兩年是不是沒做什麼事呢？</p>
<p>不然。</p>
<ul>
<li>在開源項目上，我將 Authlib 的 JOSE 功能拆分出來生成了 <a href="https://jose.authlib.org/en/">joserfc</a> 這個庫，斷斷續續已經發佈到了 <code>1.2.2</code> 版本。</li>
<li>設計了一個 Sphinx 的主題 <a href="https://shibuya.lepture.com/">Shibuya</a>，上面的 joserfc 的文檔就是用的這個主題。</li>
<li>Typlog 在重新設計 V4，目前自測中。</li>
</ul>
<p>以前，我還會寫文章介紹一下我的項目。現在這些項目我竟沒有去介紹一下，想想真是不應該。</p>
<p>除了介紹自己的項目，還能寫點什麼呢？近來有什麼所思所想，又有什麼洞見或者觀察？似乎沒有。這大約就是所謂的咸魚人生吧。意識到了，似乎就應該翻一下身，來曬曬另一面。</p>
]]></content:encoded></item><item><title><![CDATA[註冊郵箱攻擊]]></title><guid>https://lepture.com/zh/2023/signup-attack</guid><link>https://lepture.com/zh/2023/signup-attack</link><pubDate>Sun, 01 Oct 2023 10:19:39 +0000</pubDate><content:encoded><![CDATA[<p>我開發了一個博客和播客托管服務 <a href="https://typlog.com">Typlog</a>，目前你看到的這個網站就是托管在 Typlog 上的。今天給大家分享一個運營 Typlog 的慘痛教訓，希望能幫助其他的開發者。</p>
<p>前幾天有個客戶發郵件給 Typlog，來咨詢一些問題。我回復後，過了好幾天也沒收到客戶的反饋。於是我又用自己的私人郵件去問客戶有沒有收到我的回復，答案是沒有。我有點不好的預感，於是用 Typlog 的郵箱給自己的私人郵箱發了一封郵件，不出意外地進入了垃圾箱。我們的郵箱設置沒有任何問題，DKIM、SPF、DMARC 都正常設置好了，Gmail 也全部給我們標記為 &quot;PASS&quot; 了，但是這封郵件就是進入了垃圾箱。</p>
<p>我的預感沒有錯，而且我也意識到問題出在了哪裡。進入 AWS SES 後台查看了一下，域名的口碑(domain reputation)出了問題。Typlog 的聯絡郵箱使用的是 Google Workspace，通知郵件是使用的是 SES，他們都是使用的 typlog.com 這個域名。通知郵件服務導致域名出了口碑問題，影響到了聯絡郵箱。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8303848933_872489.jpg" alt="Complaint rate" /></figure></div><p>為何我們的通知郵件服務會出問題呢？一直以來我都忽略了一個問題，我很早的時候就注意到 Typlog 有很多的垃圾註冊，但是我沒有太在意。一是我們的註冊接口是有頻率限制的，他也註冊不了多少；二是我們是一個付費服務，他註冊了不激活也做不了什麼事。直到 Typlog 的聯絡郵箱出了問題我才意識到我忽略了什麼。</p>
<p>因為註冊的時候，系統會發送激活郵件給用戶。如果你並沒有註冊某個服務，但是你收到了註冊郵件，你有可能會將這封郵件標記為垃圾郵件。當標記的人多了，郵件商便會認為這個域名是個發垃圾郵件的域名。即使沒有人標記垃圾郵件，但是你的郵箱經常發送郵件到不存在的郵箱地址，郵件商也會認為這個域名是個發垃圾郵件的域名。而且還有找回密碼這個接口，也是會發送郵件的。這樣就導致你發送的郵件數量更多了。更有甚者，有的機器人還會點擊激活鏈接。也許這個機器人還順便舉報了一下垃圾郵件呢。</p>
<p>你開發了一個服務，運營了一段時間，總是會遇到一些黑產機器人的。我還是沒有理解為什麼會有這些垃圾註冊機器人，他們會通過什麼方式為黑產者生產利益呢？</p>
<p>不幸中的萬幸，Typlog 的郵件列表服務是另一個域名，沒有影響到用戶的郵件列表功能。</p>
<p>現在我需要重建 Typlog 的域名口碑，這會是一個漫長的過程。下面是我的計劃：</p>
<ol>
<li>首先要防止機器人的垃圾註冊，我給註冊賬戶和找回密碼頁面添加了 <a href="https://www.cloudflare.com/products/turnstile/">Turnstile</a> 驗證。</li>
<li>將通知郵箱和聯繫郵箱的域名分開。</li>
<li>在註冊頁面添加文字說明，讓用戶檢查一下垃圾箱，以便用戶能正常註冊</li>
<li>尋找朋友們的幫助，用 Typlog 的聯絡郵箱和朋友們交流，請他們標記這封郵件不是垃圾郵件</li>
</ol>
<p>如果你有更好的建議，歡迎給我留言。也希望這篇文章能幫到新的開發者。如果我再開發一個新產品的話，我會在一開始就執行如下計劃：</p>
<ul>
<li>不要使用 root domain 作為通知郵箱，比如域名是 example.com，可以使用 noreply@accounts.example.com 作為通知郵箱</li>
<li>要用驗證碼服務，比如 Turnstile，hCaptcha，reCaptcha 之類的</li>
<li>關注產品的各種狀態</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[個人域名郵箱免費方案]]></title><guid>https://lepture.com/zh/2023/free-email-provider</guid><link>https://lepture.com/zh/2023/free-email-provider</link><description><![CDATA[使用 Cloudflare Email Routing，AWS SES 和 Gmail 創建免費的個人域名郵箱]]></description><pubDate>Sun, 20 Aug 2023 07:30:07 +0000</pubDate><content:encoded><![CDATA[<p>Cloudflare Email Routing 這項服務推出來有一段時間了。今日，我將個人郵箱 (me@lepture.com) 從 Google Workspace 轉到了 Cloudflare Email Routing，每月節省了 6 美金。</p>
<p>我們知道 Cloudflare Email Routing 可以接收郵件然後轉發到你指定的另一個郵箱，但是並不能發送郵件。這樣的話，我們就不能使用自己的個人域名郵箱了。所以要搭配一個方案，讓我們可以用自己的域名發郵件。所以我另外註冊了一個 Gmail 來發郵件。</p>
<p>下面是我遷移的整個流程：</p>
<h2>備份郵件</h2>
<p>首先我們需要備份個人域名郵箱里的郵件，如果你沒有什麼重要的郵件的話，也可以不備份。不過最好還是備份一下。這裡我選擇將郵件導入到我新註冊的 Gmail 里。如果你已經有一個 <code>@gmail.com</code> 的郵箱了，這一步可以省略。不過我還是註冊了一個全新的 Gmail，這樣比較清爽。</p>
<p>進入 Gmail 的設置裡面，選擇 <strong>Accounts and Import</strong>，在下面可以找到 <strong>Check mail from other accounts</strong>，點擊 <strong>Add a mail account</strong>。在彈出的對話框里輸入自己的郵箱，按照引導會進入填寫 POP 服務的頁面。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/8307487731_322001.jpg" alt="Sync emails" /></figure><figure><img src="https://i.typlog.com/lepture/8307487022_940852.png" alt="Add mail account" /></figure></div>
<p>按照<a href="https://support.google.com/mail/answer/7104828?hl=zh-Hans">官方文檔</a>，你需要填寫的信息：</p>
<ul>
<li>用戶名：你的郵箱</li>
<li>密碼：你的郵箱密碼</li>
<li>POP 服務器：pop.gmail.com</li>
<li>端口：995</li>
<li>要求 SSL：是</li>
</ul>
<section class="admonition important">
<p class="admonition-title">Important</p>
<p>你也許無法成功添加郵箱賬戶。這裡可能會出現錯誤提示：</p>
<p>Server denied POP3 access for the given username and password.</p>
</section>
<p>我們需要先開啓個人域名郵箱（Google Workspace Gmail）里的 POP3 功能。到你的個人郵箱設置里，選擇 <strong>Forwarding and POP/IMAP</strong>，然後開啓 POP 服務。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307486563_982321.jpg" alt="Enable POP" /></figure></div><p>但是可能還是無法成功，這是因為 Gmail 的安全機制導致的。雖然官方文檔里說密碼使用郵箱的登錄密碼就可以了，但其實不行。這裡有兩個解決方案：</p>
<ol>
<li>使用 App password</li>
<li>修改賬戶安全等級</li>
</ol>
<p>在下面的章節里會介紹如何創建 App password，這裡先介紹一下修改賬戶安全等級。因為我們這個 Google workspace 賬戶最終是要銷戶的，所以修改賬戶安全等級並沒有什麼影響。進入<a href="https://myaccount.google.com/security">賬戶安全中心</a>，激活 <strong>Less secure app access</strong> 即可。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307485881_938867.png" alt="Less secure app access" /></figure></div><p>再次嘗試添加 POP 郵箱賬戶，應該就會成功了。你需要等待一段時間才能備份完所有郵件。也許睡一覺，明天再繼續？</p>
<h2>開啓 Cloudflare Email Routing</h2>
<p>當郵件備份完後就可以開啓 Cloudflare Email Routing 了。進入 Cloudflare，選擇你的域名，進入 Email Routing 設置。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307485590_671038.jpg" alt="Email Routing" /></figure></div><p>比如你註冊的 Gmail 叫 <code>example@gmail.com</code>，你的個人域名郵箱是 <code>me@example.com</code>，上面的表單可以填寫：</p>
<ul>
<li>Custom address: <code>me</code></li>
<li>Destination: <code>example@gmail.com</code></li>
</ul>
<p>然後根據 Cloudflare 的提示，三步後就可以開啓 Cloudflare Email Routing 了。現在，當別人發郵件給 <code>me@example.com</code> 時，你的 <code>example@gmail.com</code> 郵箱就會收到郵件了。</p>
<h2>個人域名發件人</h2>
<p>最後我們需要讓發件人使用我們自己的域名。這一步與上面的備件郵件有些類似。進入 Gmail 的設置中心，選擇 <strong>Accounts and Import</strong>。在 <strong>Send mail as</strong> 里選擇添加一個新郵箱。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307485148_297025.jpg" alt="Send mail as" /></figure></div><p>比如我添加自己的郵箱 <code>me@lepture.com</code>，根據提示一步一步走，會進入最後的 SMTP 服務器設置：</p>
<h3>使用 AWS SES</h3>
<section class="admonition warning">
<p class="admonition-title">Warning</p>
<p>如果你還沒有開通 AWS SES，建議試試其他 SMTP，比如 Alibaba Direct Mail。因為 AWS SES 默認為 sandbox 環境，需要申請才能開通生產環境，但是有可能申請不通過。</p>
</section>
<p>之前介紹的 Gmail SMTP 方案（在文章最下面）沒有 DKIM 驗證，會導致郵件進入垃圾箱。我們可以使用其他的 SMTP 服務來發送郵件。比如 AWS SES，因爲 SES 會提供每個月 3000 封免費郵件，這對於個人用戶來説絕對夠用了。</p>
<p>首先你需要注冊一個 AWS 賬戶，這裏就不介紹了。注冊後，到 SES Dashboard 裏面添加自己的域名，比如我的：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307464387_801556.png" alt="SES" /></figure></div><p>下一步，設置 DNS，讓 SES 支持 DKIM 認證。你將需要設置類似如下的 CNAME。注意，<strong>在 Cloudflare 裏面設置 CNAME 時要關閉 Proxy</strong>。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307463952_243525.png" alt="Setup DNS" /></figure></div><p>接下來，我們為這個域名創建一個 SMTP 登錄賬戶：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307463704_46915.png" alt="Create SMTP credentials" /></figure></div><p>只需要一步一步跟著 AWS 的步驟來，你就能創建出 SMTP 登錄賬戶和密碼。之後，再將這個 SMTP 賬戶登記到 Gmail 裏面就可以了。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307462591_68517.png" alt="Add AWS SES SMTP credentials" /></figure></div><p>我們需要設置：</p>
<ol>
<li>SMTP Server: <code>email-smtp.us-west-2.amazonaws.com</code></li>
<li>Port: 465</li>
<li>Username：AWS SMTP Username</li>
<li>Password：AWS SMTP Password</li>
</ol>
<p>完成後，可以在設置頁面里將個人域名郵箱設置為默認發件人。</p>
<p>另外，你還可以設置一下 MAIL FROM 域名，如果沒有設置的話，收件人會看到你發的郵件 <code>mailed-by: us-west-2.amazonses.com</code>。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307460439_331902.png" alt="MAIL FROM" /></figure></div><h3>使用其他 SMTP</h3>
<p>除了 AWS SES，還有其他 SMTP 服務選擇。比如 SendGrid，比如 Alibaba Direct Mail。</p>
<ul>
<li><a href="https://sendgrid.com/pricing/">SendGrid</a> 提供每天 100 封免費郵件。</li>
<li><a href="https://www.alibabacloud.com/tc/product/directmail/pricing">Alibaba Direct Mail</a> 每個阿里雲主帳戶每日可獲 200 封免費郵件發送額度。</li>
</ul>
<h2>測試</h2>
<p>最後，我們使用自己的域名郵箱發送一封郵件試試。下面是我收到的測試郵件示例：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307462892_008056.png" alt="Received email" /></figure></div><p>可以看到，SPF、DKIM 都成功了。</p>
<hr />
<p>下面是使用 Gmail SMTP 的方案，請謹慎使用。</p>
<section class="admonition warning">
<p class="admonition-title">Warning</p>
<p>這個方案有缺陷，沒有 DKIM 認證，只能保證 SPF pass。</p>
</section>
<p>這裏我們可以直接使用當前的 Gmail 賬戶，比如 <code>example@gmail.com</code>。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307484834_1006365.png" alt="smtp.png" /></figure></div><p>我們需要設置：</p>
<ol>
<li>SMTP Server: smtp.gmail.com</li>
<li>Port: 587</li>
<li>Username：你的 Gmail 郵箱（就是你當前在用的這個郵箱）</li>
<li>Password：等一下，這裡需要創建一個 App password</li>
</ol>
<p>在上面的最後一步需要填寫一個密碼，由於 Gmail 的安全機制，你不能直接填寫賬戶密碼，這裡需要使用一個 App password。</p>
<ol>
<li>進入<a href="https://myaccount.google.com/security">賬戶安全中心</a></li>
<li>激活兩步驗證（2-Step Verification)：</li>
</ol>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307484528_354437.jpg" alt="2-Step Verification" /></figure></div><p>激活後，進入 2-Step Verification，在頁面的最下面有一個 <strong>App passwords</strong>，進入後創建一個 Mail 的 App password 即可。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8307484370_11209.png" alt="App passwords" /></figure></div><p>然後在密碼填寫框里使用這個生成的密碼即可。完成後，可以在設置頁面里將個人域名郵箱設置為默認發件人。</p>
]]></content:encoded></item><item><title><![CDATA[Markdown on ruby markup]]></title><guid>https://lepture.com/en/2022/markdown-ruby-markup</guid><link>https://lepture.com/en/2022/markdown-ruby-markup</link><description><![CDATA[Design a better syntax of ruby markup for Markdown]]></description><pubDate>Thu, 14 Jul 2022 13:44:30 +0000</pubDate><content:encoded><![CDATA[<p>The <code>&lt;ruby&gt;</code> HTML element represents small annotations that are rendered above, below, or next to base text, usually used for showing the pronunciation of Japanese and Chinese characters. There is a <a href="https://talk.commonmark.org/t/proper-ruby-text-rb-syntax-support-in-markdown/2279"><code>&lt;ruby&gt;</code> markup syntax discussion</a> in the CommonMark Discussion, among which, I prefer the <a href="https://juanitofatas.com/series/ruby/markdown-ruby-markup">syntax designed by JuanitoFatas</a>.</p>
<div class="block-code"><pre><code>[漢字(かんじ)]
[漢字(かんじ)](https://jisho.org/search/漢字)</code></pre></div>
<p>The benefits are obvious:</p>
<ol>
<li>The markup looks pretty and easy to understand</li>
<li>It works well together with links</li>
</ol>
<p>However, the syntax for separated ruby annotations is a little complex:</p>
<div class="block-code"><pre><code>[[漢(かん)][字(じ)]](https://jisho.org/search/漢字)</code></pre></div>
<p>While in <a href="https://github.com/lepture/mistune">mistune</a> <strong>v3</strong>, I've added a ruby plugin for multiple annotations with the below syntax:</p>
<div class="block-code"><pre><code>[漢(ㄏㄢˋ)字(ㄗˋ)]
[漢(かん)字(じ)](https://jisho.org/search/漢字)</code></pre></div>
<p>You can group all ruby texts in <code>[]</code> which can reduce the use of punctuation characters.</p>
<p><a href="https://typlog.com/">Typlog</a> has been updated with mistune v3, you can use ruby syntax right now in Typlog. Here are the rendered results with the ruby markup syntax:</p>
<p><ruby>漢<rt>ㄏㄢˋ</rt></ruby><ruby>字<rt>ㄗˋ</rt></ruby> with zhuyin, <ruby>漢<rt>hàn</rt></ruby><ruby>字<rt>zì</rt></ruby> with pinyin, and <ruby>漢<rt>かん</rt></ruby><ruby>字<rt>じ</rt></ruby> with hiragana.</p>
]]></content:encoded></item><item><title><![CDATA[Display country flags emoji on Windows]]></title><guid>https://lepture.com/en/2022/windows-country-flags-emoji</guid><link>https://lepture.com/en/2022/windows-country-flags-emoji</link><description><![CDATA[Fix country flags emoji display on Windows with web font.]]></description><pubDate>Sat, 30 Apr 2022 09:51:46 +0000</pubDate><content:encoded><![CDATA[<p>I've been using Windows for a while now. Everything goes well except it doesn't render country flags emoji. I didn't notice this until I became a Windows user.</p>
<p>In the <a href="https://typlog.com">Typlog</a> admin portal, there is a region visit metric that displays country flags via emoji. It looks terrible on Windows. Here is how would it look like on Windows:</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8348687989_856062.jpg" alt="Broken country flags" title="Broken country flags" /><figcaption>Broken country flags</figcaption></figure></div><p>There are several ways to fix this problem:</p>
<ul>
<li>Display country flags with <code>&lt;img&gt;</code> tag: <a href="https://github.com/hampusborgos/country-flags">https://github.com/hampusborgos/country-flags</a></li>
<li>Using CSS sprits with class names: <a href="https://github.com/lipis/flag-icons">https://github.com/lipis/flag-icons</a></li>
<li>Render emoji with web fonts: <a href="https://github.com/talkjs/country-flag-emoji-polyfill">https://github.com/talkjs/country-flag-emoji-polyfill</a></li>
</ul>
<p>I prefer the web font way, although this repo contains only a few stars on GitHub. I didn't use the code provided by <code>country-flag-emoji-polyfill</code>, it is the <code>woff2</code> font I'm using directly in CSS. The font is a subset of &quot;Twemoji Mozilla&quot;.</p>
<p>We would load the web font only on Windows because Mac and Linux can display country flags emoji well. In this way, it would reduce the HTTP requests and net bandwidth, though the font is quite small.</p>
<p>A simple JavaScript is required:</p>
<div class="block-code" data-language="js"><pre><code>if (/windows/i.test(navigator.userAgent)) {
  document.body.classList.add('win')
}</code></pre></div>
<p>This code will add a <code>win</code> class to the <code>&lt;body&gt;</code> tag. We would only use the web font for <code>.win</code> in CSS:</p>
<div class="block-code" data-language="css"><pre><code>@font-face {
  font-family: 'Twemoji Country Flags';
  unicode-range: U+1F1E6-1F1FF, U+1F3F4, U+E0062-E0063, U+E0065, U+E0067, U+E006C, U+E006E, U+E0073-E0074, U+E0077, U+E007F;
  src: url('https://cdn.jsdelivr.net/npm/country-flag-emoji-polyfill@0.1/dist/TwemojiCountryFlags.woff2') format('woff2');
}

body {
  font-family: var(--user-defined-font), sans-serif;
}

.win {
  font-family: &quot;Twemoji Country Flags&quot;, var(--user-defined-font), sans-serif;
}</code></pre></div>
<p>Here is what the region visit metric looks like on Windows now.</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8348687961_643282.jpg" alt="Twemoji Country Flags" title="Display with Twemoji Country Flags" /><figcaption>Display with Twemoji Country Flags</figcaption></figure></div>]]></content:encoded></item><item><title><![CDATA[週記，垂死病中驚坐起]]></title><guid>https://lepture.com/zh/2022/weekly-20220409</guid><link>https://lepture.com/zh/2022/weekly-20220409</link><description><![CDATA[重新振作的一週，希望好狀態能維持下去。]]></description><pubDate>Sat, 09 Apr 2022 09:42:37 +0000</pubDate><content:encoded><![CDATA[<p>春光正好，人也從憂鬱厭世的情緒中解脫出來。也是長久未寫文章了，竟對寫字產生了一種疏離生僻之感。錢鐘書談寫作時說道：我們常把自己的寫作衝動誤認為自己的寫作才能，自以為要寫就意味著會寫。我大概連寫作的衝動都在逝去，同時逝去的還有各種興趣，比如好久沒去拍照了，就連玩遊戲看電影也提不起興致。</p>
<p>也許是身處疫情的自我隔離，也許是漫漫冬季的蕭瑟，也許是自我間歇性的抑鬱。之前的幾個月裡，整個人如在泥潭，想要掙脫卻越陷越深，什麼都不想做，不想工作，不想起床，不想吃飯，不想洗澡，不想睡覺。現下終於恢復了，趁著這個時候紀錄一下，寫點流水帳，也算是活在了人間。</p>
<h2>作息調整</h2>
<p>告别了昼夜颠倒的生活，作息終於規律了起來，現在也算是早睡早起了。一天的時間安排大致如下：</p>
<ul>
<li>自然醒，時間跨度比較大，大致 6:30 ～ 8:00</li>
<li>早餐，可能是烤麵包，可能是出門吃，也可能不吃</li>
<li>學習日語，是的，我又一次開始學習日語了，大致是 9:30 開始</li>
<li>處理 Typlog 的 bug，或優化代碼，或寫新功能</li>
<li>下午，處理真正的工作（養活自己的）</li>
<li>晚餐後，看情況，可能還是在為 Typlog 工作，可能是解決 GitHub 上的 Issue，也可能休閒</li>
<li>11:00 ～ 12:00 睡覺</li>
</ul>
<p>以上安排比較彈性，實際上會有一些出入。</p>
<h2>日語學習</h2>
<p>不記得是第幾次學習日語了，這次是在 NHK 上學習<a href="https://www.nhk.or.jp/lesson/chinese/learn/list/">《簡明日語 2015 》</a>，每天學習一到兩課，目前學到第九課了。這次至少要將這本書學完吧。</p>
<p>一點新知分享，<strong>こんにちは</strong>的漢字寫法是<strong><ruby>今<rt>こん</rt></ruby><ruby>日<rt>にち</rt></ruby>は</strong>，<strong>こんばんは</strong>的漢字寫法是<strong><ruby>今<rt>こん</rt></ruby><ruby>晩<rt>ばん</rt></ruby>は</strong>。所以日語的問安並沒有「安」字，亦如我們說早安時會說「早」而省略掉「安」字。當然，我們不會簡略地說「晚」。</p>
<h2>Typlog</h2>
<p>Typlog 最近做了一些優化，也新增了一些功能。</p>
<ul>
<li>Typlog 變快了，當然你可能也感受不到，反正我們服務器的壓力變小了點。</li>
<li>優化了郵箱列表的任務隊列，開始記錄用戶發郵件的次數，之後會做限制，Pro 用戶每月可免費發 2000 封郵件。</li>
<li>跳轉服務支持 wildcard matching 了。</li>
<li>自動鏈接 embed 服務支持 App Store、IMDb、豆瓣電影以及所有支持 Open Graph 的網站了。</li>
</ul>
<p>因為豆瓣有中國特色的圖片防盜鏈設置，豆瓣電影的卡片會出現圖片無法加載的問題，這是由於豆瓣設置了 referrer 校驗。但是在現代瀏覽器里，這種校驗是沒有意義的，除非限制必須帶有特定的 referrer。我們可以通過 <code>referrerpolicy=&quot;no-referrer&quot;</code> 來防止瀏覽器發送 referrer 信息。</p>
<div class="block-code" data-language="html"><pre><code>&lt;img referrerpolicy=&quot;no-referrer&quot; src=&quot;...&quot; /&gt;</code></pre></div>
<div class="schema" data-type="Movie"><img referrerpolicy="no-referrer" src="https://img9.doubanio.com/view/photo/s_ratio_poster/public/p451926968.jpg" alt="黑客帝国 The Matrix" loading="lazy" width="120" /><div class="schema-main"><div class="schema-name">黑客帝国 The Matrix</div><div class="schema-author">莉莉·沃卓斯基 Lilly Wachowski, 拉娜·沃卓斯基 Lana Wachowski</div><div class="schema-action"><a href="https://movie.douban.com/subject/1291843/" rel="noopener noreferrer">View</a><span class="schema-rating">★★★★★★★★★☆ 9</span></div></div></div><h2>Authlib</h2>
<p>修復了一些 bug，發佈了 <a href="https://github.com/lepture/authlib/releases/tag/v1.0.1">v1.0.1</a> 版本。接下來可能會再次投入時間到 Authlib 的改進。</p>
<hr />
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8350430928_900284.jpg" alt="鯉幟" /></figure></div><p>又到了鯉魚旗飄揚的時節了，出門的時候手機隨手拍了一張，也算是拍照了吧。</p>
]]></content:encoded></item><item><title><![CDATA[程序員與文學衝突]]></title><guid>https://lepture.com/zh/2020/tweet-yuan</guid><link>https://lepture.com/zh/2020/tweet-yuan</link><pubDate>Sun, 06 Dec 2020 06:21:58 +0000</pubDate><content:encoded><![CDATA[<p>昨日 Yachen Liu 談「程序員」一詞引起了頗多討論，其文曰：</p>
<div class="blockquote"><blockquote><p>一直很不喜欢「程序员」这个描述。以「员」为后缀的职业，如收银员、驾驶员等，多为一种机械地反复做同一件事情的工作，这个标准对 coding 来说太低、太无趣了。「软件工程师」则好的多，既表明了专业性，也包含了 coding 所具备的艺术和创造的可能。</p>
<p>via <a href="https://twitter.com/Blankwonder/status/1335053263360540672">Tweet</a></p>
</blockquote></div>
<p>暫不言「員」字一解，且談他人回復，或曰其言「是對其他職業的貶低」。Yachen 表示否定，今<a href="https://link.medium.com/nVhkAMH8Ybb">撰文解釋於此</a>：</p>
<div class="blockquote"><blockquote><p>所以只是我的言论映射出了批判者自己内心的价值观，而这些批判者往往也是善良的，这个价值观很刺眼他不愿意相信是自己的偏见，所以要怪罪于我的言论。</p>
</blockquote></div>
<p>這一表述亦是常見的，不過於「你自己心裡有鬼」，此文怕也是不妥，再次演繹了「子非魚，安知魚之樂？」和「子非我，安知我不知魚之樂？」</p>
<p>我們不談他人心中所想，只談文字表達。</p>
<p>原文 Tweet 建立了一個矛盾衝突，將程序員與收銀員、駕駛員等對立起來，而這個矛盾衝突是比較激烈的，如果沒有「這個標準對 coding 來說太低、太無趣了」一句，則衝突會低很多。分析此句，「這個標準」即是指「收銀員、駕駛員等」的標準，不異於指桑罵槐：</p>
<p>你們收銀員、駕駛員等的工作標準太低、太無趣了。</p>
<p>作者心裏是否這樣想的，我們不知，也不必評說，但是文字的表達上確實是有此意的，所以也怪不得他人誤解。這裏並沒有解釋一文裏所說的「腦力勞動者與體力勞動者」的衝突，而且腦力勞動亦有許多重復性。</p>
<hr />
<p>再談「員」字，是否真的指「機械地反復做同一件事情的」人呢？並沒有，大家不要誤解了「員」字。員字源於二形，一是上口下貝，一是上口下鼎。其本意是指「物的數量」，這個物是什麼呢，是貝，貝是錢。而後引申為人數，比如關羽乃一員猛將，再引申指人，便有了官員、吏員。</p>
<p>而「程序員」之員字，當指從事某一職業的人。這一釋義倒是頗合本意，寫程序來獲得收入的人，收入是什麼，是錢，錢是什麼，是貝。那麼員字是不是可以理解為<strong>做某事來獲得錢的人</strong>呢？所以官員是做官來獲得收入的人，收銀員是靠幫別人收錢來獲得收入的人，駕駛員是靠駕駛車來獲得收入的人。</p>
]]></content:encoded></item><item><title><![CDATA[Fireside 遷移記]]></title><guid>https://lepture.com/zh/2020/fireside-export</guid><link>https://lepture.com/zh/2020/fireside-export</link><description><![CDATA[當你的 Fireside 賬戶過期了，如何遷移你的數據呢？]]></description><pubDate>Thu, 11 Jun 2020 02:52:34 +0000</pubDate><content:encoded><![CDATA[<p>上周「<a href="https://biaojiangfm.typlog.io/episodes/">婊醬FM</a>」打算遷移到 Typlog，他們之前使用 Fireside 托管，但是賬戶已經過期，RSS 不能訪問，又未能找到 Fireside 的導出功能。於是將賬戶密碼告知我，委託我來處理。我卻是不信 Fireside 沒有導出功能的，結果是真的找不到。</p>
<p>但是我卻發現了 Fireside 有一個 Hiatus Mode，可以用 $5 來托管播客，方便遷移：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8408157585_794095.png" alt="Hiatus Mode" /></figure></div><p>告知「婊醬FM」確實需要再續費一個月才能方便遷移到 Typlog，但是可以使用這個 Hiatus Mode 來減少費用。申請後，兩個工作日依舊沒有反應，再次申請，還是沒有反應。賬戶信用卡已更新，但是賬戶依舊是 Suspended 狀態。不知道 Fireside 是否沒有人在工作，無法激活賬戶。</p>
<p>這樣等下去也不是辦法。據說互聯網是有記憶的（當然大國只有 404），也許可以在其他地方找回數據。先到<a href="http://archive.org/">互聯網檔案館</a>尋找，未能找到。</p>
<p>再搜索，找到一個叫 <a href="https://www.listennotes.com/podcasts/%E5%A9%8A%E9%85%B1bitch-up-bitchup-4NWS-sDvUr2/">Listen Notes</a> 的地方，似乎可以一用。頁面解析起來不太方便，但是這個網站是有 API 的，遂去申請，可惜需要填寫太多資料，還不知道 API 里是否有 Typlog 所需要的全部資料。便先放在一邊，再找找別的地方，實在不行再回來申請 API key。</p>
<p>最終從 <a href="https://www.breaker.audio/biao-jiang-bitch-up">Breaker</a> 處獲取到了所有數據：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8408156671_350819.png" alt="Breaker" title="Breaker Network" /><figcaption>Breaker Network</figcaption></figure></div><p>將數據從 Network 里複製出來，剩下的便很簡單了，寫上幾行 Python 腳本，將數據整理成 Typlog 需要的格式導入到 Typlog 里便可以了。Fireside 那裡，即使賬戶已經過期，只要知道音頻文件的地址，音頻文件依然是可以訪問的，所以還是可以將音頻文件抓取到 Typlog 的存儲里。</p>
<p>這裡不得不說，<a href="http://typlog.com/">Typlog</a> 還是做得不錯的。從一開始就做了導出功能，即使賬戶過期了，網站不可訪問，導出功能依舊可用。</p>
]]></content:encoded></item><item><title><![CDATA[Typlog 三週年]]></title><guid>https://lepture.com/zh/2020/3-years-of-typlog</guid><link>https://lepture.com/zh/2020/3-years-of-typlog</link><description><![CDATA[Typlog 不知不覺已存活了三年，分享這三年里 Typlog 的成長與變化。]]></description><pubDate>Sat, 02 May 2020 16:50:32 +0000</pubDate><content:encoded><![CDATA[<p>不曾想，距離 <a href="https://lepture.com/zh/2017/hello-typlog">Typlog 正式亮像</a>已經三週年了。這是我第一次做付費服務，也虧得是付費服務，不然早就歇業了吧。Typlog 就這樣安安靜靜地過了三年，直到最近才在 <a href="https://www.producthunt.com/posts/typlog">Product Hunt</a> 上做了一下宣傳，可惜不太成功，只新增了一個付費用戶。</p>
<p>三年時間里，Typlog 修修改改亦有不少變化。經營日淺，多半靠著大家捧場口口相傳罷了，也只能分享一點技術方面的收穫了。</p>
<h2>從靜態到動態</h2>
<p>Typlog 是用 Python Flask 寫的，這一點沒有變化。初時的方案是文章內容存在數據庫里，文章發佈時，後端會讀取數據庫，遍歷文章表生成整個網站的靜態文件，這些靜態文件通常是 HTML，通過 nginx 托管。這裡可以分享一個小技巧，註冊 Typlog 後 Typlog 會給你分配一個 Typlog 的域名，在你綁定了域名後，你就有了兩個域名，需要兩個域名都可以訪問，我當時的做法是：</p>
<ol>
<li>所有靜態文件生成到一個固定的文件夾，比如 <code>/data/build/lepture</code></li>
<li>創建軟鏈接 <code>/data/www/lepture.com</code> 和 <code>/data/www/lepture.typlog.com</code> 到 <code>/data/build/lepture</code></li>
<li>Nginx 配置里設置 <code>root /data/www/$http_host</code></li>
</ol>
<p>如是，當你訪問 <code>lepture.com</code> 或者 <code>lepture.typlog.com</code> 時便都可以訪問到相應的頁面了。最初設計選擇使用靜態方案是因為之前自己的博客是靜態的，而且靜態博客速度上會快。考慮到靜態緩存的問題，當時我選擇了不做分頁，採用按年歸檔的方案。這樣的好處是顯而易見的，比如 <code>/archive/2019/</code>，當 2019 年過後，這個頁面的內容便不會再變化了，而形如 <code>/page/2</code> 的設計，每增加一篇文章所有的 <code>/page/</code> 頁面都要重新生成一遍。按年歸檔的設計亦保留到現在。</p>
<p>靜態化的方案堅持了許久，漸漸也有了些許用戶，功能也越加越多，設計亦不時變化，每次變化都需要將所有網站的所有內容重新生成一遍，用戶越多耗時越多，這樣是沒有辦法 scale 的，於是不得不考慮改成動態化。</p>
<p>後來發現完全是自己考慮多了，動態化後一點都不慢。我在 Nginx 的日誌里輸出了 <code>$request_time</code> 值，這是請求從 nginx 進入 Python 程序到返回 nginx 的時間，通常這個時間是 3ms，真的非常快，這歸功於 Typlog 的緩存設計。因為保留了按年歸檔的設計，我們的緩存非常方便處理，比如當一篇文章更新了，我們會自動清除相應的緩存，假如這篇文章是 2019 年寫的，我們同時也會清理掉 <code>/archive/2019/</code> 的緩存，如果是按頁歸檔的話，就沒有如此方便了，只能將緩存時間設置得短一點。</p>
<h2>從博客到播客</h2>
<p>Typlog 誕生時只是一個博客服務，當時的目標是做一個有管理後台的靜態博客。主要是給我自己用，另外如果有其他人能和我一起用的話，服務器的費用便可以省下來了。之前一直在用靜態生成器來生成自己的博客，一個是圖片不好管理，需要找圖床上傳照片；一個是生成的過程太程序員了，沒有寫作的感覺，而且經常時隔很久後相應的編譯環境會出點各種各樣的問題。這便有了做一個有管理後台的博客的想法。</p>
<p>之後又心癢，想做一個播客節目，於是開始想方案將播客融入進 Typlog。這便是為何播客的 Feed 地址是 <code>/episodes/feed.xml</code> 而文章的 Feed 是 <code>/feed.xml</code>。因為播客是後來加入的，一開始沒有考慮到，不然文章的 Feed 應該設計為 <code>/posts/feed.xml</code> 的。最終自己的播客節目並沒有做出來，但是播客的功能卻在 Typlog 里生根了。</p>
<p>我需要感謝 <a href="http://nirokita.cn/">Niki</a>，他是我們的第一個播客用戶。也因為有了播客用戶，Typlog 才能一點一點改進完善播客功能。播客功能亦為 Typlog 帶來了不少用戶，博客多半隨意，播客通常會注重品牌，感謝播客的品牌效應，Typlog 亦漸為人知。</p>
<p>Typlog 也漸漸清晰了自己的定位，才有了我們現在首頁上的宣傳語：</p>
<div class="blockquote"><blockquote><p>Share your stories, either in text, images or audio. Focus on your creation and let Typlog take care of the rest.</p>
</blockquote></div>
<p>最近又將圖片功能升級了，於是有了 <a href="/moments/">Moments</a>。</p>
<h2>Admin 的三次重寫</h2>
<p>Typlog 的後台 Admin 部分經歷了三次重寫。感謝 Vue，雖然重寫了三次，但是三次都是用的 Vue。</p>
<p>第一次，初版 Admin，未用第三方 UI 庫，界面簡潔，只有文章功能。</p>
<p>第二次，新增播客功能，Admin 變得複雜許多，之前的簡潔佈局已不再適用，於是重新開始設計佈局。當時在 ElementUI 和 iView 里選擇，不知是不是使用有誤，ElementUI 總是出現各種問題，於是選擇了 iView。</p>
<p>第三次，iView 越使用越不好用，如果只使用他已有的 UI 的話還是不錯的，自定義起來就會很麻煩，後來亦遇到了一些問題，也給 iView 提交了修改代碼，但是越用越覺得還是什麼 UI 框架都不用最好。於是便有了現在的 Admin，整個項目結構合理，邏輯清晰，用 Jessie 的話來說便是，Saber（項目代號） 寫起來特別省心。</p>
<h2>善用第三方服務</h2>
<p>Typlog 使用了許多第三方服務，比如：</p>
<ol>
<li>Stripe 用來收款</li>
<li>Cloudflare Partner 綁定用戶的域名，支持 SSL</li>
<li>Sentry 收集錯誤信息</li>
<li>Google Analytics API 生成後台圖表數據</li>
<li>AWS SES 發通知郵件</li>
<li>Alibaba Cloud JP 處理圖片</li>
</ol>
<p>這是目前使用的部分服務，Typlog 亦替換過不少服務，最典型的便是 Google Cloud Storage。這是一個大坑，慎入。初時不覺，使用量大起來後費用立刻大漲，賬單里的各種信息看不明白，這裡收一點費那裡收一點費，流量分了好幾種、存儲也分了好幾種，與 Cloudflare 里的流量數據對比，Google Cloud Storage 的流量翻了三倍，也不知他是如何計算的。後來便遷移到了 DigitalOcean Spaces，費用立刻降下來了。</p>
<p>之後要做圖片自動裁剪的功能，於是自己搭了一個 <a href="https://github.com/imgproxy/imgproxy">imgproxy</a>。用過一段時間，時常會遇到一些問題。後來發現阿里雲 OSS 自帶圖片處理功能，於是註冊了日本區的 Alibaba Cloud，將圖片遷移到了阿里的 OSS。現在圖片 URL 上的  <code>?x-oss-process=</code> 便是阿里雲 OSS 提供的功能。這一點特別好用，比 Google Cloud Storage 和 AWS S3 不知高到哪裡去了。</p>
<p>Google Analytics 確實是很好用的，我在 Authlib 的博客里分享過如何<a href="https://blog.authlib.org/2018/access-google-analytics-api">通過 API 讀取 Google Analytics 的數據</a>，有興趣可以一閱。</p>
<h2>開放互聯網</h2>
<p>我向來支持互聯網而反對 App，能用瀏覽器的話就不下載 App 了。Web 是互聯的，App 是隔裂的。所以在做 Typlog 時會非常在意規範。比如最基本的，我們支持 Open Graph 和 Twitter Card。此外，我們還完美支持 <a href="https://lepture.com/zh/2015/fe-microdata">Microdata</a> 以及 Microformats。最初 Typlog 使用的是 Microformats 1，後來改作了 Microformats 2。</p>
<p>再比如我們還支持 <a href="https://blog.typlog.com/pubsubhubbub">PubSubHubbub</a> 或者 WebSub，這樣的話當你的博客一有更新後，你的 RSS 訂閱者便會收到更新。新近，我們又加入了 IndieWeb 社會，支持了 <a href="https://typlog.com/integrations/webmention">Webmention</a>。</p>
<h2>未來的規劃</h2>
<p>Typlog 一點點成長，希望他能漸漸成長到可以養活我。近期的規劃是多做一些 Pro 功能，讓 Pro 套餐更吸引人，已經規劃好的功能點：</p>
<ul>
<li>搜索功能已經快完成了，還在優化 (更新：已在內測）</li>
<li>郵件列表，遷移到 AWS SES 便是為郵件列表功能做準備</li>
<li>會員服務，方案還沒有想好</li>
</ul>
<hr />
<p>感謝 Typlog 用戶們的支持與包容。</p>
]]></content:encoded></item><item><title><![CDATA[How to style RSS feed]]></title><guid>https://lepture.com/en/2019/rss-style-with-xsl</guid><link>https://lepture.com/en/2019/rss-style-with-xsl</link><description><![CDATA[Let's create a beautiful RSS feed UI for human before its dead in next year again.]]></description><pubDate>Sat, 21 Dec 2019 08:24:08 +0000</pubDate><content:encoded><![CDATA[<p>&quot;RSS is dead&quot; every year; it will be dead in the next year again. But before the dead coming in next year, we can do something to make it dead in an elegant way.</p>
<p>RSS feed is meant to be used by machine (apps) not by human. But people may visit a feed link directly and shout out WTF is this.</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8424206158_188736.jpg" alt="Raw RSS" /></figure></div><p>The RSS feed however can be human friendly. Take an example of my blog's RSS feed. It is simple and clean, not so scary to ordinary people.</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/8424206380_291282.jpg" alt="My blog feed UI" title="My blog feed UI" /><figcaption>My blog feed UI</figcaption></figure></div><h2>XSL URL</h2>
<div class="blockquote"><blockquote><p>But how can we make a RSS feed look like the above UI?</p>
</blockquote></div>
<p>We added this UI in <a href="https://typlog.com/">Typlog</a> recently. It is pretty simple with <a href="https://www.w3.org/Style/XSL/"><strong>xsl</strong></a>. I'm not going to explain XSL in this post, instead, we can quickly decorate our RSS feeds with the famous copy paste method.</p>
<div class="block-code" data-language="xml"><pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;?xml-stylesheet href=&quot;/rss.xsl&quot; type=&quot;text/xsl&quot;?&gt;
&lt;rss version=&quot;2.0&quot;&gt;</code></pre></div>
<p>Here you can see, the feed is styled by an external file <code>/rss.xsl</code>. Note here, instead of providing a shared URL <code>typlog.com/rss.xsl</code>, we are using a relative path here. Because it is required by some browsers for security reasons; we need to put the xsl file under the same domain, protocol and port with the RSS feed.</p>
<p>Next, we can inspect the source code of <code>rss.xsl</code>:</p>
<div class="block-code"><pre><code>view-source:https://lepture.com/_style/default.xsl</code></pre></div>
<h2>XSL Template</h2>
<p>Here is an overview of the XSL file:</p>
<div class="block-code" data-language="xml"><pre><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;
&lt;xsl:stylesheet version=&quot;3.0&quot; xmlns:xsl=&quot;http://www.w3.org/1999/XSL/Transform&quot;
xmlns:atom=&quot;http://www.w3.org/2005/Atom&quot;&gt;
  &lt;xsl:output method=&quot;html&quot; version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; indent=&quot;yes&quot;/&gt;
  &lt;xsl:template match=&quot;/&quot;&gt;
    ...
  &lt;/xsl:template&gt;
&lt;/xsl:stylesheet&gt;</code></pre></div>
<p>Things to do:</p>
<ol>
<li>XML namespaces: register the required namespace when you need to select it via xpath.</li>
<li>XSL template: create the UI in XHTML</li>
</ol>
<h2>XSL Methods</h2>
<p>We will use some XSL methods to create our XHTML template:</p>
<ol>
<li><code>xsl:if</code></li>
<li><code>xsl:for-each</code></li>
<li><code>xsl:attribute</code></li>
<li><code>xsl:value-of</code></li>
</ol>
<p>Take a look at <code>https://lepture.com/_style/default.xsl</code>, follow the example. It is not hard to create a pretty UI for RSS feed.</p>
]]></content:encoded></item><item><title><![CDATA[談談獨立播客]]></title><guid>https://lepture.com/zh/2019/indie-podcasts</guid><link>https://lepture.com/zh/2019/indie-podcasts</link><pubDate>Sun, 09 Jun 2019 06:51:41 +0000</pubDate><content:encoded><![CDATA[<p>昨天發佈了一個<a href="https://typlog.com/podlist/">中文獨立播客</a>的項目，在 Twitter 上說了一句，不曾想應者雲集。愚頗為惶恐，怕是要解釋一番了，談一談<a href="https://blog.yitianshijie.net/2019/06/08/what-is-indie-podcast/">什麼是獨立播客</a>。</p>
<p>我原先便有做一個播客列表的想法，不過是推薦一下使用 <a href="https://typlog.com/">Typlog</a> 的播客用戶，順便給 Typlog 做點宣傳。然而<a href="https://blog.yitianshijie.net/2019/06/04/what-is-happening-to-chinese-podcasts-201906/">二零一九年春夏之交國內播客的異動</a>打斷了我的方案，與其只推薦 Typlog 上的播客，何如推薦中文獨立播客。</p>
<p>愚聽播客並不算多，初始不過列舉三四個，幸得眾人共襄，今已數十者矣。好壞暫且不論，至少都滿足了「獨立域名」這一條件，當是時所立之唯一准則。此番定義怕是很難讓人信服，然而「這可以算是獨立播客的一個不完美但有效的定義」。</p>
<p><a href="https://twitter.com/Jesoooor/status/1137520030826913793">Jesse Chan 言</a>：</p>
<div class="blockquote"><blockquote><p>獨立 = 創作過程不受限制、影響。</p>
<p>這是要從內容本身出發去判斷的，真正「獨立」的內容會散髮出獨一無二的氣質。</p>
</blockquote></div>
<p>愚非賢者，不敢斷言何為真正獨立的內容，亦不可能聽遍所有節目。這裡取「獨立域名」一條皆因中國播客審查之過，假使播客並沒有自己的域名，而是使用喜馬拉雅、荔枝等，其便不至於被蘋果除名，因為這些國內平台已經幫忙審查過了。</p>
<p>當前情形，愚不可能去評判播客內容之優劣。止以域名論之，何也？假使播客 RSS 地址不能自己控制，全仰仗平台，平台說刪就刪，何談獨立？而有了自己的域名，即使使用別人提供的服務，想移走時便可以移走，可對 RSS 地址做重定向。至少在這一點上算得獨立了。</p>
<p>針對蘋果國內播客政策，我以為有如下解決方案：</p>
<ol>
<li>保留現有獨立域名的 RSS 作為國際版播客，同時提供一個喜馬拉雅或荔枝等 RSS 作為特色社會主義版。</li>
<li>且<a href="https://blog.yitianshijie.net/2019/06/06/time-to-double-down-on-open-podcast-clients/">加倍鼓勵用户使用泛用型播客客戶端</a>，如 Castro、Pocket Casts、 Overcast 等。</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[貓與網絡暴力]]></title><guid>https://lepture.com/zh/2019/cat-and-violence</guid><link>https://lepture.com/zh/2019/cat-and-violence</link><description><![CDATA[貓很可愛，但這不是我們支持網絡暴力的理由。]]></description><pubDate>Wed, 01 May 2019 14:16:08 +0000</pubDate><content:encoded><![CDATA[<p>今日偶遇一則微博，言其室友虐貓，轉發已過十萬。其文指名道姓，又向其室友之公司與學校控訴，望學校與公司對其室友作出應有的裁決。聲援者雲集，大約是能如意了。</p>
<p>而我卻擔心起室友來，恐怕其會有輕生之意。希望她能堅強一點，渡過此次難關，待事件平息後還能過上正常的生活。</p>
<p>我這裡也不願列出原始出處，算是保護一下二位當事人。也許是一廂情願，搜一搜便可以找到的，甚至都不需要「人肉」一下。</p>
<p>我亦曾感嘆於互聯網之偉大，樂見社交網絡的興起。那時我們一起見證社交網絡的力量一度左右過政府的政策，恍惚間便生出了幻覺，以為公權力終於有了束縛。</p>
<p>萬不料網絡最終成了威逼個人的利器。公權力？那是我無法理解的力量，畢竟年輕，畢竟單純。便是前年，<a href="https://lepture.com/zh/2017/liqi">我亦嘗言</a>：</p>
<div class="blockquote"><blockquote><p>確實駭人聽聞，我單知道可以在網上批評某一類人的，譬如中國的某類老人家。又或是怒斥強權，一邊健身一邊控訴幾個委員書記之類。此番號召眾人去人肉一個平民女子，還是第一次見。</p>
</blockquote></div>
<p>確實是我孤陋寡聞，大約網絡暴力已經是一種常態了。此番卻不必號召眾人去人肉了，姓名學校公司與照片齊全。倘使是正經的媒體，人物會化名，照片會打碼，我們知道發生了這樣的事，輿論可以去批評虐貓的行為，當事人能得到教訓而不必生活於恐懼之中。</p>
<p>社交網絡的力量很可怕，雖然還沒有公權力可怕，個人在其面前是如此的渺小，如何使用他，這是一件值得思考的事。我們縱然無法利用他做正確的事，亦當謹慎用其作惡。</p>
<p>我不想分辨室友算不算是在虐貓，貓很可愛，但這不是我們支持網絡暴力的理由。</p>
]]></content:encoded></item><item><title><![CDATA[那霸的夜]]></title><guid>https://lepture.com/zh/2019/night-in-naha</guid><link>https://lepture.com/zh/2019/night-in-naha</link><pubDate>Wed, 10 Apr 2019 18:35:37 +0000</pubDate><content:encoded><![CDATA[<p>夜，最是適合逃避的時間。沒有工作，沒有生活，只是純粹的夜。在那霸的夜裡漫步，暫時忘卻白天的煩惱。沿著馬路，一個人靜靜走著，邊上一輛輛出租車駛過，玻璃擋板里閃耀著紅色，模模糊糊看不清楚的二字分明是空車。</p>
<p>走上國際通的街道。還有深夜經營的小店，居酒屋拉麵店咖啡館。行人三三兩兩，行至人少處，終究忍不住尖叫了兩聲，權當發洩了。</p>
<p>不遠處聞得歌聲，是首英文歌，我也哼得起來，便慢慢向著歌聲走去。過了紅綠燈，見到是兩個年輕人在那裡唱歌，男生彈著吉他，女生捧著話筒，面前是個小音箱。我問了一聲能否坐在這裡，得到肯定的答復後便坐在了旁邊。</p>
<p>女生唱完後問我想聽點什麼，我說你隨意，唱你喜歡的就好了。而後便靜靜呆著，聽他們唱完一首又一首。路上偶爾一兩人丟下硬幣便走了。忽而一個大叔騎著自行車經過，又回轉回來，停下車，在對面坐下來，亦聽起歌來。兩首後過來與他們搭過話，再聽了聽也離開了。倒是以為我跟他們一起的，連忙解釋說我是外國人。</p>
<p>歌聲繼續，沒有人駐足，唯有微風吹拂。這那霸的夜溫暖濕潤，最是怡人。曲終人散，從錢包掏出幾張紙幣後匆匆離去。又是一個人漫無目的的閒轉。</p>
<p>大抵生命還是美好的。晚安。</p>
]]></content:encoded></item><item><title><![CDATA[Authlib Under BSD License]]></title><guid>https://lepture.com/en/2019/authlib-bsd-license</guid><link>https://lepture.com/en/2019/authlib-bsd-license</link><description><![CDATA[Happy new year, happy new license. Authlib will be licensed under BSD instead of AGPL starting from next release v0.11.]]></description><pubDate>Tue, 01 Jan 2019 09:16:29 +0000</pubDate><content:encoded><![CDATA[<p>The new year has arrived, it is the time to make a change. The <a href="https://github.com/lepture/authlib/issues/59">license issue</a> of <a href="https://authlib.org/">Authlib</a> has been there for a long time, AGPL seems not a good choice in first place. Although I had planned to split Authlib into 3 modules and issue them under different licenses, that won't happen in a short time. AGPL is preventing many people from using Authlib, the situation will end soon. New year is the time to change the license, and I'm happy to announce that Authlib is going to be licensed under BSD from next release.</p>
<h2>Background</h2>
<p><a href="https://authlib.org/">Authlib</a> was created to replace my <a href="https://github.com/lepture/flask-oauthlib">Flask-OAuthlib</a> project. In my previous blog article <em><a href="/en/2018/announcement-of-authlib">Announcement of Authlib</a></em>, the following is posted:</p>
<ul>
<li><p>Flask-OAuthlib is not designed well.</p>
<p>It was caused by the API provided by OAuthLib and my poor understanding of OAuth at that time.</p>
</li>
<li><p>Flask-OAuthlib is not maintained well.</p>
<p>It was caused by the maintenance problem of OAuthLib at that time (things are better now).</p>
</li>
</ul>
<p>That is why I started Authlib <strong>from scratch</strong> and build it as a <strong>monolithic project</strong>. Starting from scratching would provide Authlib a cleaner and better designed code base, it won't be affected by the maintenance of other projects. Being monolithic would keep specification implementation and framework integrations synchronic in Authlib, and it would also provide a better API design for integrations.</p>
<h2>Sustainable</h2>
<p>The original goal of making Authlib sustainable won't change. Being profitable equals being sustainable. Although I had switched Authlib license from AGPL to BSD, it is still suggested that enterprise users <a href="https://authlib.org/plans">buy a commercial plan</a>.</p>
<ol>
<li>You will be enlisted in the security mail list to get security notification at first time;</li>
<li>Your feedbacks will get responses more quickly than community support;</li>
<li>There will be more features in the future.</li>
</ol>
<p>There is also another way to support my work on Authlib. I'm now accepting donations and sponsorships on <a href="https://www.patreon.com/lepture">Patreon</a>. Thanks for supporting my work on Authlib.</p>
<a class="md-img-link" href="https://www.patreon.com/lepture" title="Support me via Patreon" style="display:inline-block;border:0;width:220px">
![Patreon](https://c5.patreon.com/external/logo/become_a_patron_button@2x.png)
</a>

<h2>Get Updates</h2>
<p>Version 0.11 should be released in January soon. Follow <a href="https://twitter.com/authlib">Authlib's Twitter</a> account and subscribe <a href="https://blog.authlib.org/">Authlib's Blog</a> to get updates.</p>
<p>Here is the official blog announcement: <a href="https://blog.authlib.org/2019/switch-to-bsd-license">https://blog.authlib.org/2019/switch-to-bsd-license</a>.</p>
<hr />
<p><em>Happy new license, happy new year.</em></p>
]]></content:encoded></item><item><title><![CDATA[PyCon JP 2018 記]]></title><guid>https://lepture.com/zh/2018/pycon-jp-2018</guid><link>https://lepture.com/zh/2018/pycon-jp-2018</link><description><![CDATA[第一次參加日本 PyCon，記錄一下經歷與感想。]]></description><pubDate>Wed, 19 Sep 2018 13:40:56 +0000</pubDate><content:encoded><![CDATA[<p>PyCon Japan 2018 剛剛結束。會議場地位於大田區，離我住處偏遠，於押上轉一趟車全程大抵一時半，倒不是怨言，整體感受挺不錯的。這次亦有在 PyCon 做一點分享，話題為 The Modern OAuth 2.0，正是最近在寫的<a href="/zh/2018/oauth2-intro">《摩登 OAuth 2.0》</a>系列。</p>
<h2>分享</h2>
<p>我自己偏愛概念類的分享，以傳播為主，這次的分享亦是順帶推廣自己的項目 <a href="https://github.com/lepture/authlib">Authlib</a>。下面的視頻大抵從 15:00 開始，英語口語不好講話不太流利（國語口語也不好），但是還算講清楚了。</p>
<p><a href="https://www.youtube.com/watch?v=LesqkjqiCr8" title="The modern OAuth 2.0">The modern OAuth 2.0</a></p>
<p>另外 slide 可見 <a href="https://speakerdeck.com/lepture/the-modern-oauth-2-dot-0">SpeakerDeck</a>。</p>
<h2>體驗</h2>
<p>這次參加 PyCon JP 算是偶然，來日本第三年了才第一次參加。報名後才發現原來講者亦要買票的，這卻是第一次聽說，查了一下，一般 PyCon 確實都要買票，但是可以申請補助。</p>
<p>中途並沒有人來聯繫，也沒有提前要 slide，所以一直拖到演講前一天才寫完。也正是這一天，PyCon 開始前的一天，主辦方終於有一個活動了，邀請了工作人員、贊助商、講者去晚餐。餐廳有點搞笑，將 PyCon 寫成了 TyCon——Welcome TyCon 樣。人很多，沒有自我介紹，就瞎聊，日語不太會，只能找人講講英語，我又不太會社交，有點尷尬。不過好歹還認識了幾人。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/We/TFohxYpr9A6lQJGBYDEg.jpg" alt="Welcome TyCon" /></figure></div><p>這次 PyCon JP 規模很大，除了早上的 Keynote 沒有並行外（住太遠趕上不 keynote），下午場的演講都是 6 個場次並行的，參會者可以自行挑選。當然，我只能挑選英文場的去聽了，可選擇項就少了。</p>
<h2>收穫</h2>
<p>PyCon JP 這邊有蠻多台灣 PyCon 過來的志願者，有好些人差不多每個地方的 PyCon 都會去，還挺驚奇的。跟台灣的朋友聊了聊，畢竟講國語比較輕鬆，決定明年去台灣宣傳一下。</p>
<p>我自己的分享結束後，亦收到了幾張名片，可惜我自己沒有印名片。有一位大約是想找我做點什麼，不過好像名片弄丟了，沒辦法聯繫了。還有一位馬爾代夫來的先生邀請我去給他們團隊做一個短期的培訓，看名片上的網址還帶 gov 難道是政府人員？倒是可以考慮一下，順便去度個假，得找個空閒時間。</p>
<h2>感想</h2>
<p>日本這邊辦得比中國要好。首先是議題，什麼樣的都有，有適合初學者聽的，有適合中高級聽的，有公司的有社區的，比較多樣。中國的看起來都是架構師，感覺像是 Summit（峰會），而不是 Conference，話題太過厚重。PyCon 還是要多元一些，要有對初學者友好的話題。</p>
<p>另外一點，PyCon JP 和其他地方的聯繫比較多，比如 PyCon 台灣、印尼、新加坡等，國際化比較好。問了一下在場的人，大家都沒聽過 PyCon 中國，以為中國沒有。正巧我分享完後有北京 PyCon 的人聯繫我（我沒有時間去），幫他要了 PyCon 日本、台灣、印尼的主席們的聯繫方式，希望 PyCon 中國能走出來與其他地方多多聯繫。</p>
<p>預祝十月的 PyCon 中國順利。</p>
]]></content:encoded></item><item><title><![CDATA[摩登 OAuth 2.0：簡介]]></title><guid>https://lepture.com/zh/2018/oauth2-intro</guid><link>https://lepture.com/zh/2018/oauth2-intro</link><description><![CDATA[隨著時間的推移，OAuth 2.0 相關的標準越來越多，該系列文章旨在介紹更現代的 OAuth 2.0 知識。本章乃第一章簡介。]]></description><pubDate>Tue, 11 Sep 2018 03:13:08 +0000</pubDate><content:encoded><![CDATA[<p>OAuth 2.0 開發者應該不陌生。其正式發表至今已逾六年，加之眾多知名公司使用，圍繞著這些公司的開放（或偽開放）平台，開發者也越來越多，名氣也就愈發響亮了。但是認真讀過標準的卻沒有多少人，不按標準來實現 OAuth 2.0 服務的公司不可勝數。而況這些年里，OAuth 2.0 又有許多新的標準規範誕生。我因為在寫 <a href="https://authlib.org/">Authlib</a><sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup>，相關的標準略有閱讀，或有所得，不才分享一二，但求解惑三四人。</p>
<h2>歷史</h2>
<p>OAuth 的歷史最早可追溯到 2006 年 11 月，Twitter 需要一套 API 的授權訪問方案，彼時並沒有一套開放的標準規範來實現 API 的授權。由 Twitter 的開發者 Blaine Cook 首倡<sup class="footnote-ref" id="fnref-2"><a href="#fn-2">2</a></sup>，最終於 2007 年 7 月完成了 OAuth 的初版草案。2007 年 12 月 4 日 OAuth Core 1.0 正式發佈。</p>
<p>這是 OAuth 1.0 的歷史。其後 OAuth 1.0 進入 IETF，2010 年 4 月 RFC5849 發佈。這期間，即 2009 年 IETF 成立了一個 OAuth 工作組，後來的 OAuth 2.0 正是由這個工作組創建的，用以取代 OAuth 1.0。</p>
<p>無論是 OAuth 1.0 還是 OAuth 2.0，他們解決的都是同一個問題，即「如何讓一個應用在用戶的授權下訪問操作用戶授權的有限資源」。</p>
<h2>框架</h2>
<p>OAuth 2.0 與 OAuth 1.0 並不兼容，其實是個全新的體系。不同於 OAuth 1.0，OAuth 2.0 是一個框架，而 OAuth 1.0 是一個協議<sup class="footnote-ref" id="fnref-3"><a href="#fn-3">3</a></sup>。框架，意味著開發者可以在 OAuth 2.0 這一體系里添磚加瓦，修補 OAuth 2.0 的不足，亦可以利用其構建新的協議。</p>
<p>時至今日，OAuth 2.0 框架體系里已經誕生了許多標準協議，亦有許多草案等待完善。除卻最初的 RFC6749 (OAuth 2.0 Framework) 和 RFC6750 (Bearer Token)，這裡列舉些許別的 RFC:</p>
<ol>
<li>RFC7009: OAuth 2.0 Token Revocation</li>
<li>RFC7519: JSON Web Token</li>
<li>RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants</li>
<li>RFC7591: OAuth 2.0 Dynamic Client Registration Protocol</li>
<li>RFC7636: Proof Key for Code Exchange by OAuth Public Clients</li>
</ol>
<p>發現沒有，就連 JWT 亦是 OAuth 工作組起草的。更全面的 RFC 列表可參考 <a href="https://tools.ietf.org/wg/oauth/">OAuth Status Page</a>。</p>
<h2>角色</h2>
<p>在 OAuth 2.0 框架內，通常有 4 種角色。分別是：</p>
<ol>
<li>Resource Owner：資源所有者。例如一個圖片分享網站，圖片就是資源，而圖片的上傳者便是資源所有者，通常便是這個圖片分享網站的用戶。</li>
<li>Resource Server：資源服務。提供訪問這些圖片數據的 API。</li>
<li>Client：客戶端。例如能訪問這個圖片分享網站的 iOS App。</li>
<li>Authorization Server：認證服務。在資源所有者的允許下，提供訪問權限給客戶端。</li>
</ol>
<p>有時你能明顯感受到這些角色的存在，有時則不然。但通常這四種角色都會存在於一個完整的 OAuth 2.0 授權訪問流程里，如圖所示：</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/bV/9YWZijYr-qzIuJBko4NQ.png" alt="OAuth Flow" title="一個完整的 OAuth 2.0 授權訪問流程" /><figcaption>一個完整的 OAuth 2.0 授權訪問流程</figcaption></figure></div><p>OAuth 2.0 的授權流程：</p>
<ol>
<li>客戶端提供其自身的信息，在資源所有者的允許下，向認證服務請求 Access Token。</li>
<li>認證服務驗證通過後，返回 Access Token 給客戶端。</li>
<li>客戶端使用 Access Token 向資源服務請求用戶數據。</li>
<li>資源服務驗證 Access Token 有效後，返回資源數據。</li>
</ol>
<p>須知，資源所有者的授權有多種方式，客戶端提供自身信息有多種方式，請求 Access Token 的方法亦有多種方式，便連返回的 Access Token 種類也可以有多種形態。而這些不同的方式不同的形態，在 OAuth 2.0 框架體系里是可以擴展的，隨著時間的推移，會有更多的草案變成標準，亦會有更多人提出其他草案。</p>
<section class="footnotes">
<ol>
<li id="fn-1"><p>Authlib -- The ultimate Python library in building OAuth and OpenID Connect servers. Start it on GitHub <a href="https://github.com/lepture/authlib">lepture/authlib</a>.<a href="#fnref-1" class="footnote">&#8617;</a></p></li>
<li id="fn-2"><p>OAuth started around November 2006, while Blaine Cook was working on the Twitter OpenID implementation. <a href="https://hueniverse.com/explaining-oauth-3735e3de27a8#582d">Read more</a><a href="#fnref-2" class="footnote">&#8617;</a></p></li>
<li id="fn-3"><p><strong>RFC5849: The OAuth 1.0 Protocol</strong> vs <strong>RFC6749: The OAuth 2.0 Authorization Framework</strong><a href="#fnref-3" class="footnote">&#8617;</a></p></li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[夜思]]></title><guid>https://lepture.com/zh/2018/miss-you</guid><link>https://lepture.com/zh/2018/miss-you</link><description><![CDATA[寫一首小詩送給你，深夜裡的胡言亂語。]]></description><pubDate>Fri, 15 Jun 2018 02:59:24 +0000</pubDate><content:encoded><![CDATA[<p>寫一首小詩送給你：</p>
<p>玉兔隱長夜，寒蟬聒青堤。<br />
睹物思君意，憑欄空悲淒。<br />
杯淺迷人眼，相見何遲遲。</p>
]]></content:encoded></item><item><title><![CDATA[Structure of a Flask Project]]></title><guid>https://lepture.com/en/2018/structure-of-a-flask-project</guid><link>https://lepture.com/en/2018/structure-of-a-flask-project</link><description><![CDATA[Flask is very flexible, it has no certain pattern of a project folder structure. Here is my suggestions.]]></description><pubDate>Sat, 21 Apr 2018 14:30:44 +0000</pubDate><content:encoded><![CDATA[<p>Flask itself is very flexible. It has no certain pattern for a project folder structure,
which is very good for experienced developers to organize things in their own favors.
However, people new to Flask will get confused, they need some guide on it, and usually
they are going to find something works but not good (or even bad).</p>
<div class="fund"><div id="codefund"></div></div>

<p>I didn't know such a problem until someone reported <a href="https://github.com/lepture/authlib/issues/8">an issue</a>
to <a href="https://authlib.org">Authlib</a>. And I can't understand the problem either. Then another
person explained it to me with a project structure, I finally got it. I was terrified that lots
of posts, guide, boilerplates are backward importing modules from project root <code>__init__.py</code>:</p>
<div class="block-code" data-language="py"><pre><code># project/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    db.init_app(app)

# project/auth/models.py
from .. import db

class User(db.Model):
    # define columns</code></pre></div>
<p>The code itself will work, but when your projects grow, sooner or later you will face a
cyclic dependencies problem. For instance, another extension requires to init with the
<code>User</code> model:</p>
<div class="block-code" data-language="py"><pre><code># project/__init__.py
from flask_sqlalchemy import SQLAlchemy
from another_extension import AnotherExtension
from project.auth.models import User

db = SQLAlchemy()
ext = AnotherExtension(User)</code></pre></div>
<p>Oops, a cyclic dependency occurs. Because <code>auth.models</code> is importing <code>db</code> from the root,
root can not import <code>User</code> module. This is a common cyclic problem, not limited to Flask.
It is easy to fix, but junior developers may find it very hard. So why not avoid such
thing from the very begining? Actually, if you have read the <strong>official</strong> documentation,
in <a href="http://flask.pocoo.org/docs/0.12/patterns/appfactories/#basic-factories">application factories</a>
you can find this piece of code:</p>
<div class="block-code" data-language="py"><pre><code>def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_pyfile(config_filename)
    from yourapplication.model import db
    db.init_app(app)
    from yourapplication.views.admin import admin
    from yourapplication.views.frontend import frontend
    app.register_blueprint(admin)
    app.register_blueprint(frontend)
    return app</code></pre></div>
<p>See, we put <code>db</code> in <code>yourapplication.model</code>.</p>
<p>I always keep this one certain rule when writing modules and packages:</p>
<div class="blockquote"><blockquote><p>Don't backward import from root <code>__init__.py</code>.</p>
</blockquote></div>
<p>That's why I submitted <a href="https://github.com/pallets/flask/issues/2626">a ticket to Flask</a>
as soon as I found this problem. People need a guide on folder structure. And here I'm
going to share my suggestions. But think it yourself, don't treat mine as a golden rule.</p>
<h2>Functional Based Structure</h2>
<p>There are many ways to setup your project folder structure. One is by its function.
For instance:</p>
<div class="block-code"><pre><code>project/
  __init__.py
  models/
    __init__.py
    base.py
    users.py
    posts.py
    ...
  routes/
    __init__.py
    home.py
    account.py
    dashboard.py
    ...
  templates/
    base.html
    post.html
    ...
  services/
    __init__.py
    google.py
    mail.py
    ...</code></pre></div>
<p>All things are grouped by its function. If it hehaves as a model, put it in models
folder; if it behaves as a route, put it in routes folder. Build a <code>create_app</code>
factory in <code>project/__init__.py</code>, and init_app of everything:</p>
<div class="block-code" data-language="py"><pre><code># project/__init__.py
from flask import Flask

def create_app()
    from . import models, routes, services
    app = Flask(__name__)
    models.init_app(app)
    routes.init_app(app)
    services.init_app(app)
    return app</code></pre></div>
<p>Here is a trick by me. In official documentation, <code>db</code> of Flask-SQLAlchemy is registered
in this way:</p>
<div class="block-code" data-language="python"><pre><code>from project.models import db
db.init_app(app)</code></pre></div>
<p>So my trick is define a <code>init_app</code> in every folder's <code>__init__.py</code>, and unify the init
progress as one:</p>
<div class="block-code" data-language="python"><pre><code># project/models/__init__.py
from .base import db

def init_app(app):
    db.init_app(app)

# project/routes/__init__.py
from .users import user_bp
from .posts import posts_bp
# ...

def init_app(app):
    app.register_blueprint(user_bp)
    app.register_blueprint(posts_bp)    

# ...</code></pre></div>
<h2>App Based Structure</h2>
<p>Another famous folder structure is app based structure, which means things are grouped
bp application. For instance:</p>
<div class="block-code"><pre><code>project/
  __init__.py
  db.py
  auth/
    __init__.py
    route.py
    models.py
    templates/
  blog/
    __init__.py
    route.py
    models.py
    templates/
...</code></pre></div>
<p>Each folder is an application. This pattern is used by default in Django. It doesn't
mean this pattern is better, you need to choose a folder structure depending on your
project. And sometime, you will have to use a mixed pattern.</p>
<p>It is the same as above, we can <code>init_app</code> as:</p>
<div class="block-code" data-language="py"><pre><code># project/__init__.py
from flask import Flask

def create_app()
    from . import db, auth, blog
    app = Flask(__name__)
    db.init_app(app)
    auth.init_app(app)
    blog.init_app(app)
    return app</code></pre></div>
<h2>Configuration</h2>
<p>Loading configuration would be another issue that many people find difficult, it
is also a folder structure problem. I don't know how other people are doing,
I'm just sharing my solution.</p>
<ol>
<li>Put a <code>settings.py</code> in project folder, treat it as static configuration.</li>
<li>Load configration from environment variable.</li>
<li>Update configration within <code>create_app</code>.</li>
</ol>
<p>Here is a basic folder structure for configration:</p>
<div class="block-code"><pre><code>conf/
  dev_config.py
  test_config.py
project/
  __init__.py
  settings.py
app.py</code></pre></div>
<p>Define a <code>create_app</code> to load settings and environment variable:</p>
<div class="block-code" data-language="py"><pre><code># project/__init__.py
import os
from flask import Flask

def create_app(config=None)
    app = Flask(__name__)
    # load default configuration
    app.config.from_object('project.settings')
    # load environment configuration
    if 'FLASK_CONF' in os.environ:
        app.config.from_envvar('FLASK_CONF')
    # load app sepcified configuration
    if config is not None:
        if isinstance(config, dict):
            app.config.update(config)
        elif config.endswith('.py'):
            app.config.from_pyfile(config)
    return app</code></pre></div>
<p>This <code>FLASK_CONF</code> is a python file path which contains configrations. It can
be any name you want, e.g. your project is called Expanse, you can name it as
<code>EXPANSE_CONF</code>.</p>
<p>I use this <code>FLASK_CONF</code> to load production configurations.</p>
<hr />
<p>Again, Flask is very flexible, there is no certain patterns. You can always find
your favors. These are just my suggestions, <strong>do not blind by anyone</strong>.</p>
<p><em>I don't like to write posts like this. But there are so many wrong guide, I hope this post can get a better SEO, so that bad posts don't mislead people.</em></p>
]]></content:encoded></item><item><title><![CDATA[Announcement of Authlib]]></title><guid>https://lepture.com/en/2018/announcement-of-authlib</guid><link>https://lepture.com/en/2018/announcement-of-authlib</link><description><![CDATA[A Python library for OAuth/OpenID Authentication. With Authlib, you can create OAuth servers with ease.]]></description><pubDate>Mon, 08 Jan 2018 13:21:37 +0000</pubDate><content:encoded><![CDATA[<p>It has been 4 years since I posted <em><a href="https://lepture.com/en/2013/create-oauth-server">Create an OAuth Server</a></em>. Flask-OAuthlib has been shining in the Flask community, but it's time to say goodbye. <strong><a href="https://authlib.org/">Authlib</a></strong> is going to replace Flask-OAuthlib.</p>
<p>Authlib contains everything you need to create OAuth clients and servers. It contains:</p>
<ul>
<li><strong>python-requests</strong>  <strong>OAuth1Session</strong> and <strong>OAuth2Session</strong></li>
<li>Flask OAuth clients integrations</li>
<li>Django OAuth clients integrations</li>
<li>Flask OAuth servers implementations</li>
<li>Django OAuth servers implementations (not ready)</li>
<li>OpenID and many other things</li>
</ul>
<h2>Documentation &amp; Playground</h2>
<p>No, there will be no guide on how to create an OAuth server in this post. It is hard to keep information updated in a single blog post.  Instead, I've deployed <strong>a real OAuth server online</strong> so that everyone can play with.</p>
<p>To get a better understanding, you can read the official documentation and the source code of Authlib playground:</p>
<ol>
<li>Official Documentation: <a href="https://docs.authlib.org/">https://docs.authlib.org/</a></li>
<li>Source Code of Authlib: <a href="https://github.com/lepture/authlib">https://github.com/lepture/authlib</a></li>
<li>Online Playground: <a href="https://play.authlib.org/">https://play.authlib.org/</a></li>
<li>Source Code of Playground: <a href="https://github.com/authlib/playground">https://github.com/authlib/playground</a></li>
</ol>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/AW/tGz13rK1FpH3LfjzeWAQ.png" alt="Authlib Playground Screenshot" /></figure></div><h2>Starting from Scratch</h2>
<p>I have several reasons to deprecate Flask-OAuthlib. But the main reason is that Flask-OAuthlib is not maintained well. And it seems things will not go well in the future. Flask-OAuthlib depends on OAuthLib which I believe is in a chaotic maintenance (although I am a maintainer too). Besides, there are many designs I don't like.</p>
<p><strong>I created <a href="https://authlib.org/">Authlib</a> from scratch</strong> - from specification implementation to framework integrations. I didn't understand OAuth well enough when writing Flask-OAuthlib. However, this time, I read every RFC carefully so that there will be less misunderstanding (I'm not a native English speaker). <strong>The experience in OAuthLib helps too</strong>.</p>
<h2>Being Monolithic</h2>
<p>I started <a href="https://authlib.org/">Authlib</a> with a monolithic design in mind. As I've said <em>&quot;it is designed from specification implementation to framework integrations&quot;</em>. <strong>Being monolithic is a feature of Authlib</strong>. I've always hated the <em>micro modules</em> in Node ecosystem, there are even lots of one line code modules in npm.</p>
<p>Being monolithic means it won't break things. When specifications changed, implementation will change too. It keeps everything synchronized. You don’t have to worry about monolithic, it doesn’t cost your memory. If you don’t import a module, it won’t be loaded.</p>
<p>Will it always be monolithic? It depends. As time goes and Authlib becomes mature, maybe I will separate the framework integrations from Authlib source code.</p>
<h2>Profitable = Sustainable</h2>
<p>Flask-OAuthlib is not sustainable. I can't spare too much time on it. That's why I created <a href="https://authlib.org/">Authlib</a> with a sustainable idea from the beginning. I hope the project will last long enough.</p>
<p><strong>Profitable equals sustainable</strong>. If I can make a living on <a href="https://authlib.org/">Authlib</a>, I can spare more time on it, keeping it updated, fixing bugs, resolving vulnerable securities. OAuth server implementation is an enterprise level problem, if you are running a company, you wouldn't like to use a <strong>non-sustainable</strong> module for such an important feature. What you need is a sustainable solution.</p>
<p>The <a href="https://authlib.org/plans">price model for Authlib</a> is simple yet affordable. It is <strong>$1000/year</strong> (or <strong>$500/year</strong> if your company is small), it is just <strong>1/10</strong> of one month salary for one year. However, if you don't care, you can always use the open source license.</p>
<h2>Road Map</h2>
<p>I've released <a href="https://docs.authlib.org/en/latest/changelog.html">Authlib v0.3</a> when writing this post. It is started from Oct 21, 2017:</p>
<ol>
<li>Common client and Flask client integration is available in <strong>v0.1</strong></li>
<li>Django client integration is available in <strong>v0.2</strong></li>
<li>Flask OAuth 2 server is available in <strong>v0.3</strong></li>
</ol>
<div class="blockquote"><blockquote><p>Flask comes first, Django comes second.</p>
</blockquote></div>
<p>The next implementation will be Flask OAuth 1 server. After that, there will be Django OAuth servers, and more OAuth 2 specification implementation. I wish I can make a <strong>version 1.0</strong> in the end of this year.</p>
<hr />
<p>Now, head over to the <a href="https://docs.authlib.org/">documentation</a> and <a href="https://play.authlib.org/">play</a> with Authlib.</p>
]]></content:encoded></item><item><title><![CDATA[三藩記]]></title><guid>https://lepture.com/zh/2017/san-francisco-trip</guid><link>https://lepture.com/zh/2017/san-francisco-trip</link><description><![CDATA[三藩九日遊記，流水帳記之。]]></description><pubDate>Thu, 02 Nov 2017 15:38:07 +0000</pubDate><content:encoded><![CDATA[<p>維西曆十月下旬，余赴美利堅公事。初入國，幾番盤查，始得入境也。至於翻箱倒櫃脫屣赤足亦平常事也，其嚴，甚於西朝鮮矣。余頗惡之。</p>
<p>余年少而家貧，無以直航，東京途次洛杉磯而適三藩。三藩多苦民，衣衫襤褸，幽幽然無所歸者，或蹲或坐或行於街市馬路也。又有果皮紙屑棄於地，臟亂差如是，不及扶桑多矣。三藩之民疑焉，每及問必答曰：扶桑乃發達國家之宗也。</p>
<p>余居三藩凡九日，得閒四天。</p>
<p>初。邀大學同窗共遊伯克利。伯克利者，加州大學分校也，是名校也。其聲名於外，世人但知伯克利，不聞加州大學也。會伯克利慶典，吹號者、擊鼓者、舞者、觀者、錯錯然聚首一地。余與同窗隨慶典行，聞其聲樂觀其歌舞，少女短着，或搔首或旋足或立於他人之肩，青春之謂也。余不得習業於伯克利，奚爲之？購伯克利衛衣，假之充數耳。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/z_3b64228027d4f998409c5978167a63d5.jpg" alt="伯克利" /></figure></div><p>再。適東原君赴蘋果公事，相約觀金門大橋。東原君驅車，載以出行，午食於天堂洲（Paradise Cay）泰國餐廳也。金門大橋者，蓋三藩名勝，客每至，必觀之也。余不敢自欺，隨波逐流耳。觀之以高山，觀之以近海，皆賴東原君相載也。其山皆灰黃而少植被，三藩環海，乾燥如斯，何爲之？</p>
<p>是夜，與諸君聚於聖荷西，有飯團、Loddit、梁海、東原等君。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/z_3cb64cd1f8d33aacb07a470aba9e9cf1.jpg" alt="金門大橋" /></figure><figure><img src="https://i.typlog.com/lepture/z_cda230c70465104f307c07deb919244e.jpg" alt="海" /></figure></div>
<p>三。央Bao君載余行射擊之樂也。其地近聖荷西，喚曰「靶宗」（TargetMaster）。余，中國人也，未嘗近手銃也。Bao君親攜槍支彈藥，銃二彈百，余與Bao君相次射之。其聲也巨，余每射，初必入靶，次則未必，皆因初射之聲懼也。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/z_bae689d7089b59e718415d79762e9921.jpg" alt="射擊" /></figure></div>]]></content:encoded></item><item><title><![CDATA[佐渡與阿賀町記]]></title><guid>https://lepture.com/zh/2017/sado-and-ago</guid><link>https://lepture.com/zh/2017/sado-and-ago</link><description><![CDATA[無計劃之旅途，有閒情於漫步。佐渡三日遊及阿賀町一日。]]></description><pubDate>Sat, 14 Oct 2017 12:34:03 +0000</pubDate><content:encoded><![CDATA[<p>應該學一下車了，小明嘀咕道。他那時在火車上，臨時決定要去阿賀町住一晚。鐵道兩邊的稻田，多數只余枯黃的稻樁，他只偶爾抬頭瞥瞥，依舊回過來看著電腦終端里滾動的日誌，修改 <a href="https://typlog.com/">Typlog</a> 的代碼。下一站「馬下」，<a href="http://nirokita.cn/episodes/1">第一個播客節目</a>看起來沒有問題了，小明不禁松了口氣伸了一個懶腰。</p>
<p>日本十一倒不放假，究竟在家呆久了，早想著出一趟遠門。原本計劃回國去新疆玩的，機票都買好了，因為辦美國簽證，護照還在美國大使館裡，不得不更改行程。去佐渡是臨時決定的，十一的早晨，感冒還未好，小明搭了電車去往秩父，打算坐一坐蒸汽機車，路途翻看地圖，見著西邊有一個離島，便想著不妨到島上一遊，由是選定了佐渡島。</p>
<p>這次又是一個人的旅行，小明倒也習以為常，往年亦多是一人旅，雲南游山水、西北逛沙漠、清邁騎摩托，倒是快活。亦不知是上了年紀，還是異國的孤獨，一個人出行時常會<a href="/zh/2017/one-day-in-nikko">失了興致</a>，又或許因為日本的精緻，失之疏蕩宏博，無法暢懷胸意。</p>
<p>佐渡一度算作流放犯人的蠻荒之地，及至金銀礦的發現，德川幕府置佐渡奉行所直領之。明治時亦曾置縣，現下稱市，屬新潟。小明於小木港入島，両津港離島，其間不過三天，山川人物歷史風貌只得一點淺薄的認識。</p>
<p>小木略顯荒涼，便是十一這樣的時節，到處都是中國人，這裡卻見不著幾個遊客，入境時還見著懸了中文橫幅，上書不地道的歡迎語——歡迎光臨來到佐渡。走在路上，時而可見廢棄的房屋，斑駁的牆壁，爬了一牆的藤蔓，破窗上的蛛網，網中央安靜的蜘蛛。天又陰沈沈的，飄著點細雨。尋找旅館的路上，去城山公園散步的路上，到處散漫著這份荒蕪的詩意。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/GL/HHP6ySrpsTfi2I9ptzlw.jpg" alt="房屋" /></figure><figure><img src="https://i.typlog.com/lepture/Ii/SSubPqipwgXsp4jHchCg.jpg" alt="蜘蛛" /></figure></div>
<p>此番行程未曾預訂宿所。他在觀光案內所<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup>翻閱信息，看到小木溫泉有一家「かもめ莊」便記下了。在小木遊蕩，找了一家日式料理店，點了一份天婦羅的定食作午餐。究竟背著旅行包，不太方便，飯後便去尋溫泉旅館了。</p>
<p>可巧還有房間。這一路皆是到了當地再找的，沒有中國遊客，又是工作日，大約因為這些緣故，總是有空房。一路的溫泉旅館，和室加早晚餐，算起來真是便宜，不過一萬多日元，最貴的一回還是去了阿賀町，倒不是佐渡島上的，也不過一萬五。早晚餐又豐富，他竟擔心起會發胖來，也是，來日本後重了十斤。</p>
<p>かもめ莊的老闆給了他一把雨傘，小明放過旅行包便出門散步了。</p>
<p>宿根木的公車卻沒有了，要到五點才有一班。他那麼喜歡破落的人，不能去宿根木的舊房子散步，頗覺遺憾。本來買了三日的公車券，看看小木的情形，車次那麼少，今日大約作廢了，只得在附近走走，坐坐たらい舟，爬城山公園，再去日和山咖啡館喝點東西。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/Ry/hhUBBc0buyhace5Slyaw.jpg" alt="たらい舟" /></figure><figure><img src="https://i.typlog.com/lepture/Qd/Jf7tqZEdVOJxhR9CXTcg.jpg" alt="城山公園" /></figure></div>
<p>公車券要到第二日，去相川時才稍微用得多點。先是從小木坐到佐和田，於佐和田轉車去相川，到相川再坐了車去「佐渡金山」觀光。他在相川觀光案內所內查資料，問所內工作人員近處的溫泉旅館，大叔給他介紹，聽得不太懂的便用紙寫漢字，後來詢問了價格又幫他打電話給旅館，總算是確定了晚上的住所。他把包存在案內所，等公車去佐渡金山。</p>
<p>佐渡金山開放的坑道只有兩個，宗太夫坑與道游坑，另有別的坑是要預約的。如果只看一個坑的話，選宗太夫坑。道游坑乃明治時的現代坑，沒有太多感覺。宗太夫坑卻是幕府時期人工挖掘出的坑道，內里頗具巧工，有<a href="https://twitter.com/lepture/status/919155585882382341">會動的人偶</a>扮演當時的情景，或是轉水輪，或是敲石塊，又有做文書工作的。除卻工作情景，亦有洞內的生活再現。坑道長，一路乒乒乓乓，又有介紹工具人物的，不及細看。因為要趕著下一班公車回去，只能草草觀之。</p>
<p>後來才想到，如果能自己開車的話，宿根木也去得，金山坑道也可看得愜意。</p>
<p>回程時又去看過北澤浮游選礦場遺跡。到處都是爬山虎，綠的紅的，垂著飛碟般形狀不知名的建築上，對面卻只有綠色的，蓋滿了階梯。此處又有個技能傳承展示館，可以一睹織布的技藝。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/xM/5q6-aolfPzwinpGQ5Epw.jpg" alt="相川" /></figure></div>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/Fn/V7suPXv_KsSvSWpqVZmg.jpg" alt="浮游選礦場遺跡" /></figure><figure><img src="https://i.typlog.com/lepture/2Q/BzR2S1qb1HW9doHbqmJw.jpg" alt="織布" /></figure></div>
<p>晚間宿道游，其地不大，房間不多，卻皆是面海的，拉開窗簾便可見著。他回相川觀光案內所，所內的大叔幫他致電旅館，不一時便有旅館開車來接了。這家叫道游的旅館，溫泉池太小，沒有露天溫泉。かもめ莊的都有，還有桑拿房。不過都比不了在阿賀町住的福泉。</p>
<p>火車行到津川站，他在這一站下車。上車時刷的交通卡，這一站卻沒有刷卡口，到底是有多鄉下呀。翻看手機地圖，見到兩家溫泉旅館，他便選了福泉，因為看著似乎便宜點。</p>
<p>房間很大，臨窗有一處休息區，窗外便是阿賀野川。川水不息，反照著天空的藍，對面是山，長滿了杉樹，這時節還顯綠。福泉的露天溫泉亦是臨著阿賀野川，彷彿懸在山間，男湯的風景略遜於女湯，因為女湯沒人，過去參觀了一下。</p>
<p>相較起來，福泉的餐食亦是最豐富的。到底是新潟，產米的大縣，晚餐的天婦羅里居然有一例炸稻穗。說起來，新潟的米真是好吃，當時不覺得，他回家後才發覺，因為再吃家裡的米時覺得不好吃了。以前還覺得自己在家煮的米挺好吃的，過年時節回國吃過國內的米，這一對比便知道平日里自家煮的有多好吃了。這回佐渡與阿賀町的旅途才發覺，還有更好吃的米。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/Hf/J9AwvYNXHQpXfpR8s3cg.jpg" alt="炸稻穗" title="炸稻穗天婦羅" /><figcaption>炸稻穗天婦羅</figcaption></figure></div><p>到福泉時不過二點。辦了手續，旅店的姐姐領著他去房間，一路介紹各種設施，他到房間放了包，姐姐泡了茶，稍微休息一下便出門閒逛了。這姐姐會點英文，又有筆紙，聊起來還算順暢，他亦在越谷住過，小明便覺得親切了許多。</p>
<p>麒麟山公園卻在維護，進不得，來途的「狐の嫁入り屋敷」亦閉館，真是憾事。他在麒麟山邊望見城山橋下有個老奶奶在洗東西，一時好奇過去看看，原來是在洗板栗。兩個人便聊起了天，老奶奶提到冬天時特別誇張地直呼「怖い、怖い」，逗得他直笑。</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/J0/J8_bECFYVBAGgM54SrbA.jpg" alt="洗板栗的老奶奶" title="洗板栗的老奶奶" /><figcaption>洗板栗的老奶奶</figcaption></figure></div><p>後來聽說他一個人過來玩，並且一路都是一個人時，不免可憐起來。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/Ak/UmLe6nJXDiMIvXYvJ3KQ.jpg" alt="阿賀町" /></figure><figure><img src="https://i.typlog.com/lepture/xy/Qv4rm_t4Q3zpCuAdfDMQ.jpg" alt="阿賀町日常" /></figure></div>
<section class="footnotes">
<ol>
<li id="fn-1"><p>日本各旅遊地一般都會有觀光案內所，離下車點都不遠的，建議去裏面查查旅遊信息。<a href="#fnref-1" class="footnote">&#8617;</a></p></li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[週記，九月末]]></title><guid>https://lepture.com/zh/2017/weekly-20170930</guid><link>https://lepture.com/zh/2017/weekly-20170930</link><description><![CDATA[感冒的一週，結果到現在還沒好。即使這樣，也要出門旅遊。]]></description><pubDate>Sun, 01 Oct 2017 13:22:45 +0000</pubDate><content:encoded><![CDATA[<h2>9.25，晴</h2>
<p>吃了兩日不知是誰剩下來的感冒藥，但是還不見好。下午便去診所看了醫生，開了處方藥。這是我第二次用健康保險，第一次是洗牙。</p>
<p>填過一點基本信息，醫生問了幾個問題，便開了五日份的藥，分四種，每種對應各自的病癥。問診費 1060，藥費 830。藥房開藥時又細緻講解了一下各種藥是什麼作用怎麼吃，可惜聽不太懂，不過開的藥單上都寫清楚了，上面的漢字信息便夠用了。</p>
<p>今日日語學習，自動詞與他動詞。另外記新單詞、複習舊單詞。</p>
<h2>9.26，晴</h2>
<p>一整天都在處方藥的副作用裏度過，昏昏沈沈的，一直想睡覺，只能做點機械類不用動腦子的工作。</p>
<h2>9.27，晴、陰、雨</h2>
<p>今天天氣多變，感冒還不見好。藥的副作用稍微適應了些。</p>
<p>聽了一期太醫來了，講兒童性教育的。憶起自己並不曾有過學習，讀書的時候好奇，常常翻字典，專門找相關的詞條來看，看詞條也是會勃起的哦。</p>
<p>寫了一篇博客《<a href="https://lepture.com/zh/2017/fe-live-regions">前端的基礎修養：ARIA Live Regions</a>》。</p>
<p>今日日語學習，主要在記單詞，另外複習了形容詞的文法。</p>
<h2>9.30，晴</h2>
<p>本來打算今天出門旅遊的，但是感冒還沒好，只好繼續呆在家裏。</p>
<p>這一日便全身心投入 Typlog 上了，給 Typlog 加上播客功能。之前已經寫了部分了，今天把設計都做完了，順便重構了許多代碼。Podcast 的功能不日便可上線，早期（現在還是早期）用戶將會免費贈送一個 Podcast 功能。</p>
<p>之前答應了給校友會寫稿子，晚間總算把中秋聚會的稿子寫完了。</p>
<h2>10.1，晴</h2>
<p>六點多咳嗽醒了，已經一週了，感冒還沒好。但是我一定得出門，憋了好久，必須得出去旅遊了。</p>
<p>目的地還沒想好，打算先往秩父。結果途中看地圖，又決定去佐渡了。一路猶豫不決，終於還是到高崎坐新幹線至上越妙高了。在上越妙高吃過午飯後，往直江津渡口趕去。不巧差了幾分鍾，沒能趕上今日的渡輪。直江津港前邊有一家叫「みなと荘」的旅館，過去打聽了一下，還有房間，便在這邊逗留一晚吧。</p>
<p>房間是和室的，很大一間，因爲附近看起來沒有吃飯的地方，便訂了夕食朝食付，房間加兩餐一起 6500 日元。等到吃晚餐的時候才發現挺豐盛的，一份煎魚、一份刺身、天婦羅、青菜、漬物、蒸蛋、豆腐味噌，大約是越谷 1600-2000 價位的。不知明早的早餐如何，算起來房費便很便宜了。</p>
<p>終於出門了，也有了今日攝影。可惜到上越後，天氣便不太好。</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/z9/8fqo82BYRn4HafPMnAfw.jpg" alt="草間觀船" /></figure></div>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/nK/uQAVnf2GGeKcDJHr65pw.jpg" alt="直江津港" /></figure><figure><img src="https://i.typlog.com/lepture/gi/UO7p6zb5ljP4Idjv--lw.jpg" alt="夕陽" /></figure></div>
]]></content:encoded></item><item><title><![CDATA[前端的基礎修養：ARIA Live Regions]]></title><guid>https://lepture.com/zh/2017/fe-live-regions</guid><link>https://lepture.com/zh/2017/fe-live-regions</link><description><![CDATA[Live Regions 可以通知讀屏軟件頁面上有什麼變化，有什麼重要的通知等。]]></description><pubDate>Wed, 27 Sep 2017 09:40:00 +0000</pubDate><content:encoded><![CDATA[<p>「前端的基礎修養」這一系列時隔已久，原本打算持續更新的，中間竟斷了兩年，現在又撿起來，亦不知能否堅持下來。所以喚作「修養」，大抵因為心虛，覺得沒有什麼技術含量，不便言之技術。</p>
<p>這一篇還是談 Accessibility，未來亦有相當一部分這一主題的文章。由簡入繁，一點一點提高網站的可訪問性。也是因爲稍微複雜的話題我還需要學習。這一篇單講 Live Regions。</p>
<h2>Live Regions 是什麼</h2>
<p>現代的 Web 越來越依賴 JavaScript，頁面裏的內容亦會隨着使用者的操作發生變化，我們需要標註出哪些變化必須通知給讀屏軟件，這些需要標註的動態區域便是 Live Regions。</p>
<p>在實際應用裏，Live Regions 一般用作消息提醒。例如 Twitter，你可以 Inspect 一下頁面代碼，當有新的 Tweet 時會出現，在 <code>&lt;body&gt;</code> 的最下面會出現：</p>
<div class="block-code" data-language="html"><pre><code>&lt;div id=&quot;sr-event-log&quot; class=&quot;visuallyhidden&quot; aria-live=&quot;polite&quot;&gt;
  &lt;p&gt;New Tweets available. Press period to review them.&lt;/p&gt;
&lt;/div&gt;</code></pre></div>
<p>如果你開啓了 Voice Over<sup class="footnote-ref" id="fnref-1"><a href="#fn-1">1</a></sup> 或者別的讀屏軟件，這些軟件便會誦讀：</p>
<div class="blockquote"><blockquote><p>New Tweets available. Press period to review them.</p>
</blockquote></div>
<p>但是你在網頁裏並不會看到這段內容的呈現，因爲它 <code>class=&quot;visuallyhidden&quot;</code>，與我在 <a href="https://lepture.com/zh/2015/fe-aria-label">aria-label</a> 一文裏提到過 <code>.sr-only</code> 一樣。</p>
<h2>aria-live</h2>
<p>如上代碼所示，我們使用 <code>aria-live</code> 來標示 Live Region，它有三個可選參數：off/polite/assertive。</p>
<ul>
<li>off: 等同於不標示 Live Region，讀屏軟件不做任何處理</li>
<li>polite: 通常使用這個設定，在用戶的當前行爲終止後，讀屏軟件發出消息通知</li>
<li>assertive: 比 polite 的優先級高，當同時有其它消息時，讀屏軟件會先讀 assertive 的消息</li>
</ul>
<p>我們看到 Twitter 的那段通知代碼正是用的 <code>polite</code>。但是我們偶爾也會有重要的信息，需要即時反饋，這時便可以用 <code>aria-live=&quot;assertive&quot;</code> 了。例如「保存錯誤」等消息。這裏有一個消息提醒的例子，我們可以看到 Voice Over 的效果：</p>
<p><a href="https://vimeo.com/235672780">https://vimeo.com/235672780</a></p>
<ol>
<li>註：如果你在中國大陸，可能看不到這個視頻演示。</li>
<li>註：這個 UI 庫我暫時沒有精力（財力）更新，大家看看就可以了，不要使用。</li>
</ol>
<h2>aria-atomic</h2>
<p><code>aria-live</code> 用來標記 Live Regions，以及定義它們是否打擾式提醒；<code>aria-atomic</code> 用來定義是否將 Live Region 當作一個整體。舉例如下：</p>
<div class="block-code" data-language="html"><pre><code>&lt;textarea v-model=&quot;content&quot;&gt;&lt;/textarea&gt;
&lt;div aria-live=&quot;polite&quot; aria-atomic=&quot;true&quot;&gt;
  You have entered
  &lt;span v-text=&quot;content.length&quot;&gt;&lt;/span&gt;
  characters.
&lt;/div&gt;</code></pre></div>
<p>假使我們不使用 <code>aria-atomic=&quot;true&quot;</code>，那麼當 <code>content</code> 的長度發生變化時，讀屏軟件將只讀出變化的內容，亦即 <code>&lt;span v-text=&quot;content.length&quot;&gt;&lt;/span&gt;</code>，你聽到的便是 1、2、3 等。當標記了  <code>aria-atomic=&quot;true&quot;</code> 後，你聽到的則是完整的一句話：</p>
<div class="blockquote"><blockquote><p>You have entered 3 characters.</p>
</blockquote></div>
<p>註：使用不同的讀屏軟件或者不同的瀏覽器，得到的效果會有差異。</p>
<h2>aria-relevant</h2>
<p>我們使用 <code>aria-relevant</code> 控制讀屏軟件在何種情況下發出通知消息。它的可選參數爲：</p>
<ol>
<li>additions: 當 live region 內有新增的 Node/Element 時</li>
<li>removals: 當 live region 內有 Node/Element 被刪除時</li>
<li>text: 當 live region 內的文本有變化時</li>
<li>all: 以上所有的情況</li>
</ol>
<p>默認值爲：<code>aria-relevant=&quot;additions text&quot;</code>，也就是說，如果 live region 內有節點被移除的話，讀屏軟件不會通告。這在一般情況下是合理的，但是有時我們也想知道是否有節點被移除了，比如當我們要做一個 Todo 時：</p>
<div class="block-code" data-language="html"><pre><code>&lt;ul aria-live=&quot;polite&quot; aria-relevant=&quot;additions removals&quot;&gt;
  &lt;li v-for=&quot;(s, i) in todo&quot; :key=&quot;i&quot;&gt;{{s}}&lt;/li&gt;
&lt;/ul&gt;</code></pre></div>
<p>當我們在其它地方的操作導致 Todo 裏的項目減少時，我們亦希望得到通知，這時需要給 <code>aria-relevant</code> 添加 <code>removals</code> 參數。</p>
<hr />
<ul>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions">https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions</a></li>
<li><a href="http://pauljadam.com/demos/aria-atomic-relevant.html">http://pauljadam.com/demos/aria-atomic-relevant.html</a></li>
<li><a href="https://dev.opera.com/articles/introduction-to-wai-aria/">https://dev.opera.com/articles/introduction-to-wai-aria/</a></li>
</ul>
<section class="footnotes">
<ol>
<li id="fn-1"><p>Voice Over 是 Apple (Mac &amp; iOS) 的讀屏軟件<a href="#fnref-1" class="footnote">&#8617;</a></p></li>
</ol>
</section>
]]></content:encoded></item><item><title><![CDATA[週記，九月三週]]></title><guid>https://lepture.com/zh/2017/weekly-20170924</guid><link>https://lepture.com/zh/2017/weekly-20170924</link><description><![CDATA[開始週記，結果這一週竟然不順，感冒了。]]></description><pubDate>Sun, 24 Sep 2017 11:33:59 +0000</pubDate><content:encoded><![CDATA[<p>最近字寫得太少，表達也跟著不流暢起來。雖然偶爾還有寫作的衝動，不至於荒廢了博客，往往因為拖延症，遲遲不肯下筆，甚至於失去了興趣。所以決定寫點週記，無論多麼無聊，總勝於無。</p>
<h2>9.20，晴</h2>
<p>今天去新宿，到一家喚作 サポート 的事務所咨詢辦公司或個人事業主的事宜。因為打算做自由職業，不想呆在辦公室里。結果卻不能滿意，辦公司需要雇人，個人事業主需要有日本公司的合同，目前都不具備。</p>
<p>咨詢過後，去涉谷的 Apple Store 處理 AirPods 的問題。是在上週六買的 AirPods，取出時左耳沒電，右耳有一半以上的電量。充電時當右耳充滿時左耳只有 60% 多一點。在都充滿的情況下使用，右耳還有 80% 左右時，左耳已經只有一半了。於是預訂了今天的 Genius Bar，結果 AirPods 又神奇地好了。</p>
<p>在 Apple Store 排了很久的隊，順便在店裡寫了一段時間代碼。Apple Store 的網絡挺不錯的，比附近的一家星巴克好多了。最終還是沒有處理 AirPods，等向一北過來時，還沒有輪到到我。畢竟 AirPods 又恢復了，就算了吧。</p>
<p>今日攝影</p>
<div class="photo"><figure><img src="https://i.typlog.com/lepture/BM/hZZjROVzK7m48xwHRfpw.jpg" alt="涉谷街頭" /></figure></div><h2>9.21，晴</h2>
<p>昨日決定了週記事項，今日決定重啓日語學習計劃。每次都是學了點開頭，過些天便放棄了。倘使在週記里記錄一下，會不會能堅持下來呢？</p>
<p>因為已經有點日語知識了，跳過一些內容，先復習部分語法。今日日語學習動詞的未然形和過去形。</p>
<p>晚間戴 AirPods 跑步，有點擔心 AirPods，便沒跑太快。不知道跑太快了會不會掉出來。</p>
<h2>9.22，雨</h2>
<p>今日下了一天的雨，斷斷續續，時大時小。</p>
<p>寫了一篇博客《<a href="https://lepture.com/zh/2017/live-better">像遊客一樣生活</a>》，這標題躺在我的 Trello 里也有好幾個月了，今天終於解決了，雖然有點潦草，得意便可。</p>
<p>今日日語學習，動詞的助詞，主要是 を、に、へ、で。</p>
<h2>9.23，陰</h2>
<p>昨晚便有感冒的跡象，今早起來喉嚨便乾疼。但是還是堅持著出了一趟門，去附近的田野里轉轉，看秋的稻穗。</p>
<p>晚上很早就睡下了，便沒有學日語，不過看了一章《夾邊溝記事》——許霞山放羊。九點半醒來，燒熱水喝藥，打算泡個澡，似乎煤氣出了問題，沒能泡成。</p>
<p>今日攝影</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/GC/E8IvyryPFWCJahArEjMA.jpg" alt="稻" /></figure><figure><img src="https://i.typlog.com/lepture/le/cRDHJEiAT-Wz8MfsAxfA.jpg" alt="稻" /></figure></div>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/3e/e0ZoO1swi3W0YXA0o30g.jpg" alt="蕩鞦韆的小朋友" /></figure></div>
<h2>9.24，陰</h2>
<p>晚上沒睡好，出了很多汗，被子翻來翻去的。到了早上感冒還沒好。</p>
<p>今天有一個武大校友會的活動，在龜有「中國文武學校」做月餅。中間有活動，獎勵玫瑰和康乃馨。也得了幾束，可惜沒有能送的對象。</p>
<p>今日攝影</p>
<div class="photos"><figure><img src="https://i.typlog.com/lepture/08/CVxumG1D8rAjnSk5rS_w.jpg" alt="小朋友" /></figure><figure><img src="https://i.typlog.com/lepture/6l/MkpiDjCwHJLVGe7pJY7A.jpg" alt="做月餅" /></figure></div>
<h2>Typlog 一周回顧</h2>
<ul>
<li>如果不喜歡 AMP，現在可以在 Labs 里關掉了</li>
<li>完善了付款流程，內測將在十月一日結束</li>
<li>後台統計數據從最近 30 天改為本月數據</li>
</ul>
]]></content:encoded></item></channel></rss>