每特17劃

及時當勉勵 2004/06/07

自由市場並不自由 — 2023-04-16

自由市場並不自由

記得很久以前,曾和一個朋友聊到時事經濟的話題,當時朋友主張政府不該干預,因爲政府干預違背了自由市場的原則。而我對此表達了反對的意見,論點大意如下:

一個完全無干預、無管制的市場,必然走向不公平競爭而大者恒大、壟斷市場。
而一個能公平競爭、自由選擇的市場環境,其實反倒是外力介入管理下的結果。

註: 應該是 10 年以前的事了,記憶不一定精確達意。

2017-12-22

央行總裁彭淮南的反擊,19年來的大平反|天下雜誌

2018-02-22

國外篇/德國:捍衛多元閱讀風景 堅守圖書統一定價 | 文化+ | 中央社 CNA

  • 德國出版業的統一訂價制度
  • 這某種程度違背了自由市場原則,但卻使出版業維持了生機
  • 如何權衡?

2023-04-15

立法院全球資訊網 -圖書統一定價制相關問題研析

  • 英國在取消淨價圖書協議(Net Book Agreement)後,十年間關閉了三分之一的獨立書店,獨立書店已不到 1,000 家
  • 放任市場自然競爭的美國,也有半數獨立書店在過去二十年歇業
  • 墨西哥在 2008 年通過圖書統一定價法案,原本希望可以挽救書店關門潮,避免通路折扣戰,但是卻因法案缺乏強制執行的條款,亂下折扣的違法行為卻無相應罰則,反而成為變相懲罰守法通路

【深度報導】電商書價打到骨折 出版界史無前例團結向政府討救兵 – 太報 TaiSounds

2023-04-15

個人看法:

適當管制 〉 最小管制 〉 不當管制 〉 放任毫無管制 
  • 管制的適當建立在精準客觀的數據之上
  • 若管制的手段不佳而產生反效果,反而使大家轉向拿掉管制的一方
  • 然而,若落入放任無管制而混亂的苦果後,不堪其擾的人們又會再回頭擺向管制,(ex: 主張治亂世用重典)
  • 若管理手段仍未進步,週而復始
我的 Linux 啓蒙緣起 — 2023-03-18

我的 Linux 啓蒙緣起

記得還在高中的時候,有次在電視上看到國外有人在抗議電腦強迫搭載 Windows 作業系統的遊行。新聞畫面裏,有人呼喊他們不要裝 Windows, 他們要裝 Linux, 他們要退費。

雖然這則新聞只佔了短短的邊角片段,但引起了我的好奇心: " Linux 是什麼? 好像是很酷的東西的樣子?!"

於是,我就去書店找看看有沒有相關的東西可以學習。記得當年買的第一本書是 ”施威銘“ 的,裏面附了 3 片光碟,其中一片是 Slaceware 3.6 。是我第一次嘗試安裝的 Linux distro。

這次回高雄時,看到以前的舊書籍,一時興起調查起來以前的舊新聞。沒想到還能找到以前的資料。

新聞

如果記憶沒錯的話,當時國外的活動應該就是 Windows Refond Day。

而當初買的第一本書,後來查出來是這一本:

  • ISBN: 9789577174642
  • 書名: LIUNX實務應用
  • 施威銘研究室
  • 出版日期: 1998-12-01
  • 旗標出版股份有限公司

其中,我在一個舊網頁 瞥見了下面敘述:

Imgur

其中提到的 3 片光碟,到今天我都還有印象:

  • Slaceware 3.6
  • Redhat 5.2
  • Debian 2.0

相關鏈接:

粗略調查 XQueryPointer 背後的運作 — 2023-03-10

粗略調查 XQueryPointer 背後的運作

這兩天於聊天頻道看到一個問題:

請問這個是函數封裝嗎~對c++還不太熟悉 我搜尋找不到 XQueryPointer 的 function 定義

註: 程式碼類似於 barrier/XWindowsImpl.cpp :

Bool XWindowsImpl::XQueryPointer(Display* display, Window w,
                                 Window* root_return, Window* child_return,
                                 int* root_x_return, int* root_y_return,
                                 int* win_x_return,  int* win_y_return,
                                 unsigned int* mask_return)
{
    return ::XQueryPointer(display, w, root_return, child_return, root_x_return,
                           root_y_return, win_x_return, win_y_return,
                           mask_return);
}

因爲,昨晚剛好有聊到 Xwindow System 及 libinput 的話題,加上剛好有人問到 QueryPointer 的問題,便 trace code 一番,並分享了一下看到的內容跟過程。

C++ 的 :: 開頭語法的疑問

很久沒用 C++ 了,看到 ::XLockDisplay(display);:: 開頭的寫法,一時不知道是什麼意思。 後來查到了, :: 是指 global namespace 。

參考資料如下:

程式架構

上面程式感覺像 Design Patterns 中的一段 p.63

Imgur

用途大約是:

  • XWindowsImpl 是要實作的類別, 而產生的物件是符合某個 XWindows 的通用規範。ex: IXWindowsImpl
  • 呼叫 XWindowsImpl::XQueryPointer() 時,背後會呼叫既存的 ::XQueryPointer()

XQueryPointer

我對後面發生什麼事好奇,請 ChatGPT 生一段 sample code 給我, 如下(截圖):

from Xlib import X, display

# 顯示器物件
d = display.Display()

# 螢幕物件
screen = d.screen()

# 根視窗
root = screen.root

# 取得滑鼠位置
pointer = root.query_pointer()

# 輸出滑鼠位置
print("滑鼠位置: (%d, %d)" % (pointer.root_x, pointer.root_y))

(註: 要裝 python-xlib 套件)

程式真的有執行如下圖

Imgur

下一步,根據裏面主要函式 root.query_pointer() 這個線索,去 GitHub 查背後內容, 找到 source code 片段:

    def query_pointer(self):
        return request.QueryPointer(display = self.display,
                                    window = self.id)

看出這有種類似發送 HTTP request 的味道, 進一步查 QueryPointer 的實作,找到 source code 如下:

class QueryPointer(rq.ReplyRequest):
    _request = rq.Struct(
        rq.Opcode(38),
        rq.Pad(1),
        rq.RequestLength(),
        rq.Window('window')
        )

    _reply = rq.Struct(
        rq.ReplyCode(),
        rq.Card8('same_screen'),
        rq.Card16('sequence_number'),
        rq.ReplyLength(),
        rq.Window('root'),
        rq.Window('child', (X.NONE, )),
        rq.Int16('root_x'),
        rq.Int16('root_y'),
        rq.Int16('win_x'),
        rq.Int16('win_y'),
        rq.Card16('mask'),
        rq.Pad(6),
        )

根據上面的程式片段,我注意到有 opcode38 這兩個關鍵字。 依此線索,之後在 google 查到相關的兩處相關定義:

概況

從上述的線索跟取得的資訊,推測取得滑鼠座標的框架大概是:

  • 視窗系統背後是 client, server 架構

  • 使用者應用程式,屬 client 端

  • User App 依通訊標準(XWindow prototol) 產生 request 封包,並向系統內的 XWindow server 發送要求。

    • 產生 request 封包有多種方式
      • 以 C/C++ 透過 libx11 (Xlib.h, libX11.so) 來生成 QueryPointer request 封包
      • 以 Python 透過 python-xlib, 來生成 QueryPointer request 封包
      • 以 Rust 生成 QueryPointer request 封包
  • XWindow server 處理完後回應資料給 User App。收到回應後的 User App 再繼續它的後續動作。

  • C/C++ => libX11.so => 產生 QueryPointer 要求封包 => 發給 Xwindow System(server)

  • Python => python-xlib(底層應該還是 libX11.so) => 產生 QueryPointer 要求封包 => 發給 Xwindow System(server)

