[歌詞翻訳] (what you’ve) lost – つづき

時がたてば忘れると誰もが口を揃える
どうしてかな
この悲しみはまだ
君の匂いがするのに

大家都說時間過了就忘了
為什麼呢?
悲傷的是明明我還聞得到你的味道

無くしたものや後悔を眺めて
ただやり過ごしている
こんな自分になりたくなかったくせに
なりたい自分もわからない

望著失去的東西與後悔的事情
但就只是讓事情過去
明明自己不想變成這樣的
我也不知道想變成怎樣

ずっといつまででもその温もりを覚えているよ
もうどうしようもないことばかりだよ
僕だけうつむいたまま

一直以來我一直都記得那個溫暖喔
已經只剩下無藥可救的東西了喔
只剩我一個人一直在低著頭難過

時が経てばわかるよと誰かが言葉を濁した
それとなくそれなりに生きる今の僕に
なにを願うの?

有人不清不楚地說時間過了就懂了
對不知為何活著的我有什麼期望嗎?

夕立 白く煙る街並み
肩を濡らして家路急ぐ人たち
ひとたび ピントずらせば楽じゃない?
濡れたいわけじゃないのにいつも傘がない

夏天午後的大雨,覆蓋著白煙的城市
肩膀被淋濕急著回家的人們
一旦轉移焦點不是比較輕鬆嗎?
就像是明明沒有想被淋濕卻總是不帶傘

離れていく時間の中で
想い出だけ近すぎて
通り抜ける風が
見上げた夕空の高さが今思い出させる(おもいださせる)

離開後的這段時間
回憶太近了
吹過的風,還有抬頭向上看的夕陽的高度
讓我想起了你

きっといつまででもその温もりが僕を生かすよ
もうどうしようもないことばかりでも
雨が降り止まなくても

一定是一直以來的那份溫暖滋養我繼續往前走
即使都是一些不能挽回的事
即使雨一直不停

[歌詞翻訳] 僕は呪う – 大橋ちっぽけ

抱きしめあう夢ばかりを
今も見てる 余白の多いベッドで
満たされることのない心に水を注いだ

即使是現在,也總是夢到我們相擁著
為留白很多的床上那顆無法被填滿的心澆了水

君の幸福を 否定してまでも愛して欲しかった僕は
いつも愛に飢えた醜い獣みたいだったなぁ

我甚至否定你的幸福,還一直希望能得到你的愛
這樣的我,像是個對愛飢渴又醜陋的野獸

君の感情がわからないまま
傾いた秤をただ眺めていたんだ
でも生活は 続いていくんだ
君が居なくてもなんとなく生きていける僕を

一直不知道你的感受
只是看著傾斜的天秤
但是生活持續著
不知道為何,即使失去了你卻還能活著的我

僕は呪う

我詛咒

たった一粒の黒い染みすら 上手に拭えなかった

連只是一粒小小的污點,都沒有辦法好好地抹去

二人が対称に 釣り合っていたのは
傷ついた胸を 必死に隠していたから
最後の夜に 君は可憐に泣いた
朝靄に白んで枯れ切った頬に 日が差せば

因為我們過去為了要平衡兩個人相反之處
死命地隱藏了心中的傷痛
所以最後那天晚上,你可愛地哭了
晨霧中漸漸變白的天空映著你枯萎的臉龐

僕はまた 願ってしまうから
余命宣告を受け入れなきゃいけない
この先に 君を想って嘆く
夜はないよ でも好きだよ

如果陽光能照進來的話(上個段落的日が差せば),我可能又會許個願望,希望你能留在我身邊
所以,不得不接受被宣判的死期
今後,不會在夜晚想到你就哭了
但我仍愛著你

君の感情がわからないまま
傾いた秤をただ眺めていたんだ

一直不知道你的感受
只是看著傾斜的天秤

君の感情がわからないまま
傾いた秤をただ眺めていたんだ
でも生活は 続いていくんだ
君が居なくてもなんとなく生きていける僕を

一直不知道你的感受
只是看著傾斜的天秤
但是生活持續著
不知道為何,即使失去了你卻還能活著的我

僕は呪う

我詛咒

瀏覽器bfcache

