Posts

Instant Proofreading Using Macos Automator

Introduction #

年末了還是來寫一點東西,絕對不是聖誕節沒有人約,只是不想浪費行憲紀念日放假的美好夜晚。

公司有很多外國同事,每天 slack 筆戰總是輸人一截,刷 credit 往往也刷得差強人意,一切的問題根源都是英文訓練不足。

幸好這是美好的 AI 時代,分分鐘請 LLM proofreading 不在話下,打開對話視窗都是一排 proofreading,把我打這個單字的手速鍛鍊的跟拓海的排檔一樣快。

於是痛定思痛,在這個放假的夜晚我悟了,開始找一下有沒有簡單的做法可以做到像 grammarly 以前那樣會飄一個 icon 在 input box 周圍可以按。

問了一下 Claude 解法還真不少,推薦使用 Automator + Keyboard Shortcut 的組合,這樣不只是右鍵選單可以快速呼叫,也可以用系統 global shortcut 來觸發。

原本想直接用 claude -p 或是 gemini -p 來省錢,但是登入一直搞不定,最後還是寫一個 Python script 串 openai 來處理。

Automator 建立步驟如下:

  1. Create Quick Action in Automator
  2. Set “Workflow receives” to text
  3. Check “Output replaces selected text” ← this is the key setting
  4. Add Run Shell Script, set “Pass input” to to stdin

The code #

Python script 參考如下:

正音班 - 台大語文中心

最近上了台大語文中心的正音班,可以參考開課列表,不定期會推出不同課程。

兩週四堂課,每周三、五 18:30~19:15,費用共 $3,300,上課教授為 Tom Sellari,是在台灣待很久的紐約人!

先說結論,滿推薦的,課程時間短,可以讓你有一個快速入門的方向,課程安排有兩大主題:

  1. Minimal pairs and troublesome sounds
  • vowels
  • consonants
  1. Speaking naturally
  • fluency and linking
  • Phrasing and Pauses
  • Emphasis

我自己學習動機是發現 zoom 或是 google meet 的 caption 功能其實不是我講的每一個單字都可以辨識出來,加上跟外國同事開會常常會聽不懂我講的單字是哪個導致很卡,最後一根稻草大概是之前跟同事聊到優勝美地,我以為他發音比較像是 yow·sun·might,結果是 yow·seh·muh·tee,雖然他們是有聽到,但我尬爆。於是就找找看有什麼學習資源可以幫助我更好的跟外國同事聊天。

課程一開始會透過一組又一組的單字來說明大家容易搞混的母音發音,譬如說 [peak, pick], [eat, it],這組要區分的是 long e & short i 的差別,課堂中會藉由分組練習的方式,來互相確認有沒有發正確,老師課堂上也會強調一些口舌之間的動作,譬如說 long e,要發正確其實嘴巴要出一點力,所以嘴巴懶的時候都會退化成 short i。

我自己觀察到比較台式的發音,問題大多是會在過度省略需要的口部動作,子音的部分這邊問題比較少,比較特別的地方大概是區分 SH /ʃ/ and ZH /ʒ/ ,單字都會是 sh,但發音會不一樣,可以參考這個 youtube

教授課堂上也會提一些其他國家的口音特點,以墨西哥來說的話 sh sound 他們通常都會發成 voiced sh sound、加州口音的話很喜歡用 like,跟語句最後都會比較上揚,想到 Valley Girl Accent #1 #2、日式口音的話就是 r,l 不分、台式發音的話我自己觀察大多是語調比較平,然後母音沒有發正確的問題、印度發音的話教授有特別說除了有一些子音不同以外,另外 rhythm 也有很大的不同,導致其實連他都聽不太懂(但我覺得是 native speaker 的謙虛 XD)。

富良野季末滑雪,入住王子飯店

2024/04 趁離開上一份工作,四月安排了去日本玩,這是疫情以來後第一次到日本走跳。 距離上次出國已經四年,還卡在桃機自動通關前面超丟臉,出發前也沒有不知道要辦 Visit Japan, 還在飛機上坐等空服員姊姊發申報相關紙張,結果第一個下飛機,然後卡在櫃檯前面乖乖寫申報,已經變成旅遊白痴。