Server 在哪?

在 Linux 上用 ps 指令查詢,找到 X server:

/usr/lib/Xorg :0 -seat seat0 -auth /run/lightdm/root/:0 -nolisten tcp vt7 -novtswitch

以下述的 netstat 指令找 Xorg 相關的線索:

sudo netstat -n -p | grep Xorg

得到結果如下:

unix  3      [ ]         STREAM     CONNECTED     21239    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     31689    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     19362    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     24421    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     24419    548/Xorg             @/tmp/.X11-unix/X0
unix  3      [ ]         STREAM     CONNECTED     25019    548/Xorg             @/tmp/.X11-unix/X0

大略得知, 系統內由 Xorg 運行 XWindow server ,並於 @/tmp/.X11-unix/X0 接收 User Apps 發來的 request.

X client 從那裏知道 X server 的指向位置?

透過 DISPLAY 這個環境變數,例如:

export DISPLAY=:0

Xephyr :42 &
DISPLAY=:42 ssh -Y host

註: Xephyr 爲一個 nested X server ,可在目前的 Xwindow 下執行另一個 X server.

Server 於何處接 XQueryPointer 的 request?

一樣進 GitHub 查 QueryPointer。 但直接查, 搜尋結果太多太雜。(如圖)

Imgur

憑經驗, freedesktop.org 是 XWindow 發展的主力組織之一,在搜尋 GitHub 搜尋時加上 org:freedesktop 的範圍條件, 可大幅減少搜尋雜音,從 32K 多筆結果精簡到約 50 多筆資料(如圖):

Imgur

進一步檢視後,不久就找到處理 QueryPointer request 的 source code:

Imgur

此處再往下,預期能進一步找到 XWindow server 去取得 keyboard/mouse 數據的動作。

2023-03-19 試用 Rust 寫 sample code

準備目錄與檔案如下:

./test/
├── build.rs
├── Cargo.toml
└── src
    └── main.rs

Cargo.toml:

[package]
name = "test"
version = "0.1.0"
edition = "2021"

[dependencies]
x11 = "2.21.0"

main.rs:

extern crate x11;

use std::ptr;
use x11::xlib::{XOpenDisplay, XCloseDisplay, XDefaultRootWindow, XQueryPointer};

fn main() {
    unsafe {
        let display = XOpenDisplay(ptr::null());
        let root_window = XDefaultRootWindow(display);

        let mut root_return = 0;
        let mut child_return = 0;
        let mut root_x_return = 0;
        let mut root_y_return = 0;
        let mut win_x_return = 0;
        let mut win_y_return = 0;
        let mut mask_return = 0;
        XQueryPointer(
            display,
            root_window,
            &mut root_return,
            &mut child_return,
            &mut root_x_return,
            &mut root_y_return,
            &mut win_x_return,
            &mut win_y_return,
            &mut mask_return
        );

        println!("Mouse position: ({}, {})", root_x_return, root_y_return);

        XCloseDisplay(display);
    }
}

build.rs:

fn main(){
    println!("cargo:rustc-link-lib=X11");
}

( 註: build.rs 是特殊檔案,用於編譯參數設定。Ref: Build Scripts – The Cargo Book )

然後再執行:

cargo build
cargo run

預期會得到結果如下:

    Finished dev [unoptimized + debuginfo] target(s) in 0.02s
     Running `target/debug/test`
Mouse position: (653, 967)
自己對茶與咖啡的不同之處 — 2023-03-03

自己對茶與咖啡的不同之處

昨晚聚會時,由 autoteamaker 之故而聊到茶與咖啡的不同的話題。 有的人比較偏好咖啡, 而我自己是比較喜歡茶的。 不過,不代表我不喝或是排斥咖啡。基本上,只要是好喝的飲料,我都喜歡喝。

更精確一點的說,對於固定每天都要來上一份的,我比較傾向茶。 列了一下自己對茶和咖啡的感覺差異大致有:

咖啡
感覺 像是放一顆炭精進烤肉架 像是用水沖洗被油封塵蓋的地方
喝的量 1~2 杯 1 杯 ~ 3 壺
器具 大勝,咖啡機按一下就有 相對麻煩。還要洗濾茶網、清茶渣之類的
上火程度 較容易上火 較溫和。有些喝多會覺得冷(偏寒)
抒壓 不太有、反而會更亢奮 稍有緩解緊張感
調味 除單品外,常佐糖、奶精、檸檬、… 傾向不加任何調味
口味 多元 清爽、喝多不膩

總的來說,我偏好茶的帶給我的特色是:一種逐漸神清氣爽的感覺,順便喝很多水。

這裏特意避開對茶與咖啡的健康成份的分析。 因爲在沒有被可靠的驗證之下,這些資訊對我來說,多是扮演一種”安慰劑效應“的角色,說服自己 ”正在固定攝取對自己有益的東西“ 而已。 若認真以對身體健康的來說,兩者可能都遠不如一顆綜合維他命或一份表飛鳴(益生菌)來得簡單有效。 於是,最終還是會回到本質: “因爲它好喝” ! 而 “好喝” 就每人口味各異,各自表述、見仁見智了。

茶與咖啡的比較有太多地方被討論了,自己就不獻醜了。 但客觀來說,在 2023 的這個當下, 咖啡的發展體系顯然是遠遠勝於茶的體系。(在 18 世紀時可能是反過來的) 而目前在生活中,想要喝到“好茶”,也是確實有他麻煩的地方,這可能也是自己想作工具來改善的動機吧。

人多少會想得到認同,然爲身外物,可遇不可求。更重要的是,內觀自己真正追求的是什麼? 願大家都能找到並跟隨自己真心喜歡的事物。

捐款支持無國界醫生的工作 — 2023-02-17

捐款支持無國界醫生的工作

Imgur

幾個月前(約莫 2022 年 10 月),在捷運站出口遇到了無國界醫生的勸募志工,拿了張傳單回家,但後來一直拖着沒完成。近期因土敘大地震的天災事件,便特別抽個時間把它完成。

線上捐款操作不難,只要有 email 、信用卡、姓名、身份證字號的資料即可,對有網路購物經驗的人來說應該相當簡單。

從官方網站上可以找到最新的捐款資訊: 捐款方式 | 無國界醫生(台灣)

不過這裏以我自己當時收到的傳單上的 QRcode 的連結來執行。

線上捐款流程

首先,依拿到的傳單的 QRcode 掃瞄得到的連結如下:

https://www.msf.org.tw/f2f

依上述連結打開網頁後,可看到三個預設的捐款按鈕:

Imgur

值得一提的是,每月捐款金額是可以自己訂的。 這裏我以 300 TWD (約 10 USD) 的額度來進行。 (註: 經過實測,有限定"最低捐款金額爲 100 TWD")

下一步,進到填寫資料的地方,則以最精簡的方式勾填下述資訊:

Imgur

  • 姓氏
  • 名字
  • 電子信箱

Imgur

  • 收據寄發方式 => 我選 "直接上傳至國稅局"
  • 打勾 "收據抬頭同聯絡人姓名"
  • 身分證字號
  • 打勾 "我同意 隱私權條款"

Imgur

(因爲不喜歡被推播訊息,所以改預設設定如下)

  • 打勾 "不,我不願意接受相關訊息"
  • 於其下打勾 "以上皆是"
  • 再按 "下一步"