bfcache是“back/foward cache”的縮寫。當頁面「符合某些條件」的時候,當使用者按了瀏覽器的上下頁,瀏覽器可以快速回復剛剛瀏覽的頁面。且因為bfcache做cache的方式是對整個頁面做快照(包含了Javascript Heap(用來儲存變數、函式的地方)),因此會包含剛剛所做的改變,例如變更過的DOM內容、Javascript的執行結果等等。同時,因為不需要再次載入資源,所以速度超快。

因為Javascript也被緩存的關係,假設我們是用vue.js開發前端頁面,當離開頁面後再返回時,就勢必不會重新觸發fetch, data, asyncData, mounted, created…等等的Methods與Lifecycle Hooks。

可快取條件

目前我測起來,各個瀏覽器可bfcache的條件似乎不太一樣。例如在Chrome中點擊了File Input就會無法cache,但在ios的Safari中並不會有問題。以下把查到的以及自己測試出來的整理出來。

  1. 不要使用unload事件:可以改用pagehide替代。
  2. 有條件地使用beforeunload事件:不使用匿名函數,且使用完後立刻removeEventListener是可以的。
  3. 標頭不要有Cache-Control: no-store
  4. 不要使用window.open()開啟新視窗或分頁,改用a標籤並指定target是_blank並加上rel=”noopener”
  5. 不要使用File Input, WebSocket, WebRTC, WebAuthetication API, IndexedDB, Web Locks API

測試方式

因為WebSocket不能用,因此若有使用Hot Module Replacement的開發環境,例如Vue, React,在開發模式時無法使用bfcache也是正常的。因此測試時最好以相同於正式環境的方式來測試。

打開Chrome的console,切換到Application頁籤,左邊找到Background Services,點選Back/foward cache。針對要測試的頁面點選Test back/foward cache按鈕。點選後頁面會快速地來回chrome://terms/與當前頁面,並回應是否成功。若失敗的話則會在下方顯示錯誤的原因。

用瀏覽器console批次移除Jenkins建置紀錄

某天公司Jenkins的容量爆了,而那天有權限進那台機器的同事剛好請假。唯一能做的只能從前端一筆一筆刪掉。但這樣刪,不知道要刪到民國幾年。於是看看他刪除是怎麼打的,然後用一個回圈批次把一個branch中的所有舊建置都刪掉。

沒權限進主機以外,懶得進主機,也可以用這個方式來刪除。原始碼如下:

const jenkinsCrumb = 'your jenkins crumb'

async function remove (domain, project, branch, from, to) {
  for (let i = from; i < to; i++) {
    const url = `https://${domain}/job/${project}/job/${branch}/${i}/doDelete`
    const form = new FormData()
    form.set('Jenkins-Crumb', jenkinsCrumb)
    const body = new URLSearchParams(form)
    await fetch(url, {body, method: 'POST', headers: {
      'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
    }})
  }
  alert('刪除完畢')
}

remove('your.jenkins.domain', 'my-repo', 'development', 1, 300)

先將程式碼複製下來,打開Jenkins頁面,打開瀏覽器console,找到head標籤的data-crumb-value把這段token(黃色處)複製起來。貼到const jenkinsCrumb的引號中。

這段token每次當我們登入Jenkins時都會變,所以你可以每次要執行之前都先更新一下,避免後面執行失敗。接著,我們開始修改 remove(‘your.ci.domain’, ‘my-repo’, ‘development’, 1, 300) 這行。

首先把你Jenkins的domain複製起來,替換掉’your.ci.domain’。接著進入要刪除的專案與branch,複製專案名稱與branch名稱,替換掉’my-repo’與’development’。

接著,看看畫面左下的建置號碼。目前最新的是85,假設我想刪掉1~84,我就把後面的1, 300替換成1, 85

接下來,打開瀏覽器console,把這段改好的code貼入console按下Enter即可。fetch的地方我用非同步的方式,是因為想讓一筆刪完後,在刪除下一筆。雖然會比較花時間,但數量一多才不會灌爆Jenkins。接下來只要等待刪完時彈出的「刪除完畢」視窗就大功告成了!

AI歌聲生成手把手教學

有聽了陳珊妮的「教我如何做你的愛人」了嗎?