這次主要目的是滑雪,自從 2019 年去藏王體驗了一下有點念念不忘,四、五年來不能說沒有成長,成長最多的大概是肚子與內臟脂肪, 人也從原本是個 2x 的精神小伙,到現在是個神經的肥宅,大家一定要正視工作造成的健康危害 (?

行前準備 #

四月還是滑得到雪的,先訂了新千歲機票,再來找滑雪場,出乎意料大部分北海道雪場都會標營業到 5/1 號, 最後問一下滑雪大大同事,他推富良野,看了一下新王子飯店 ski in/out 每晚價錢也還滿便宜的,交通看一下沒問題就決定是富良野了。

但滑雪還是要老天賞臉,原本出發前看積雪 145cm,結果抵達當天剩下 110cm,消逝的速度比我逐漸退後的髮線還快。

教學部分找了 Furano Snow School 因為季末的關係選擇也不多,大多學校只會開到三月。 對初學來講我實際上了,兩天各兩小時總費用是 ¥40,000,費用相比其他學校來說已經是偏低的了,但一個人找教練真的也是滿貴的 XD 其它備選的學校包含:

後來領悟了一個道理,如果要費用較低的話盡量找當地的,教學品質穩定但有語言障礙。 如果是多人均分的話應該還是會找台灣教練吧。

交通部分因為四月找不到飛旭川的飛機,要馬 JR 要馬巴士,巴士站的起程站牌我 google 半天看不太懂,最後還是選 JR。 中間要在瀧川轉乘根室本線 體驗有點差,車廂僅兩節?也因為沒有電氣化的關係,有一股柴油味,開的也滿慢的,、從舒服的特急 KAMUI 轉乘這個會有巨大落差,回程就直接搭巴士回札幌了。

到場滑雪 #

四月的時候新王子飯店其實不能 ski in/out,並不是不能直接進出雪道, 而是從旅館出去的那條雪道,纜車沒有營運,有開的只有中間 gondola 跟上面的一條纜車, 所以每天最痛苦的時候是滑完然後再把裝備走上坡扛回飯店櫃子,整體來說我很滿意教學的品質,受限於體力關係兩天只有學完落葉飄,對大多數台灣人而言這進度應該落在倒數 20%,但正確的落葉飄對我來說是個重要里程碑,代表了即使速度慢,我也可以走綠線慢慢的飄下山了。

抵達當天晚上,可以看到最靠近飯店的雪道積雪已經剩沒多少了 從房間看到的雪道 新王子飯店的早餐 離開那天的雪道模樣,這幾天都充滿晴天娃娃的祝福 離開那天的雪道模樣 雙人床房型 森之時計咖啡屋

Gsutil Need Pip Install Pyopenssl

Need to create some signed-url links, using following command.

gsutil signurl -d 2d service-account-key.json gs://my-gcs-bucket/my-object

But the response keeps auguing

The signurl command requires the pyopenssl library (try pip install pyopenssl or easy_install pyopenssl)

Already check those variables and install pyopenssl thousand times.

CLOUDSDK_PYTHON=path/to/python
CLOUDSDK_PYTHON_SITEPACKAGES=1

After checking the source code under platform/gsutil/gslib/commands/signurl.py

try:
  from OpenSSL.crypto import FILETYPE_PEM
  from OpenSSL.crypto import load_pkcs12
  from OpenSSL.crypto import load_privatekey
  from OpenSSL.crypto import sign
  HAVE_OPENSSL = True
except ImportError:
  HAVE_OPENSSL = False
  ...

The load_pkcs12 has been removed from PyOpenSSL==23.3.0, so I install the previous version and get works.

Why the secrets Module is the Ideal Choice for Generating Random Strings in Python

以前就一直很愛用 ruby 提供的 securerandom 來產生隨機字串,

但之前長時間使用 Python2 進行開發,一直忽略了 Python3 應該也會有類似的 module,

直到昨天有需求又查了一下發現,ㄏㄏ早在 3.6 就有提供這個 module 了,試一下跟 securerandom 87% 像,推薦給大家

簡單來說用 secrets 的好處是比起傳統 random module 產生出來的隨機字串

  • 更具有密碼學上的安全性
  • 更快,更簡單可以產生字串

原理的話其實就是用 os.urandom 取代 random module 內的擬隨機演算法

#### 偷懶版本,要注意 32 是 bytes,實際產生出來的長度因為 base64 encode 的關係會超過 32
from secrets import token_urlsafe
print(token_urlsafe(32))

#### 長一點的版本,使用 secrets.choice
import secrets import string
alphabet = string.ascii_letters + string.digits
random_string = ''.join(secrets.choice(alphabet) for i in range(16))
print(random_string)

ChatGPT 的回答 #

The benefits of using the secrets module to generate random strings (and other values) over the random module are:

Cryptographic security: The secrets module uses a cryptographically secure random number generator provided by the operating system, which is designed to be resistant to prediction and manipulation. The random module, on the other hand, uses a simpler algorithm that is not intended for cryptographic use.

Convenience: The secrets module provides a simple and intuitive interface for generating random values, without requiring manual seeding or other setup.

Efficiency: The secrets module is optimized for generating large amounts of random data quickly, and can generate random strings and other values much faster than the random module.

References #

Orbstack the Docker Desktop Replacement

身為一個資料水管工,在開發的時候常常需要用 docker compose 把整套 Airflow 拉起來測試,

這時候我的 intel-based 筆電常常就會起飛,在咖啡廳都被路人瞪,讓我非常不好意思。

昨天試玩了一下 OrbStack,覺得好棒棒

第一點是 OrbStack 啟動速度超級快!

以前覺得 Docker Desktop 記憶體吃太兇的時候就想讓他重開,要等等上一分鐘,

現在 OrbStack 啟動只要數秒,連偷個懶都不行,很不方便。

再來是資源使用率部分,

Memory 顯著有感,以前大概會吃到 10G swap,換了之後大概只吃到 3G,

CPU 部分也滿不錯,之前開 Docker Desktop 的時候其他東西都會比較卡,

開發體驗很差,索性開發完才拉起來測試,現在可以邊跑邊開發了,法喜充滿。

轉換的痛點

  • 需要升級到 macOS Ventura (13.2?)
  • 所有 image 要重 build

behind the scenes

  • 背後的原理,是利用 macOS Ventura 提供的 Virtualization Framework,share kernel 跟 windows 的 WSL2 很像
  • 未來會收錢,且用且珍惜

memory

BigQuery JSON_VALUE_ARRAY, JSON_QUERY_ARRAY 差異跟小雷

JSON_VALUE_ARRAY 只吃 scalar value

任何不在 {string, number,boolean} 都會變成 NULL, 像是

select JSON_VALUE_ARRAY('{"product_ids": [UCCU]}', "$.product_ids");

--
Row	f0_	
1	null

但如果裡面有 NULL 則會噴錯

select JSON_VALUE_ARRAY('{"product_ids": [null]}', "$.product_ids");

Array cannot have a null element; error in writing field f0_

啊如果我就是要 scalar value 裡面又可能有 null 怎麼辦? 這邊可以改用 JSON_QUERY_ARRAY 替代,但因為 output 不同(多了 double quote),視資料情況可以直接 trim 掉處理

SELECT
    ARRAY(SELECT TRIM(item, '"')
FROM
    UNNEST(JSON_QUERY_ARRAY('{"product_ids": [null, 1, 2, 3]}', "$.product_ids")) AS item);

打包 markdown 筆記並上傳到 R2 (S3 like service)

自從用 markdown 開始寫工作日誌與筆記,一直放在 iCloud 上面,之前看到 iCloud 掉資料的新聞覺得好像還是有必要多異地備份一下,那時候選了 S3,看重的當然是 11 nines 的耐用性,這樣畢生筆記萬無一失了吧。

這陣子發現 Cloudflare 的 R2 可以開始用 beta 版本了,基於我本人是 Cloudflare 無腦粉,馬上就想把目前的筆記也丟一份放在上面,於是就來研究一下。

首先 Cloudflare R2 跟 S3 一樣是 11 nines,對外宣稱的耐用性跟 S3 一樣,再來是每 GB 價錢,R2 目前是 $0.015 per GB per month,跟 S3 Virginia $0.023 比起來低了 35%,再來是資料不收輸出的頻寬費用,這點我自己很好奇,那不就可以拿來做一個免費的圖床?最後是跟 s3 相容的 api 把 api token 申請好之後就可以直接用 aws cli 上傳檔案。

一開始到 https://dash.cloudflare.com/sign-up/r2 申請服務,可以看到開 bucket 是不用選 region 的,下面說明 R2 buckets are automatically distributed across Cloudflare's data centers 另外也注意到 bucket name 應該是帳號內不能相同,跟 AWS 整個 region 不能有相同的 bucket name 不一樣。

抓網站內容做成電子書 (epub)

alt

買了 BOOX note5 之後, 覺得電子紙螢幕呈現效果真舒服, 所有要長時間閱讀的東西都想丟進去. 另外發現雖然系統用 Android 11, 理論上可以裝所有 Android App, 包含瀏覽器, 不過在裝了一堆 App 發現都是虛幻, 排版與文字呈現還是用 epub 格式最好.

本次範例使用 EbookLib, requests

以及 mark_mew 大大的關於我幫新公司建立整套部屬流程那檔事 為範例 感謝 mark_mew 大大分享自身經驗

另外粗粗產生出來的 epub 還是有很多排版問題要修, 像是圖片不見了, script tag 跑出來了, 在過一層 strip_tags 應該會好一點. 看來最適合的還是轉小說進去 (?

這次網頁數量不多用 requests 抓抓就好, 就不寫 scrapy 了, 抓別人網站注意禮貌

程式分成四段, 第四段跟 ebooklib 範例程式只有差異在產生 sections 部分

  • 抓內容網址
  • 抓內容 -> local file
  • 建立章節
  • 寫成 epub
from ebooklib import epub
from lxml.html import fromstring
from tqdm import tqdm
import os
import requests

# 抓內容的網址
urls = []
headers={
    "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36"
}
page_urls = [
    'https://ithelp.ithome.com.tw/users/20141518/ironman/4653',
    'https://ithelp.ithome.com.tw/users/20141518/ironman/4653?page=2',
    'https://ithelp.ithome.com.tw/users/20141518/ironman/4653?page=3',
]
for url in tqdm(page_urls):
    response = requests.get(url, headers=headers)
    tree = fromstring(response.text)
    urls += [s.strip() for s in tree.xpath('//h3[@class="qa-list__title"]/a/@href')]

# 抓內容
for url in tqdm(urls):
    fname = url.split('/')[-1]
    with open(fname, 'w') as wf:
        response = requests.get(url, headers=headers)
        wf.write(response.text)

# 建立章節
def build_sections():
    sections = []
    for fname in tqdm(sorted(os.listdir('./ebooks'))):  # 利用檔案名稱排章節順序
        if '.' in fname:
            continue
        with open('./ebooks/' + fname) as f:
            tree = fromstring(f.read())
            title = tree.xpath('//h2[@class="qa-header__title ir-article__title"]/text()')[0].strip()
            content = ''.join(list(tree.xpath('//div[@class="qa-panel__content"]')[0].itertext()))
            content = content.replace('\n', '<br/>')
            section = epub.EpubHtml(title=title, file_name=fname, lang='zh-hant')
            section.content = content
            sections.append(section)
    return build_sections

# 寫成 epub
from ebooklib import epub

book = epub.EpubBook()

# set metadata
book.set_identifier('ithome_ironman_4653')
book.set_title('關於我幫新公司建立整套部屬流程那檔事')
book.set_language('zh-hant')
book.add_author('mark_mew')


# create chapter
sections = build_sections()

# add chapter
for section in sections:
    book.add_item(section)

# define Table Of Contents
book.toc = [
    epub.Link(section.file_name, section.title, section.title)
    for section in sections
]

# add default NCX and Nav file
book.add_item(epub.EpubNcx())
book.add_item(epub.EpubNav())

# define CSS style
style = 'BODY {color: white;}'
nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style)

# add CSS file
book.add_item(nav_css)

# basic spine
book.spine = ['nav'] + sections

# write to the file
epub.write_epub('關於我幫新公司建立整套部屬流程那檔事.epub', book, {})

重寫所有 git author name & email

在家工作之後,幾乎都是用公司筆電,一些個人專案不小心套到公司 email 設定, 想說可能要寫個 script 來處理,沒想到一行就解決了。

git config --local user.name "Kaneshiro Takeshi"
git config --local user.email "your_email@example.com"
git rebase --root --exec 'git commit --amend --no-edit --reset-author'