之後進到下一頁,填寫信用卡付款資訊,跟常見的網路購物一樣簡單:

Imgur

  • 信用卡號碼
  • 有效日期
  • 卡片背面後三碼

最後按 "確認捐款" 。

完成之後,會看到 "謝謝您的支持" 的結束畫面。

Imgur

信用卡交易會在按下 "確認捐款" 後就立刻進行。 同時也很快收到來自 donation@taipei.msf.org 信箱的 email 通知以及手機簡訊(若有填手機號碼的話) 裏面會載有金額、捐款人編號的訊息。

Imgur

這樣就完成定期每月捐款無國界醫生的設定了。

取消捐款的方式

取消捐款的方法,目前是要透過打電話的方式。

沒有提供捐款人方便自主取消捐款的機制,是目前我覺得美中不足的地方。 有些人可能會疑惑,不是讓捐款人離開越不方便越好嗎?爲何要讓反直覺的方便離開。

對此,我想引用下述的觀點:

其中提到:

因為我害怕萬一要切換回來會太麻煩,所以沒有用他們的服務

這就是進入障礙。不是切換進去有多難,而是切換出來有多難。

資訊檢查

無國界醫生本身有於官方網站主動提供公示資訊:

其中也有提供 捐款明細資料庫 的查詢工具。不過,據我實測的結果並不是即時的,下方有顯示提示訊息:

本資料庫僅顯示三年內資料,且不包括近兩個月。若有其他疑問,請來信...

預計可能要 2 個月後才看得到資料。

另外,於公益勸募管理系統輸入關鍵字查詢 "無國界醫生" ,亦有對應資訊:

Imgur

關於無國界醫生

在線上的維基百科有關於無國界醫師的介紹:

而其組織的核心精神寫於:

對於無國界醫生

自己相當欣賞與敬仰這個組織的地方有

真正實踐

說是一層面,做又是一層面。

入危邦、居亂邦,主動至第一線提供援助。 這精神說起來容易,但做起來很困難。 不時會聽到無國界醫師的人員在一些武裝衝突的地區實地執行任務的報導。 對於他們頂着生命安全實踐理想的精神,一直深感敬佩。

着重專業分工

很多人提到救助志業時,多是只有熱血, 沒有專業。然而付諸實現時, 是必須要俱備足夠的專業自主能力,才能真正提供別人實質的幫助。

例如,以這一篇採訪文:

放下武器就有醫療權 戰火下的醫者魂 | 生活 | 中央社 CNA

文中提及:

選址時也要分辨哪裡是衝突的核心區、哪裡是交通要衝,將需求、安全性、後勤補給等因素都納入考量,並針對不同國家政經情勢訂定規則,確保醫療團隊人身安全,例如幾小時必須回報一次、哪些地區能去或不能去,在治安條件特別差的地區,甚至會禁止醫療團隊「落地」,只能搭車移動,以免遭到綁架。

以及

無國界醫生全球每年執行為數眾多的任務,前線醫療人員調度、安排是一大挑戰,劉鎮鯤說,總部設有一套龐大且複雜的「配對系統」,依照任務所需的科別、時間與各國「志願醫師資料庫」進行配對,不同科別醫師的任務時間長短也不同。

對於中立的拿捏

“中立和不偏不倚” 是所有特質中,我覺得最難做到的部分。

在無戰事地區,要做到中立相對簡單一些。因爲兩邊都不幫,也兩邊都不得罪。 然而,在激烈的武裝衝突地帶中,中立反而最危險的。 因爲站在天平的中間,兩端都持槍指着你,都覺得你可能是站在對方那一方。稍有不慎,就粉身碎骨。 而在此惡劣環境,才能真正考驗出對原則的持守程度。

信任的建立,不是一朝一夕就能得到,而是要一路走來始終如一。 而當下的信譽,也是過去每一個關鍵選擇,每一個犧牲的生命,一路累加堆疊起來的。 中立的特性,使他們得以進入其他勢力無法進入的地帶。 但也因此,也容易成為其他勢力意圖滲透及利用的對象。

從難民兒童成為無國界醫生職員—索羅的故事 | 無國界醫生(台灣) 一文中,主角 Moses Soro 提及:

"雖然我從未忘記對自己的承諾,但我知道只要自己還在活躍於政界,就不該加入無國界醫生,因為這有悖於組織中立及不偏不倚的原則。"

這或許是當一個組織的精神得到每一個參與者的認同並自主實踐的象徵。

資源目標取捨

大家都希望惠施天下,但現實是資源是有限的。 對於一個持續有捐助資金流入,持有一定資源在手的組織來說。 決定不作什麼,可能比決定要作什麼,還困難更多。

從 2021 年的資料 全球財務與人力資源概覽 – AR2021 中,可看到多數的資源是投入更多於環境較困乏的地區。

過去數年的參考資訊:

財務自主、公開、透明

無國界醫生 – 維基百科,自由的百科全書 的資訊,提及該組織超過 80% 的資金來自獨立捐款。 而該組織自述其 資金與財政 | 無國界醫生(台灣) 的 97% 的經費來自公眾捐款。

一個組織的資金組成與來源性質,對該組織的性格與發展有根本性的影響。 能做到這個比例,覺得非常了不起。

此外,於 放下武器就有醫療權 戰火下的醫者魂 | 生活 | 中央社 CNA 一文中,提及:

”募得的款項有80%用於前線、20%用於行政,只要募得隔年任務夠用的款項,就會停止募款。”

2023-02-17 土敘大地震

2023-02-17 Moses Soro

一包 Plumpy’ nut 背後的故事:我如何從難民兒童,成為無國界醫生職員|無國界醫生|換日線

2023-02-17 廖滿嫦

無國界醫生主席廖滿嫦卸任 批西方國家自私 | am730

  • 廖滿嫦(Joanne Liu)
  • "她指西方國家沒在疫症初期施援,直至國內出現個案、引發恐慌才介入,但已太遲" (伊波拉)
  • "MSF於地中海一條危險偷渡路線的搜救,遭意大利阻撓,指控其丟棄醫療廢物於海中,令救援暫停數月"
  • "美軍15年空襲阿富汗東北部昆都士的醫院,造成42人死亡,包括14名MSF醫護人員,事件反映醫護設施在衝突中受保護的清晰界線,變得模糊"

2023-03-01 劉鎮鯤

放下武器就有醫療權 戰火下的醫者魂 | 生活 | 中央社 CNA

  • 劉鎮鯤‬
  • “選址時也要分辨哪裡是衝突的核心區、哪裡是交通要衝,將需求、安全性、後勤補給等因素都納入考量,並針對不同國家政經情勢訂定規則,確保醫療團隊人身安全,例如幾小時必須回報一次、哪些地區能去或不能去,在治安條件特別差的地區,甚至會禁止醫療團隊「落地」,只能搭車移動,以免遭到綁架。”
  • “無國界醫生全球每年執行為數眾多的任務,前線醫療人員調度、安排是一大挑戰,劉鎮鯤說,總部設有一套龐大且複雜的「配對系統」,依照任務所需的科別、時間與各國「志願醫師資料庫」進行配對,不同科別醫師的任務時間長短也不同。“
  • “在他看來,無國界醫生是一個相當特別的組織,組織內部沒有強勢的主導者,每個決定都是群體共同做出的決定“
  • ”募得的款項有80%用於前線、20%用於行政,只要募得隔年任務夠用的款項,就會停止募款。”
Migrate to Vim native package manager — 2023-02-13

Migrate to Vim native package manager