協助這首曲子的Vocal的大功臣 AI Labs.tw 台灣人工智慧實驗室釋出了體驗平台,讓大家可以自己生成自己的創作。

這個平台有兩個模式,一般版模式比較屬於好玩的版本,只要輸入歌詞,AI會幫你作曲然後唱出來。專業模式的話,就需要一點專業能力,需要先用Midi做主旋律,然後便可以讓AI依照自己作的曲來唱出歌詞。

如果沒有安裝Midi軟體,可以用這個網頁版的,也算是不難用。確認曲速後,可以打開節拍器,然後把音符標好。

需要注意音符不可重疊,並適當留空白給予呼吸。雖然AI生成平台限定了音域範圍在48-84之間,也就是C3-B5之間,超過這個範圍會生成失敗。但是經過實測後,我發現只要是C5以上的音符,都會走音,男女聲都一樣。

接下來,本來以為可以調整每個音符的Volume,想說這樣AI唱出來就會更具有真實感,不過實測之後發現對歌聲生成AI並沒有用。不過還是可以稍微看一下這個線上版的Midi軟體可以調整哪些東西。

下方頁籤最左邊的Velocity是用來調整單一音符的音量,把頁籤切換到Volume頁簽的話,就可以像GarageBand一樣直接用畫的。然後這個網頁版的Midi軟體竟然連Pan, Pitch Bend, 研音踏板都可以設定,一樣都可以直接用畫的。但測試後目前AI對這些設定似乎都唱不出來,所以這個階段就先不用白做工了。

一切都搞定後,我們就可以把Midi匯出成檔案了。選擇左上方的 File -> Download MIDI File,點選後,.mid就會下載到電腦中。

進入專業版的歌聲生成平台,我們主要要準備的Input就是Step1的Midi檔跟Step2的歌詞

我們把剛剛輸出的Midi拖拉進左邊Step1下的方格。把歌詞填入Step2下方的文字框。這邊要注意一下,因為一個音符需要配一個文字,可以透過Step1下方的音符與歌詞字數統計,來確認一下是否有少。

我們可以從Step3這裡的音符表來確認,歌詞是否都有對應到我們設定的音符上

一切都確認無誤後,我們就來決定要怎樣的Vocal吧。目前平台上只有男生跟女生各一的選項可選。(*2023/7/19更新:前陣子開始只剩女聲可選)

選取完成後,按下「合成音檔並下載」就完成拉!

下面是剛剛我做的範例的男聲版與女聲版,真的真假難辨,連呼吸聲都有。硬要挑一些小細節的話,可能就只有拉長音的時候,偶爾會出現一點聽起來不太自然的抖動。

最後我把女生改成合音,再把兩個聲音放進去GarageBand,加上其他樂器後,就完成拉~

[歌詞翻訳] 運命の人 – 槇原敬之

作詞:槇原敬之 作曲:槇原敬之

残念な事に君は
僕の友人に恋してて
彼の事を聞きだそうと
誘われた焼き鳥屋を出た所
帰り道が同じ方向で
送ってけるのは嬉しいけれど
家の前に来ると改まって
礼を言う君がいつも少し寂しい

很遺憾
你愛上我的朋友
你想打聽他的事情
所以約我來串燒店,出去的時候
雖然因為回家的方向一樣
可以送你回家我很開心
但來到你家門前時
突然變得很正經地向我道謝的你,總是有點落寞

君が時々ぼんやり
遠くを見てため息付いてる
気持ち痛いほどわかる
君の事考えている僕と同じだ

你常常心不在焉
看著遠方嘆氣
內心的痛楚我了解
正在思考著你的事情的我也是

他の誰かの事を
好きだと知った後でも
いきなり嫌いになれるはずもなく
当面は君を好きなまま
ハシカみたいな恋だったと
笑える時が来るのかな
それともこのまま
一人で君を想いながら
年を取って行くのかな

就算知道你喜歡別人之後
我也應該無法突然討厭你
目前我還是喜歡你
我覺得像麻疹一般的戀情
能微笑的那天會到來吧
亦或是依然像現在這樣
一個人想著你
然後慢慢變老呢