我原本是用 Pathogen 來管理 Vim 的外掛。 但發現 Vim 自 8.0 (2016-09-12) 之後,官方已推出的內建套件管理機制。

近來趁重灌電腦之際,便順手將 Vim 相關的外掛從 Pathogen 轉移至新的機制。

新的機制是什麼?

  • 從原本的一層 plugin ,改爲 package 和 plugin 兩層。一個 package 下可包含多個 plugin。

  • 預設以 ~/.vim/pack/ 作爲放 package 的目錄。

  • 提供自動載入及延遲載入(Lazy loading) plugin 的機制:

    • 自動載入的目錄位置: ~/.vim/pack/《PACKAGE_NAME》/start/《PLUGIN_NAME》/
    • 延遲載入的目錄位置: ~/.vim/pack/《PACKAGE_NAME》/opt/《PLUGIN_NAME》/
  • 《PACKAGE_NAME》《PLUGIN_NAME》 就直接是目錄名稱,可以任意自訂。

  • 要新增自動載入的 plugin ,只要將 plugin 直接複製到 ~/.vim/pack/《PACKAGE_NAME》/start/ 目錄下即可。

  • 延遲載入的 plugin 則是複製到 ~/.vim/pack/《PACKAGE_NAME》/opt/ 目錄下,而之後手動載入的指令爲:

    :packadd 《PLUGIN_NAME》
    

官方文件

於 Vim 編輯器內透過下列指令,即可取得官方說明內容:

:help packages

或可查閱線上網頁: Vim: repeat.txt — 5. Using Vim packages

這裏不再重複文件已有的內容,而以紀錄過程中遇到的問題及找到的答案爲主。

Q: packpath 的設定值是什麼? 如何顯示?

  • packpath 預設值取自 runtimepath, 即包括有 ~/.vim/ 目錄。

  • 查詢 packpath, rumtimepath 的設定值,指令如下:

    :set packpath
    :set runtimepath
    
  • 若要顯示 Vim 內的所有設定值,則是用:

    :set all
    :set! all
    
    • 註: 其中 :set! 的部份加了 ! 的差異是用於一行顯示一個,而非預設的一行顯示多欄。