見送る道のウィンドウは
秋冬の服を着せられた
マネキンが並んでいて
ダブって僕らが映っている
こういう時どう思うの?とか
どっち貰う方が嬉しいの?とか
男心のサンプリングに
必死な横顔に笑えた

送你離開的路上的櫥窗
有著被穿上秋冬裝的模特兒假人排列著
剛好映在我們的反光身上
這種情況男生會怎麼想?之類的
得到哪個東西男生會開心呢?之類的
你拼命地調查著男人的內心的側臉讓我笑了

はぐらかす事も出来るけど
真面目に答えてしまうのは
君の恋が上手く行けばいいとも思う
僕もいるから

我也可以轉移話題
但我不小心誠實地回答了
因為我也希望你的戀情可以順利
戀愛麻煩的地方就在於,每次遇到一個人
我都會覺得這個人一定就是對的人

他の誰かの事を
好きだと知った後でも
いきなり嫌いになれるはずもなく
当面は君を好きなまま
この人こそがきっと
運命の人に違いないと
出会うその度に
思ってしまうのが
恋のやっかいな所だ

就算知道你喜歡別人之後
我也應該無法突然討厭你
目前我還是喜歡你
我說「因為是這個人,所以一定是對的人沒錯(一直很想翻成真命天子)」
戀愛麻煩的地方就在於,我都會不小心想起那次見面的事

夏の終わりの匂いがする
風が今日この街に吹いた
移ろう季節の中で
僕は移ろわない気持ちもてあましている
ハシカみたいな恋だったと
笑える時が来るのかな
それともこのまま
一人で君を想いながら
年を取って行くのかな

聞起來有夏天要結束的味道的風
今天吹著這個小鎮
季節轉換之中
我難以處理我還沒換季的心情
我覺得像麻疹一般的戀情
能微笑的那天會到來吧
亦或是依然像現在這樣
一個人想著你
然後慢慢變老呢

まぁ たぶんそれはないよな
ちゃんと気付けるかな
運命の人と会ったら
君だったらいいのに

大概不會那樣吧
如果遇到對的人
我應該不會錯過吧?
如果那個人是你就好了

[歌詞翻訳] 我がままなハイヒール – 秋元薫

作詞:秋元薫 作曲:鳥山雄司

我がままなハイヒール
気ままな方よ私
女心はきっと
秋の空より Magical
気にするのやめなさい 噂もなんでもない
危ない夜 私にかかればきっと Paradise

任性的高跟鞋
我很做自己呦
女人心一定比變化無窮的秋天的天空還魔幻
不用擔心,謠言我無所為
把危險的夜晚交給我的話,絕對是天堂

街を歩いていても
みんな私に Fall In Love
ちょっと Wink をすれば
こわいもの何にもない
からかってただけなのに 本気であわてないでね
眠ってた街のあかり 今に輝き出す

即使只是走在街上
大家都會愛上我
稍微眨眨眼的話
沒什麼可怕的東西
我明明只是鬧你的,不要太驚慌耶
沈睡的街燈,現在開始閃耀

いつでも恋をしたいの ねぇ Darling!
向う見ずで我がままだわ 誰か早く
つかまえて私を

親愛的,我無論何時都想戀愛
我魯莽又任性呀
誰快來抓住我

気まぐれなハイヒール
気が強いのよ私

反復無常的高跟鞋
我很不服輸的呦

ただのゲームにしても
負けるのが嫌いなのよ
その気なら勝負していい 何賭けるつもり?
ただの Cardも 私にかかればみんな Joker

就算只是玩遊戲
我也討厭輸
你想要的話,要來輸贏一下嗎?你要賭什麼呢?
只是一般的撲克牌,交給我的話大家都變鬼牌

いつでも恋をしたいの ねぇ Darling!
ときめいてはじけてるわ 誰か早く
しかけてよ私に

親愛的,我無論何時都想戀愛
內心澎湃到炸裂了呀
快來找我

いつでも恋をしたいの ねぇ Darling!
向う見ずで我がままだわ 誰か早く
つかまえて私を

親愛的,我無論何時都想戀愛
我魯莽又任性呀
誰快來抓住我

いつでも恋をしたいの ねぇ Darling!
ときめいてはじけてるわ 誰か早く

親愛的,我無論何時都想戀愛
內心澎湃到炸裂了呀
誰快點⋯⋯

いつでも恋をしたいの ねぇ Darling!
I just wanna be the special
misty lady.
Say you love me again.

親愛的,我無論何時都想戀愛
我只想當個特別的神秘女子
再說一次你愛我

[歌詞翻訳] Plastic Love – 竹内まりや

作詞:竹内まりや 作曲:竹内まりや

突然のキスや熱いまなざしで
恋のプログラムを狂わせないでね
出逢いと別れ上手に打ち込んで
時間がくれば終わる don’t hurry!

別用突然地親吻或熱烈地凝視
讓我們的戀愛遊戲(程式)走火入魔誒
熟練地輸入相遇與分手
時間到了就結束 don’t hurry!

愛に傷ついたあの日からずっと
昼と夜が逆の暮らしを続けて
はやりの disco で踊り明かすうちに
おぼえた魔術なのよ I’m sorry!

被愛傷透了心的那天開始
一直持續著晝夜顛倒的日子
這是我在流行的Disco舞廳
跳舞跳到天亮時學到的魔法喔 I’m sorry!

私のことを決して本気で愛さないで
恋なんてただのゲーム
楽しめばそれでいいの
閉ざした心を飾る
派手なドレスも靴も 孤独な友だち

絕對不要真的愛上我
愛情什麼的只是遊戲
只要享受就好
用華麗的衣服跟鞋子裝飾我關上的心門
他們都是陪伴我孤單的好夥伴

私を誘う人は皮肉なものね
いつも彼に似てるわ
なぜか思い出と重なり合う
グラスを落として急に涙ぐんでも
わけは尋ねないでね

約我的人總是很諷刺
很像他(已經分手的那個他)
我不知為何把記憶重疊了
手中的玻璃杯掉落,我突然流下眼淚
但拜託不要問我為什麼

夜更けの高速で眠りにつくころ
ハロゲンライトだけ妖しく(あやしく)輝く
氷のように冷たい女だと
ささやく声がしても don’t worry!

在深夜的高速公路上快睡著的時候
只有鹵素燈魅惑人心地亮著
即使被用耳語說著「你真是個跟冰一樣冷的女子」也 don’t worry!

I’m just playing games
I know that’s plastic love
Dance to the plastic beat
Another morning comes

我只是在玩一個遊戲
我知道那是速食戀愛(雖然「速食戀愛」老派已經是死語了,但我一時想不到更好的)
跳著這個沒感情的節奏
迎接另一個早晨

I’m just playing games
I know that’s plastic love
Dance to the plastic beat
Another morning comes

我只是在玩一個遊戲
我知道那是速食戀愛
跳著這個沒感情的節奏
迎接另一個早晨

I’m just playing games
I know that’s plastic love
Dance to the plastic beat
Another morning comes

我只是在玩一個遊戲
我知道那是速食戀愛
跳著這個沒感情的節奏
迎接另一個早晨

[歌詞翻訳] 「跟拍到你家」海外版插曲 『Home』- エイティア

作詞:Ryu Matsuyama     作曲:maigoishi

Always worried about your future
And nothing goes right these days
Always searching for excuses
Only trying hard to be praised

總是擔心你的未來
最近沒有一件順心的事
總是在找藉口
只為了贏得稱讚而努力

You’re not the only one
Don’t be full of yourself
Let myself understand
All your happiness and pains
This is not a farewell
But a start of a new day

你不是一個人
不要自以為是
讓我理解
你所有的快樂與痛苦
這不是告別
而是嶄新的一天的開始

To our home sweet home
To our warm sweet home
To our hope to our road
To our warm sweet home
To our home sweet home
To our warm sweet home
To our hope to our road
To our warm sweet home

回到我們甜蜜的家
回到我們溫暖甜蜜的家
為了我們的希望與前進的道路
回到我們溫暖甜蜜的家
回到我們甜蜜的家
回到我們溫暖甜蜜的家
為了我們的希望與前進的道路
回到我們溫暖甜蜜的家

Always regretting your choices
And still thinking that nothing goes right
Always taking it all on yourself
Why do you think that nobody is on your side