Q: ~/.vim/pack/*/start/ 的目錄是只掃描一層,還是深入多層子目錄?

要注意 ~/.vim/pack/, ~/.vim/pack/*/start/, 跟 ~/.vim/pack/*/opt/ 都只掃描一層子目錄,並無支援下層所有子目錄 (Recursive subdirectories) 的設定。 因此安置 plugin 跟 package 時,要留意目錄的位置。 多一層、少一層就不會如預期般載入。

Q: ftdetect 是什麼?

"ftdetect" 的 "ft" 是指 file type,ftdetect 是用於偵測檔案型別的 script 。

Q: packadd! 加了 ! 的差異

有時候會需要 Vim 在完全不載入 plugin 下執行 (ex: debug 或 shell script) ,而使用 vim --noplugin 的選項。 然而,若原先 optional plugin 是在 ~/.vimrc 設定檔或是某處 script 裏以 :packadd 《PLUGIN_NAME》 執行的話,還是會被逕行載入,而與 vim --noplugin 的目的衝突。 若是希望 optional plugin 在 vim --noplugin 的狀況下不被意外載入,可改用 :packadd! 《PLUGIN_NAME》。兩者的載入條件比較表如下:

vim vim --noplugin
:packadd load load
:packadd! load not load

簡單來說,要點有:

  • 若在 vim 一般模式,則 :packadd:packadd! 無差異
  • 若要在 vim --noplugin 時不被意外載入, 則使用 :packadd!

從 Pathogen 轉移到 Vim native

Step 1: 將外掛從 Pathogen 的目錄搬到新目錄

mkdir -p ~/.vim/pack/default/start/
mv -v -i ~/.vim/bundle/* ~/.vim/pack/default/start/
rmdir ~/.vim/bundle/

註: 這裏的 default 是自訂名稱,可依個人需要更改。

Step 2: 刪除 Pathogen 的檔案

[ -f ~/.vim/autoload/pathogen.vim ] && rm -v ~/.vim/autoload/pathogen.vim

Step 3: 拿掉 Pathogen 的設定

~/.vimrc 中,刪除下列設定

execute pathogen#infect()

完成後,再重新啓動 Vim 程式即可。

軼聞

2010-08-12 聚會時 pct 提到了 pathogen 和 c9s 寫的 Vimana

關於 Vimana , Tsung 曾寫過一篇 blog 介紹: Vim 的 套件(外掛)管理系統 – Vimana – Tsung’s Blog

2013-03-28 時,yan 也提到 pathogen

2015-01-08 時, peter 提到 vundle

2015-04-09 時, dlin 提到 Shougo/neobundle.vim

2016-11-10 , BestSteve 提到 Vim 8.0 釋出。

電腦可以 patch,人腦不能 patch — 2023-01-27

電腦可以 patch,人腦不能 patch

電腦可以 patch, 人腦不能 patch

總覺得這句話太過經典,以至於當年參加活動時的記憶都模糊了,唯獨這句話始終烙印在腦海中。

當時好像是講者提到,即使知道問題的原因,也有解決問題的技術跟方法,但在可行性上卻受限於人本身的使用習慣和對資安的態度。講者認爲,在現實世界中要改變使用者非常困難而幾乎不可爲,因而說出 "電腦可以 patch, 人腦不能 patch"。

註: 這已是 17 年前的記憶,可能與原文有所出入。

出處

這次過年回家,在整理舊物時,意外發現還留着當年的紙本資料。大致確認這句話是聽自於,2005 年 7 月 HIT(Hacks In Taiwan) 年會的議程: "The Evolution of Windows Spyware Techniques",講者爲 “Birdman"。

Imgur

Imgur

Imgur

相關連結:

搶救 OneTab 資料 Save my OneTab data — 2023-01-12

搶救 OneTab 資料 Save my OneTab data

昨晚在如往常按下 OneTab 的圖示後,Chrome 瀏覽器的 OneTab 分頁卻進入了無窮等待載入狀態, 再也顯示不出 OneTab 的頁面了。在網路上能查到 OneTab 在 Linux 上的資料目前並不多, 只好自力求生。

OneTab 是什麼?

OneTab 是一個瀏覽器外掛,它的功能是將瀏覽器的分頁們整批收起來儲存到一頁式的顯示頁面。它主打的訴求是 "節省記憶體",但原理其實只是關掉分頁,從而釋放出其佔用的記憶體。因爲它提供了一個對整批分頁的 Save / Restore 的方便操作方式,對於瀏覽器常常有大量分頁的使用者來說,是個不錯的工具。

進一步可參考: OneTab extension for Google Chrome and Firefox – save up to 95% memory and reduce tab clutter

資料存在哪?

網路上找到的線索主要有:

  • C:/Users/《Username》/AppData/Local/Google/Chrome/User Data/Default/Local Storage/leveldb
  • chrome-extension_chphlpgkkbolifaimnlloiipkdnihall_0.localstorage
  • 資料庫爲 LevelDB , 副檔名爲 .ldb

然而,實際在 Linux 系統下卻找不到目標檔案。

爲找出真實位置,我採取下述方法:

  1. 首先,建一個新的 Profile 。 方式可參考說明: Create Chrome browser profiles

  2. 在新的 Profile 下安裝 OneTab。可參考: OneTab – Chrome 線上應用程式商店

  3. 進到 Profile 的目錄下,持續偵測監看某個目標網址(ex: 6bcf7279.info)。例如:

    ```
    cd $HOME/.config/google-chrome/《NEW PROFILE》/
    watch 'grep -nHr "https://6bcf7279.info" . | cut -c-80'
    ```
    
  4. 開啓目標網址,並觀察底下檔案的動靜。 註: 此時異動的主要是瀏覽歷史記錄,還不是 OneTab 資料庫。

  5. 將目標網址加進 OneTab,並觀察此操作所觸發的新增異動。此時的異動,才是 OneTab 資料庫真正的位置。

透過這個方法,最終找出實際位置在:

$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/

如何讀取?

OneTab 的資料以 LevelDB 資料庫方式儲存。一開始試了幾種工具都沒成功,後來試成功的有兩個:

Method: ldbdump

在 Linux 系統下, 須先安裝 python-leveldb 套件。之後可執行下述指令存取 OneTab 的資料:

# 註: 將 "《NEW PROFILE》" 更換爲實際的目標名稱
# NOTE: Replace "《NEW PROFILE》" with your target profile
LDB_DIR="$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/"

LDBDUMP_SRC_URL="https://gist.github.com/mkorthof/412e3cb64785c4f136bbb7f6a9d3a71c/raw/e407eda812448e711a1a496cfd480c227eebacfb/ldbdump.py"
pushd "$LDB_DIR"
python <(curl -skL "$LDBDUMP_SRC_URL") -8j "$(pwd)"
popd

NOTE: 若出現下述失敗訊息,則建議先關閉 Chrome 後再執行。

Traceback (most recent call last):
  File "/dev/fd/63", line 74, in <module>
leveldb.LevelDBError: IO error: lock /home/user/.config/google-chrome/Default/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/LOCK: Resource temporarily unavailable

執行成功的話,輸出的內容大致如下:

{
"installDate": 1674487233973.0, "lastSeenVersion": "1.58", "settings": "{\"oneTabTabState\":{\"index\":0,\"pinned\":true,\"active\":true,\"updateDate\":1675574927466,\"updateEvent\":\"onRemoved\"}}", "state": "{\"tabGroups\":[{\"id\":\"api8W1May_apwfeZc3Ulw3\",\"tabsMeta\":[...],\"createDate\":1675489094057},{\"id\":\"1eydQsFZj7QgGOoAQ6jt1c\",\"tabsMeta\":[...],\"createDate\":1675335420561},{\"id\":\"J2-OWp5GcgzhgZu7ZN6yqx\",\"tabsMeta\":[...],\"createDate\":1675264586285}]}"
}

Method: LevelDB Dumper

LevelDB Dumper 在 Arch Linux 有現成套件: leveldb-dumper 。 安裝此程式後, 可執行指令如下:

# 註: 將 "《NEW PROFILE》" 更換爲實際的目標名稱
# NOTE: Replace "《NEW PROFILE》" with your target profile
LDB_DIR="$HOME/.config/google-chrome/《NEW PROFILE》/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall/"

TMPD=$(mktemp -d)
pushd "$LIB_DIR"
LevelDBDumper -d "$(pwd)" -o $TMPD -b --outputType json
cat $TMPD/LevelDBDumper.json
popd
rm -r "$TMPD"

NOTE:

執行成功的話,輸出的內容大致如下:

[
 {
  "modified_timestamp": "2023-02-05T05:31:21 UTC",
  "path": "/home/user/.config/google-chrome/Default/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall",
  "data": {
   "installDate": "1.674487233973e+12",
   "lastSeenVersion": "\"1.58\"",
   "settings": "\"{\\\"oneTabTabState\\\":{\\\"id\\\":1392050038,\\\"index\\\":0,\\\"windowId\\\":1392049925,\\\"pinned\\\":true,\\\"active\\\":false,\\\"updateDate\\\":1675575512019,\\\"updateEvent\\\":\\\"onUpdated\\\"}}\"",
   "state": "\"{\\\"tabGroups\\\":[{\\\"id\\\":\\\"api8W1May_apwfeZc3Ulw3\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675489094057},{\\\"id\\\":\\\"1eydQsFZj7QgGOoAQ6jt1c\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675335420561},{\\\"id\\\":\\\"J2-OWp5GcgzhgZu7ZN6yqx\\\",\\\"tabsMeta\\\":[...],\\\"createDate\\\":1675264586285}]}\""
  }
 }
]

NOTE: 輸出內容和 ldbdump 結構相同,差異在於 LevelDB Dumper 的內容多了 \\ 的跳脫字元。

匯出 Tab 群組資料(in Markdown)

確定能讀取 OneTab 資料後,下一步是嘗試匯出每一筆 Tab 群組資料,打算依易讀性佳的 Markdown 格式輸出。先安裝 jq 程式,用於處理 JSON 格式。之後寫好的 Shell script 如下:

#!/usr/bin/env bash

# Method: ldbdump
dump_data_with_ldbdump() {
	python <(curl -skL "$LDBDUMP_SRC_URL") -8j "$(pwd)" | jq -Mr .state
}

# Method: LevelDBDumper
dump_data_with_LevelDBDumper() {
	# NOTE: Need to remove extra escape chars and quotes
	cat $TMPD/LevelDBDumper.json | jq -Mr .[0].data.state |
		sed -e 's/\\"/"/g' -e 's/\\\\/\\/g' -e 's/^"//g' -e 's/"$//g'
}

dump_data() {
	# NOTE: Choose and uncomment one of following command

	#dump_with_ldbdump
	#dump_with_LevelDBDumper
}

dump_data | jq -c -Mr .tabGroups[] | while read -r json_TabGroup; do
	var_time=$(echo "$json_TabGroup" | jq -Mr .createDate | sed -e 's/...$//g') # Get the tab group's create time
	var_time=$(date --date="@${var_time}" --iso-8601=s)                         # Convert it to ISO 8601 time formate

	# Add a heading level 2 section and use datetime as section name
	echo -e "## $var_time\n"

	echo "$json_TabGroup" | jq -c -Mr .tabsMeta[] | while read -r json_Tab; do
		var_url=$(echo "$json_Tab" | jq -Mr .url)
		var_title=$(echo "$json_Tab" | jq -Mr .title)

		# Output the tab data with Markdown's link and unordered lists syntax
		echo "- [$var_title]($var_url)"
	done

	echo -e "\n" # Add empty lines to end section
done

NOTE:

  • 關於 jq -c -Mr 中選項
    • -c, 讓同一個 Tab group 的資料壓縮成同一行輸出,以便 shell script 進行處理
    • -M, monochrome, 避免輸出的內容夾雜 colorize code
    • -r, raw, 避開 JSON 輸出字串是多出 " 額外的雙引號

輸出的結果示意如下:

## 2019-09-01T08:25:24+08:00

- [Link title 1](Link url 1)
- [Link title 2](Link url 2)
- [Link title 3](Link url 3)


## 2019-09-04T01:49:51+08:00

- [Link title 4](Link url 4)
- [Link title 5](Link url 5)
- [Link title 6](Link url 6)


重新檢視每天使用流程

在確定能匯出資料後,開始重新思考:”我想要作到什麼樣子?“

回想 OneTab 會累積到 3000 多筆資料,很大部分的原因是,自己一直把 OneTab 當作臨時安置的倉庫。只有把東西存進去,暫時卸下眼前的負擔,卻沒有考慮到後續 Review 消化的流程。這樣的行爲模式,必然導致今天這樣的結果。

反思了一下自己的使用場景, 得到一些想法:

Q: 這些資料是在那裏產生的?

產生地點主要是:

  • 手機瀏覽器(Chrome)
  • Laptop 瀏覽器(Chrome)
  • RSS reader (Feedly)
    • 加在 "Reader Later" 的清單
    • 另開瀏覽器
  • YouTube App
    • 加在 "Watch Later" 的清單
  • 其他,大多是以瀏覽器開啓連接而回到上述的地點

Q: 產生後的流向爲何?

  • 手機瀏覽器 => 加 Bookmark => 無下文
  • 手機瀏覽器 => 用 "Share" 功能傳到 Obsidian => 進到 Obsidian 筆記
    • => 進到當天的 note => 每隔一陣子消化 => 進到對應主題項目的 note
    • => 進到 Queue note => 每隔一陣子消化 => 進到對應主題項目的 note
    • => 進到對應主題項目的 note
      • => 未來查資料、研究、回憶時最容易用到。
  • 手機瀏覽器 => 分頁同步到 Laptop 瀏覽器 => OneTab => 無下文
  • 手機瀏覽器 => 分頁一直累積 => 受不了 => 同步到 Laptop 瀏覽器 => 之後關閉分頁
  • Laptop 瀏覽器 => Copy-n-paste 到 Obsidian => 進到 Obsidian 筆記
  • Laptop 瀏覽器 => 加 Bookmark => 無下文
  • Laptop 瀏覽器 => OneTab => 無下文
  • YouTube App => 進 "Watch Later" => 無下文
  • 其他 => 以瀏覽器開啓連結 => 進到手機瀏覽器或 Laptop 瀏覽器

嘗試改善設計

觀察到:

  • 進到 Obsidian 筆記的資料,會回頭再消化跟重複使用的比例較高
  • 進到 OneTab 或是 Bookmark 的資料,則幾乎就沒有下文了

那麼,若能做到一鍵(或甚至自動)將 OneTab 的資料傳送到 Obsidian 的筆記區,是否就能改善這個問題呢?(示意圖如下)

Imgur

依此想法初步列出目標 :

  • 能將 OneTab 資料同步到 ~/Notebook/Notes/Mobile/OneTab.md
  • 每一個 OneTab 的 Tab group 對應一個 Markdown heading level 2 的 Section
    • 以 ISO 8601 的日期時間格式作標題
    • 網址以 Unordered Lists 列出
  • 比對並跳過已匯出的 Tab group (註: 不用擔心重複執行會產生重複資料)
  • 新進的項目會附加在頁面最下方
  • 若無錯誤的執行完成,則順便一併將該檔案作 git commit 儲存記錄。
  • 儲存完後,回 OneTab 將已匯出的內容清空
  • 安排流程進 OneTab 消化 queued 的group

實作

整合上述的研究內容跟設計想法,實作 script 工具如下:

onetab.inc.sh:

#!/usr/bin/env bash

function murmur {
	echo -e ">>>" $@ >&2
}

function die {
	murmur $@
	exit 1
}

function need_prog() {
	:
}

function cmd_onetab2md() { # Export OneTab data and output with markdown format
	need_prog "LevelDBDumper"
	need_prog "jq"
	need_prog "tac"
	need_prog "git"

	local profile_path="$1"
	local output_path="$2"
	local do_git_commit="$3"
	local ldb_dpath="$profile_path/Local Extension Settings/chphlpgkkbolifaimnlloiipkdnihall"

	[ -d "$ldb_dpath" ] || die "OneTab database directory not found. Path: ${ldb_dpath}/"

	TMPD=$(mktemp -d)
	[ -d ${TMPD} ] && (
		LevelDBDumper -d "$ldb_dpath" -o $TMPD -b --outputType json

		[ -e $TMPD/LevelDBDumper.json ] || die "Output json file not found"
		[ -e "$output_path" ] || die "Output markdown file not found"

		{
			cat $TMPD/LevelDBDumper.json | jq -Mr .[0].data.state | sed -e 's/\\"/"/g' -e 's/\\\\/\\/g' -e 's/^"//g' -e 's/"$//g' |
				jq -cMr .tabGroups[] |
				tac |
				while read -r line; do
					var_id=$(echo "$line" | jq -Mr .id)
					var_time=$(echo "$line" | jq -Mr .createDate | sed -e 's/...$//g')
					var_time=$(date --date="@${var_time}" --iso-8601=s)

					(grep -e "^## $var_time $var_id" "${output_path}" >&/dev/null) && {
						murmur "[SKIP] $var_time $var_id section already exists."
						continue
					}

					murmur "Processing $var_time $var_id section..."
					(
						echo "## $var_time $var_id"
						echo ""

						echo "$line" | jq -Mcr .tabsMeta[] | while read -r line2; do
							var_url=$(echo "$line2" | jq -Mr .url)
							var_title=$(echo "$line2" | jq -Mr .title)
							echo "- [$var_title]($var_url)"
						done

						echo ""
					) >>"${output_path}"
				done
		}
	)
	rm -r ${TMPD}

	do_git_commit=$(echo $do_git_commit | tr 'A-Z' 'a-z' | sed -e 's/\s//g')
	if [ "$do_git_commit" = "yes" ]; then
		murmur "Check git changes and commit..."

		local output_dpath=$(dirname "$output_path")
		(
			cd "$output_dpath" || exit

			(git rev-parse --show-toplevel >&/dev/null) && {
				local var_filename=$(basename "$output_path")
				git diff -- "$var_filename"
				git add -- "$var_filename"
				git commit -s --message="Export and commit by onetab2md script" -- "$var_filename"
			}
		)
	fi
}

使用方式如下:

source onetab.inc.sh
cmd_onetab2md 《Chrome's profile directory path》 《Output markdown file path》 《git commit or not(yes|no)》

例:

source onetab.inc.sh
cmd_onetab2md $HOME/.config/google-chrome/Default $HOME/Notebooks/Notes/Mobile/OneTab.md yes

後記

2023-01-12

在這篇文章 https://amrf000.github.io/blogs/116893 中,作者 amrf000 分析 OneTab 丟失資料的可能原因。另外, 也在另一篇 https://amrf000.github.io/blogs/112515 有提到:

环境里C盘一旦满了之后,onetab保存标签出错后会导致所有onetab旧数据丢失

2023-01-15

我後來發現 GitHub 上也有專案找到 Local Extension Settings/ 的存放目錄:

專案的 README.md 的 Notes 寫道:

Took me a lot of time to figure out OneTab data is stored in the Local Extension Settings folder

可見資料存放位置並不是很多人知道,也不容易找。

另外,從另一個專案:

有看到 storage.js 的線索。storage.js 似乎是屬於 OneTab 在 Firefox 上存放資料的位置。另一處找到的片段:

也指向相同的結果。

2023-01-15

我以關鍵字: "OneTab", "Local Extension Settings" 合併在 GitHub 搜尋時,意外發現有人已經做了 OneTab 的備份工具:

其使用了 Tauri, React + TypeScript, Elastic UI 來打造 GUI 應用程式, 相當值得參考。

2023-02-06

GitHub 跟 OneTab 相關的專案有: onetab · GitHub Topics

沒有人跟一群人 — 2022-12-22

沒有人跟一群人

這樣的場景是否似曾相識?

明天又到了固定練球時間。
但寒流來了,又可能會下雨。
討論區裡似乎沒人出聲,大家都在互相觀望。
明天人少是一定的,不過缺席感到心不踏實。
但若是去了,只有我一個人,獨自在空盪盪的場地吹風淋雨,可不想像個傻瓜。
還是先觀察一下,等有人先出聲,我再決定好了。

於是,我觀望你,你觀望他,最終的結果就是: 沒有人

另一種情況是,有個人跳出來說:

明天我會到。
即使寒流再冷、雨再大,我一樣會到。
即使只有我一個人,我還是會到。
我會待滿練球時間,只要你有來,就一定會看到我。

而在那一刻,原本大家互相觀望的氛圍就開始變化了。

有些幸運的時候,能遇到有的人會跳出來跟著說 "我也會到" ,之後有其他人跟著說 "他也會到"、"誰跟誰也會到"。也有些人時間到了就自行惦惦出現在那裡。

於是,原本的 沒有人 變成了 一群人

但也有很大的機會,沒有人相信你或沒有人想跟,而你成了那唯一的 一個人 。 這是必經之路。因為這就是從 沒有人一群人 的代價。

發佈的內容將搬離 Facebook — 2022-10-31

發佈的內容將搬離 Facebook

近期決定把 Facebook 上自己發佈的內容,搬離 Facebook,並減少在 Facebook 上活動的方式 。

具體的細節大略是:

  • 在我個人空間,由我自己發佈的內容,會搬到 Blog 上。
  • 於發佈內容下的留言跟按讚等資料,會盡可能依原樣搬遷。(留言會顯示,但按讚資料不顯示)
  • 搬完後的內容,會自 Facebook 上刪除。
  • 自己儲存的書籤、訂閱清單,會慢慢備份至其他地方後清除
  • 若未來有發文之需,會以先在 Blog 發表後,再轉載至 Facebook 的方式。

此計劃預計於通知發佈一週後逐步進行。

暫不更動的部份為:

  • 別人 Tag 我的照片,或別人 Tag 我的貼文,則仍保留不動。(多數於 2015 年以前的不會動到)
  • 我上傳到其他群組、朋友的照片、留言、…等資料仍保留不動。

主要原因如下:

  • Facebook 容易漏失朋友的資訊。
  • 多數的動態,來自於少數的活躍人士。其他人往往被廣告和 KOL (註: Key Opinion Leader, 意見領袖)給插隊跟淹沒了。
    • 大多數的時間是消耗在從沒見過面的人,以及反覆跳過都跳過不完的廣告的身上。
  • Facebook 已反賓為主的干擾並介入並我對資訊的挑選跟判斷。
  • 演算法之下,人際關係變得更加極端,親者越親、疏者越疏、仇者越仇。

基於上述種種因素, Facebook 早已不再是個良性的社群網站。 然而 Facebook 裡仍留有過往許多回憶跟照片,也有很多朋友仍透過 Facebook 聯繫。 因此,此次非完全離開,仍會維持使用作為交流互動的管道之一。

若想了解更多關於我對 Facebook 的觀察與看法,可參考我的網頁如下:

https://6bcf7279.info/2022/10/31/7e88ea6c/ (內容如下)

日常的訊息來自於活躍的少數

約莫 2018 年前後,我發現 Facebook 有一個角落功能,知道的人不多。 (註: 此功能已經被 Facebook 拿掉了,現在已經找不到了。) 具體進入該功能頁的路徑已經忘記了,但瀏覽器裡還紀錄著的書籤如下:

https://www.facebook.com/〈Username〉/friends?ft_ref=flsa

其中 〈Username〉 是每個人自訂的使用者名稱,可在 Facebook 的 General Account Settings 查到。 例如: 若你的個人頁面網址是

https://facebook.com/xyz321

那麼你的 〈Username〉 就是 xyz321,而對應的網址就是

https://www.facebook.com/xyz321/friends?ft_ref=flsa

在這功能頁面裡,會列出自己所有的朋友清單。而最重要的是,他還會顯示朋友清單中哪些還有未讀訊息

我原本需要在動態牆滑個半小時、一小時,才勉強獲得部份朋友的動態。 有這個功能之後,每次只需要 5 ~ 10 分鐘,就能大致跟完最近一兩天的消息。

在切換到這樣的模式之後,使用了一陣子,觀察到了一些現象如下:

  • 若有好的工具的幫助,追蹤朋友動態的真正時間不會太多。
  • 一週之內,約有 4~5 % 的人有新發文(約20幾位)。絕大多數人都很少發文。
  • 而這 4 ~ 5 % 的人之中,約有 2~3 成的人(約 4~6 位),是密集發文者。
    • 每天都有發文,也可能幾小時就貼一句心得、照片、或是轉貼文章。
    • 換算一下,約佔總比例 0.8 ~ 1.5 %

(註: 當時的朋友清單約 500 人上下)

當時對照 Facebook 預設的動態牆的訊息後,感覺動態牆的成份佔比如下(註: 主觀感受,非嚴格統計)

  • 密集發文者約佔 45 ~ 55 % (約滑 1 ~ 2 下看到一則)
  • Facebook 推播轉貼(朋友按讚或留言、熱門、背後有贊助) 約佔 25 ~ 35 % (約滑 2 ~ 4 下看到一則)
  • 廣告約佔 10~15 % (約滑 4 ~ 6 下看到一則)
  • 常發文的朋友 15 % (約滑 5 ~ 7 下看到一則)
  • 較少發文的朋友 5 % (約滑 15 ~ 20 下看到一則)

在得出實際觀察的結果後,我深刻了解到幾個事實:

  • 我們絕大多數在 Facebook 所獲得的資訊跟感受,很大程度是由少數人所左右。
  • 假設朋友清單的數量在 250 人上下,那麼只要有 2~4 位的朋友是密集發文者,且密集發文或轉貼同一主題的文章時。(例如: 電影、時事、體育新聞、…)。相關的主題就可能佔據動態訊息中的 3 成以上。
  • 當你在 Facebook 上看到
    • 你有幾個朋友在活躍的提及一個主題
    • 其他人也再討論這一個主題,例如: KOL、新聞轉貼、…(背後由 Facebook 演算法推播給你)
    • 上述加乘下來,可能佔比達 30 ~ 50 %。 (約滑 1 ~ 2 下就看到一則)
    • 此時,就會產生一種,好像身邊所有人都在關注這件事 的錯覺
    • 但跟實際的情況往往有一定的落差
  • 約 95 % 不常發文的,是沉默的大多數。在 Facebook 動態牆的佔比很弱勢。
    • 假設有 2 位密集發文者跟 5 位較少發文者,分別在一週內對同一主題表示意見。
    • 結果通常是,2位密集發文者的訊息,你當天就會注意到。而另5位較少發文者,你可能過一兩天,才看到其中一兩則。另有三位,你可能會在很久以後,偶然之下才發現原來他有發過文。

在這樣的環境下,沉默的多數感到不受重視,而更少發文。而密集發文的少數,感受到更多關注與互動,而更加的活躍的發文。

之前透過只列未讀訊息的功能,大致能彌補這些狀況。 可惜好景不常,這功能在我發現並使用不到 1 年後(約 2019 年前後),就被 Facebook 拿掉了…。

社群訊息的傳遞消聲

早期在社群,發佈活動訊息時,大多數人都能在短時間內收到通知訊息 。 但自從商業廣告,以及政治活動大舉進入 Facebook 後,也開始有了更多管制曝光的機制。 因此,即使有 2000 人的社群,發佈活動訊息,往往也只有 60 ~ 120 人次的曝光。

聽說付點錢去推播訊息,情況會改善很多。 但若連發個社群公告訊息,也需要用錢來換取曝光率的話,顯然己背離我們的初衷。

這感受在 Facebook 的後期越是明顯,群組的後台功能改版的幾次。 後面改版的方向,越來越偏向廣告經營的方式。 例如:

  • 有個分頁就叫 "成長(Growth)",提供你群組成長的數據
  • 顯示成員的統計訊息,像是 "男姓/女姓", "國家","地區", "年齡分佈"…
  • 顯示按讚數量跟互動行為的數據…

這些功能對於想要投放廣告的組織確實很有幫助。 但對於我們這些,以共同興趣為交集,不以營利為目的而自我組織的社群來說。 這些作為,都離耕耘社群的初衷越來越遠,長久下來讓社群經營越來越力不從心而漸漸走向枯萎。

寫照比喻:

長期以來,我們都自主提著水,去灌溉一顆樹。
不求什麼果實回報,只要樹長得茂盛有活力,看著開心就好。
但不知從何時開始,有管理員開始限制你,
你一公升的水,只能給你澆 100 CC。
有人按讚,就讓你多澆 30 CC。
想要倒完全部的水,得加錢。
管理員接著說,我們現在還提供統計數據後台
能告訊你,現在樹有幾公分高,有多少葉子,跟葉子的各項統計資訊。
並幫你分析樹的成長速度跟含金量,方便你計算灌溉的投資回報率如何如何。

聽完這一切,心中感嘆、百般無奈,只想回覆說,
你說的那些功能我們通通不想要,
能不能還原到那個能單純灌溉跟澆水的環境就好?!

動態牆會丟包朋友的訊息

假設,我一天有 200 則朋友的發文訊息,而 Facebook 另外想推播 200 則訊息的話。 我原本以為,我努力滑完 400 ( = 200 + 200 ) 則訊息,或更多訊息之後,Facebook 的動態牆最後都還是會上完所有 200 則朋友的發文訊息。

然而,實際狀況並非如此。不管是用預設的 "熱門貼文(Top posts)" 還是用 "最新(Most Recent)" 的排序方式,我一直滑,發現即使滑了十倍的量約 2000 則,200 則的朋友訊息中,還是只出現部份朋友的訊息。估計至少還有 3 成或更多的訊息沒顯示出來。甚至滑到後半段, Facebook 都開始重複顯示已經出現過的廣告時,就是不會顯示還沒讀過的朋友貼文。(註: 我有一一點入朋友清單作會對照,確認不是單獨個案,無法用被封鎖或反追蹤來解釋)。

這個現象說明,在 Facebook 動態牆上沒看到部份朋友的消息,並不是滑的訊息數不夠多,而是部份朋友的消息根本就沒有被排進去。 Facebook 不只是插入它的廣告而已,它還決定了讓你知道誰的消息,以及決定了讓你不知道誰的消息。

簡單來說,

  • Facebook 對於要推播給你的資訊跟廣告,一定是排好排滿。
  • 你所有朋友的訊息,Facebook 只挑部份的顯示,剩下的就直接丟包了。
  • 若你不想漏朋友的消息,你必須要自己一個一個點進去才會發現。(花太多時間了,幾乎不可能)
  • Facebook 會無形之中代替你決定,你看到誰的消息,你看不到誰的消息。

這個問題,證明 Facebook 從根本上已不再是一個可信任的社群網站平台。

Facebook 上的互動變得越來越極端

不確定從何時開始。感覺 Facebook 上的留言跟討論,有越來越激化的傾向。 之前還只侷限於時事、政治相關議題的討論下才有。但現在各類話題,都開始有這樣的狀況。

印象中,早期在使用時,良性互動比較多。當時留言互動的,大多有共同好友交集,即使意見不合,也會互留個情面。 但現在遇到越來越多案例,是在發文或留言不久後,就遇到不認識的人,開頭就來個劈個幾句,甚至有人身攻擊的味道,好似跟對方有仇似的。但明明就是個素未謀面的人。這現象,不只自己遇到,不時也看到其他朋友的貼文也有如此遭遇。

次數頻率越來越高之後,我開始認為此現象的背後可能不單純。在此,作了一次思想實驗如下:

假設一個人發文或留言後,而社群網站會透過一個機制演算法,產生一個通知清單來通知相關的人。在此先稱之為頭香通知名單。而這份名單,猜測可能會是由下列來源所組成:

  • 發文者的朋友清單
  • 留言者的朋友清單
  • 文章主題的訂閱者
  • 文章所屬的粉絲頁成員清單
  • …等等

這名單加總一下,數量多半有上千人。 而社群網站的演算法,將決定誰會先看到,並有更早的機會留言。 假設有兩種通知策略如下:

  • (A) 優先通知該發文者或留言者之朋友清單中的友好清單
  • (B) 優先通知最能刺激更多留言跟討論的活躍清單

若處於 (A) 的通知策略下,那發文者跟留言者,不難預期會感受到更多友好的良性互動。

但若是處於 (B) 的通知策略下呢?

一個 能刺激更多留言跟討論的清單 或許可能也包含有:

  • 以戳人為樂的戳樂(Troller)
  • 喜歡煽風點火,以引戰為樂的人 (註: 因為敵意螺旋而招致雙方更多回文辯論)
  • 心直口快,看到標題就先嘴個一兩句再說 (回應快速)
  • 常駐在網路上的專業小編(對指定議題回應極快,且有辯必回)
  • KOL (後面會有一堆人跟著回文附和)
  • … 以及任何系統演算法判別有 "會快速回文" 且 "帶出更多回文" 的活躍用戶

因此,在 (B) 的通知策略下,不難預期,會更容易遇到 "回應快速但來者不善" 的負面互動。

藉由上述的思考,可以推測出社群網站的演算法差異,很可能產生兩種完全不同的環境風氣。而這環境風氣,並不一定來自於社會的人性變化。個性溫和的社會,在演算法煽風點火下,可能會變得極端互戰;個性飆悍的社會,在演算法的安撫下,也可能變得友善熱情。

也就是說,我們目前遇到的問題,很大的可能是由社群網站的演算法所引起的。而如果問題是由演算法所引起,自然也無法靠個人調整來修正。

舉例來說,在發文遇到不友善的回應時,比較溫和內向的人常傾向先自我檢討跟退讓,以免引發更多爭端。然而,在 頭香通知名單 上千人的名單中,總是會有最刁鑽、最能雞蛋裡找骨頭的人。而當社群網站的演算法,目標設定是優先把這些最極端的人往該文章推送曝光時,問題總是能一再發生。畢竟欲加之罪、何患無辭。那麼這是發文者的問題呢? 還是社群網站的演算法的問題呢?

我認為,社群網站的演算法的問題更大一些。

然而,社群演算法掌握在 Facebook 手中,視為商業機密,從未公開揭露,也未有公示資訊揭露,真相無從得知。 現存許多大型社群網站也有相同的問題,例如: Twitter。

目前尚沒有任何可信任的獨立機構、機制,可檢視並制衡這些社群網站的演算法問題。

結論

Facebook 仍是一個強大的網路服務,也提供許多便利的功能。 但對於在意資訊收集自主性的人來說,建議降低對 Facebook 的依賴,並保持一定的距離。

備註

在 Google Chrome 瀏覽器下,可按 F12 並在 Console 分頁的輸入列,輸入下述的 javascript script:

function to_scroll()
{
	console.log('Scroll to the bottom...');
	window.scrollTo(0, document.body.scrollHeight);
	setTimeout(to_scroll, 7*1000);
}

to_scroll();

瀏覽器會自動定時隔 7 秒滑至頁面的底端,效果相當於每 7 秒自動按一次 〈End〉 鍵。

不過現在這個方法可能要修改,因為 Facebook 後來有修改,在往下滑時,會將先前的內容刪除掉。