總是在後悔你的決定
且還在想沒有做好任何一件事
你總是把所有事情攬在自己身上
你為什麼覺得沒有人站在你這邊

You’re not the only one
Don’t be full of yourself
Let myself understand
All your clamors and complaints
This is not a farewell
But a start of a new day

你不是一個人
不要自以為是
讓我理解
你所有的叫喊與抱怨
這不是告別
而是嶄新的一天的開始

To our home sweet home
To our warm sweet home
To our hope to our road
To our warm sweet home
To our home sweet home
To our warm sweet home
To our hope to our road
To our warm sweet home

回到我們甜蜜的家
回到我們溫暖甜蜜的家
為了我們的希望與前進的道路
回到我們溫暖甜蜜的家
回到我們甜蜜的家
回到我們溫暖甜蜜的家
為了我們的希望與前進的道路
回到我們溫暖甜蜜的家

Line Bot 串接 OpenAI API

最近很紅的ChatGPT,如果也能在Line上面使用的話,是不是感覺更方便呢?我們就來串接ChatGPT的開發公司OpenAI提供的API到Line Bot上吧!

這個範例會用Express起一個web server,建立一個method POST且path為/webhook的route作為line的webhook,用來接收line傳來的events

首先你需要先到 Line developers console 點選「Create a new channel」建立一個新的Channel,類型選擇「Messaging API」。填寫完成送出後進入Channel設定頁,複製最下方的「Channel secret」。回到上方選擇「Messaging API」頁籤,複製最下方的「Channel access token」。

用NPM安裝 openai, @line/bot-sdk, express,require進檔案中

const { Configuration, OpenAIApi } = require('openai')
const line = require('@line/bot-sdk')
const express = require('express')

建立Line API需要的Config,將剛剛Line Channel的Token跟Secret填入,範例以環境變數帶入。

// line config
const lineConfig = {
  channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN,
  channelSecret: process.env.CHANNEL_SECRET
}

建立express,設定line bot所需的webhook路由以及訊息事件的handler。將程式碼上到主機上後,就可以繼續完成Line Bot的設定了。

const app = express()

// line webhook
app.post('/webhook', line.middleware(lineConfig), (req, res) => {
  Promise
    .all(req.body.events.map(lineEventHandler))
    .then((result) => res.json(result))
    .catch((err) => {
      console.error(err)
      res.status(500).end()
    })
})

// line message handler
function lineEventHandler (event) {
  const lineClient = new line.Client(lineConfig)

  return lineClient.replyMessage(event.replyToken, {
    type: 'text',
    text: 'Bot收到訊息囉'
  })
}

app.listen(3000)

Line developers console 選取剛剛新增的Channel,點選「Messaging API」頁簽,在下方的「Webhook URL」點選「edit」,填入你的Webhook網址並按下「Update」,完成後按下「Verify」,就可以檢查webhook有沒有正常運作了。

接下來首先要到OpenAI 註冊你的帳號,註冊完後,到API Keys頁面,點選「Create new secret key」,複製API Key,回到程式碼,建立OpenAI API需要的Config填入剛剛的API Key,範例以環境變數帶入。

const openAiConfig = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
})

新增一個function負責傳遞訊息給OpenAI API,並等待回傳的訊息。

async function askOpenAI (question) {
  const openai = new OpenAIApi(openAiConfig)

  // request
  try {
    // 使用text-davinci-003 model
    // const completion = await openai.createCompletion({
    //   model: 'text-davinci-003', // 看你要使用哪個model: https://platform.openai.com/docs/models
    //   max_tokens: 100,
    //   prompt: question,
    //   temperature: 0.1 // 這是一個 0 - 1 的數值,越靠近1回答會更多樣性,越靠近0回答會更可靠
    // })
    // return completion.data.choices[0].text.replace(/^\n+/g, '')

    // 使用gpt-3.5-turbo model (這個比較強)
    const completion = await openai.createChatCompletion({
      model: 'gpt-3.5-turbo',
      messages: [{ role: 'user', content: question }]
    })
    return completion.data.choices[0].message.content.replace(/^\n+/g, '')
  } catch (err) {
    console.log(err)
    return '抱歉我不知道'
  }
}

將剛剛的Handler function改成這樣,注意function的前面有加上了 async

async function lineEventHandler (event) {
  const lineClient = new line.Client(lineConfig)

  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null)
  }

  const result = await askOpenAI(event.message.text)
  // 將OpenAI回的訊息傳給使用者
  return lineClient.replyMessage(event.replyToken, {
    type: 'text',
    text: result
  })
}

最後,因為我不想讓他每次都回我,尤其是如果將他加入群聊的時候,可能會很吵,因此我在Handler中再加入了一個判斷

  if (event.type !== 'message' || event.message.type !== 'text') {
    return Promise.resolve(null)
  }

// 改為

  if (event.type !== 'message' || event.message.type !== 'text' || !event.message.text.includes('村長')) {
    return Promise.resolve(null)
  }

這樣子的話,就只有訊息中包含了村長兩個字時,才會將訊息傳給OpenAI等回應。

MySQL 筆記

新增使用者

mysql> CREATE USER 'user_name'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';

移除使用者

mysql> DROP USER 'user_name'@'localhost'

修改使用者密碼

mysql> ALTER USER 'user_name'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password';

查看使用者權限

mysql> SHOW grants for 'user_name'@'localhost';

授權使用者權限

#針對某資料庫開部分權限
maysql> GRANT SELECT,INSERT,UPDATE,DELETE,CREATE ON db_name.*(or table_name) TO 'user_name'@'localhost';

#針對某資料庫開所有權限
maysql> GRANT ALL PRIVILEGES ON db_name.*(or table_name) TO 'user_name'@'localhost';

#針對所有資料庫開所有權限
maysql> GRANT ALL PRIVILEGES ON *.* TO 'user_name'@'localhost';

重整關於權限的資料表

mysql> flush privileges;

Ubuntu ssh 安全性設定

起一台新的機器後,為了確保機器基本上有一定的安全性,我們可以在ssh登入這個環節加入一些門檻,增加登入的難度。

預計要做的設定有:更改ssh port號 -> 更改登入帳號 -> 關閉root登入 -> 關閉密碼登入

更改ssh port號

就像改門牌地址一樣,當對方不知道門牌地址時,就要一個一個門鈴去按按看,才知道要找的人住在哪間。當不知到ssh port號,要暴力登入時,就必須要一個一個port去試。

打開並修改ssh config:sudo vim /etc/ssh/sshd_config

找到 #Port 22,移除開頭的「#」號,把後面22改成想要的port號即可(列出使用中的port號 ss -tulpn | grep LISTEN 沒有列出的都可以用)。另外還可以新增 Protocol 2,使用更安全的ver2協定。修改完成後要重啟ssh服務:sudo systemctl restart ssh

重要:如果ufw防火牆有開的話(可以用 sudo ufw status 來檢查是否有開)記得要加上新的rule:sudo ufw allow <new ssh port>

更改登入帳號

首先先用 adduser <user name> 新增使用者名稱。room number, work phone, home phone, other 為選填。接著允許使用者使用sudo指令 usermod -aG sudo <user name>。再次打開剛剛ssh config文件 sudo vim /etc/ssh/sshd_config,新增一行 AllowUsers <user name>,修改完成後要重啟ssh服務:sudo systemctl restart ssh

關閉root登入

一樣打開ssh config sudo vim /etc/ssh/sshd_config,找到PermitRootLogin yes,改成no,修改完成後要重啟ssh服務:sudo systemctl restart ssh

關閉ssh密碼登入

這一步要介紹如何關閉密碼登入,只使用ssh key登入。只使用ssh key登入的好處是不會被try密碼,也省去打密碼的不便。不過,當不是使用自己的裝置登入,或是需要新增一台裝置時,稍微有點麻煩。

但這個步驟要小心一點,未測試完成前,請勿關閉console。若不慎無法登入時,可以試試看vps管理介面中的console功能來進入主機。

首先為剛剛新建立的新使用者,新增 /home/<user name>/.ssh 目錄,在這個目錄中新增 authorized_keys 檔案。authorized_keys中要填入要登入的機器的ssh key,長得像這樣:ssh-rsa AAAC4SDFasdfsdfsdfAAAA/xfvdfevvf.......sdfsdf= xxx@macbook.office

在要登入的電腦上用command line輸入 ssh-keygen,來產生ssh key。完成後輸入 cat ~/.ssh/id_rsa.pub 顯示key並將他複製起來。打開剛剛主機上的authorized_keys檔案,貼上key,多筆則用斷行來分開。

一樣打開ssh config sudo vim /etc/ssh/sshd_config,修改 PasswordAuthentication yesno,然後再重啟ssh服務:sudo systemctl restart ssh

更多保護措施

上面提到的ufw如果沒開的話,建議可以開一下,指令也相當簡單直覺。記得新增所需要的rules。另外,建議可以安裝fail2ban,若有人一直重複在嘗試登入,就會被暫時擋起來。

跨網域請求時什麼情況下會發送preflight request

當我們跨網域呼叫API時,瀏覽器會自動先發送一個method是OPTIONS的Preflight request,向後端確認是否允許跨網域請求,若可以才會繼續原本請求的request。但有時候明明就是跨網域請求,卻沒發送OPTIONS,到底為什麼呢?

問題就在於我們的請求必須是「非簡單請求」,才會讓瀏覽器發送 Preflight Request。那什麼是「非簡單請求」呢?定義為「只要滿足下列任一條件」就是「非簡單請求」:

  1. 當method「非」GET, POST, HEAD。
  2. header中有 user agent 自動設定的標頭「以外」的標頭。例如:authorization。
  3. Content-Type 「非」這三種之一 application/x-www-form-urlencoded, multipart/form-data, text/plain。

那如果我們故意用「簡單請求」做跨網域請求,會發生什麼事?

在client端送出GET與POST(但不POST任何資料)的情況下,後端還是可以收到東西。因為送出時,瀏覽器會當作一般的請求,不會發出Preflight Request去確認是否可跨網域,因此不會被擋掉。只是當server端回應時,因為沒有回應跨網域的header資訊,所以會被瀏覽器擋掉,client端會收不到response。

用express測試的結果,GET一定會收到Request。POST的情況有點不同,如果沒有帶任何資料,也會收到Request,但如果有Post資料的時候,就會被擋掉。

SCSS calc variable error

今天遇到了一個詭異的問題。想用css variable來計算transform-origin的z軸,因此在scss中輸入:

transform-origin: 50% 50% calc(var(--board-width * -0.5));

當在dev模式中,一切沒問題,但production build時,就會噴Syntax Error。但更奇怪的是,其他地方一樣也有用到 calc(var()) 這樣的寫法,卻沒有錯。應該是dart-sass (v1.52.2)的bug吧。

最後把 calc(var(–board-width * -0.5)); 在包成一個variable,竟然就可以了。

--depth: calc(var(--board-width * -0.5));
transform-origin: 50% 50% var(--depth);

CURL筆記

加上Header

當我們在使用API時,應該都會需要我們透過header傳送token,或是指定所需要的格式(例如:json, xml),亦或是所需要的語系⋯⋯等等。我們可以透過 -H “Key: Value” 這樣的指令格式來輸入想要的Header。

curl -H "Authorization: Bearer mytoken...." -X GET https://xxx.xxx.xxx

加上多個Header

要加入多筆Header時也很簡單,只要再加一個 -H {header內容} 即可:

curl -H "Authorization: Bearer mytoken...." -H "Content-Type: application/json" -X GET https://xxx.xxx.xxx

利用CURL POST Form Data

當我們需要POST資料給server時,我們可以透過 -X 指令指定 method,要POST的話就輸入 -X POST {url},要PUT的話就輸入-X PUT {url},其他的methods就依此類推。然後,我們可以用 -d 指令來指定要傳輸的資料。form data的格式就像query string一樣是key=value,多筆時中間以&相連。

curl -d 'a=1&b=2' -X POST https://xxx.xxx.xxx

利用CURL POST JSON

與POST form data 的方式一樣,只是 -d 中的格式不是query string而是json,且可能會需要在header指定Content-Type為application/json。

curl -d '{"a":1,"b":2}' -H "Content-Type: application/json" -X POST https://xxx.xxx.xxx