2021年7月20日 星期二

Node.js 筆記- 使用 Puppeteer 進行 JS Injection 與 Custom function 定義實作 @ Puppeteer v10, Node.js v16.5.0

之前使用 Puppeteer 方便自己撰寫一些監控 http requests ,像是即時監控 remote resource 變化。接著,要來把玩 JS Injection。

如果使用 Puppeteer 的用法是包括持續瀏覽網頁的話,那適合在網頁 onload 的情況下植入:

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-domcontentloaded
// https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
//
// The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded, which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.
//
page.on('load', async () => {
console.log('on.load');
});

如果是瀏覽網頁後,立馬植入 JS Code 運行並且任務就結束了,那就是適合在 network 連線閒置後處理:

await page.goto(target_url, {
waitUntil: 'networkidle0',
});
console.log('networkidle0');

而 JS Injection 的方式:

await page.evaluate((js_code) => {return Promise.resolve(window.eval(js_code));}, js_resource_content);

其中 js_resource_content 就是預計植入的 JS Code 字串。例如有多份程式碼要植入,可以跑回圈:

for(let i=0 ; i<js_resource.length ; ++i) {
console.log('inject: '+js_resource[i]+', size: '+js_resource_content[i].length);
await page.evaluate((js_code) => {return Promise.resolve(window.eval(js_code));}, js_resource_content[i]);
}

其中 js_resource[] 紀錄的是 js code 網址,而 js_resource_content[] 是紀錄該 js code 網址的內容。

最後,提一下自訂函數的寫法:

// https://github.com/puppeteer/puppeteer/blob/main/examples/custom-event.js
// Define a window._puppeteer_helper function on the page.
await page.exposeFunction('_puppeteer_helper', (data) => {
console.log(`_puppeteer_helper fired`);
});

如此,又可以靠 JS Injection 去呼叫 window._puppeteer_helper ,或是靠 phage.evaluate 執行了:

let result = await page.evaluate((data_to_page) => {
let data = data_to_page;
return Promise.resolve(window.eval(`
window._puppeteer_helper();
`));
}, 'nothing');

2021年7月14日 星期三

XBOX 遊戲目錄 6歲以下 幼兒遊戲清單 @ XBox One S

當年買 XBOX ONE S 時,想說花五千左右買來當 藍光DVD播放器 的,結果...不小心就再加碼了停產的 Kinect 感應器、連接線,接著又入坑買了些體感的遊戲,像是首選目標是 Just Dance 以及 Kinect 運動大會:對抗賽 ,事實證明,果真該先查遊戲品價再買 XD Kinect 運動大會 似乎鳥掉不少,玩起來滿常跟感應狀態不順,大概只有玩網球跟保齡球比較順吧。不過 Just Dance 的體驗滿讚的,堪稱完美的體感互動。

近一年半,因為台灣疫情的關係,開始在 XBox.com 較大筆的消費買了不少遊戲,當時以直在想 XBox Store 裡一堆 18禁 血腥遊戲,還真不知該買什麼遊戲給小孩,之後在網路上找了一下,找到不少人推薦樂高的遊戲,入門款:樂高玩電影 LEGO THE MOVIE VIDEOGAME,趁著 XBox Store 特價時候,入手小試身手。以下的遊戲就偏向把手遊戲,對於 Kinect 體感就停留在 Just Dance 才會打開來用了。

先提使用 XBox Store 就是買數位版遊戲心得,好處:
  • 不定期會有大幅度的特價
  • 購買不用等實體光碟片寄到家
  • 不用擔心 XBOX 遊樂器哪天開始挑片
缺點:
  • 遊戲很大時,要等下載很花時間,家裡網速不快時,都可能會等上數小時才能下載完畢(20GB 遊戲大小,每秒 10MB/s 下載,要花 35分鐘)
  • 遊戲不玩時,不像實體遊戲片可以在二手市場交易,之前買的 Just Dance 2018 是實體光碟片,想玩買新的一代時,就把舊的在二手市場賣了,可以回收一半以上還滿不錯的(買一千多賣六七百)。
接著來聊聊樂高遊戲了!請留意,樂高遊戲,某個角度上還是帶有暴力意識,分級來說都是建議12歲以上的小孩的,例如要打掉建築物、打壞人等等,只是 XBOX 上太少幼兒把玩的遊戲,就試著引導他們當作解題遊戲。若很在意分級設計,還是避開比較安心。

買樂高遊戲還有個重點:可以兩人一起玩的。

小孩第一次接觸樂高遊戲,在看不懂文字上果真難上手,需要大人陪著,但大概撐過去後,便開始感受到遊戲有趣的點:解題、尋找成就感。雖然他們根本連劇情都不懂,也不懂樂高音樂以及出場的角色們,但小孩很會腦補,就像玩扮家家酒那樣。看著小孩這樣兩週玩著這款玩到幾乎破關,覺得滿驚訝的。大概一天玩 2 小時左右。有些場景因為沒有讀劇情,不懂文字上描述解題方向而卡關著,推論應該有八成都可以自己摸索,剩下兩成要去幫忙點一下。

第一次感受到長劇情遊戲帶給小孩的樂趣。回想起以前玩 PC 或 TV 遊戲時,其實很多看不懂也繼續玩,像是國小玩超任的熱血硬派也看不懂日文,一樣暑假天天玩天天練功了一個多月。(現在靠模擬器跟電腦加速玩,大概一週內就能破關)

爾後趁 XBox Store 不定期特價(感覺兩三週會有一輪樂高系列遊戲特價),就順便收購老遊戲而不是買最新不特價的遊戲,收藏了:
其中,我自己覺得 The LEGO Movie 2 Videogame 複雜度又比第一代高,就算玩過第一代也不一定能上手,因為複雜度上更重文字的描述,像是要建制對應的東西,選單更加複雜,可能將近要花 50% 時間帶小孩玩,相較之下還是 The LEGO Movie Videogame 適合學齡前小孩把玩,算是致敬第一代才收藏 XD 整體上其實可以不用花錢買它的,他比較偏給青少年玩的遊戲。

依序嘗試了 The LEGO Movie Videogame、The LEGO NINJAGO Movie Video Game、LEGO® CITY Undercover、The LEGO Movie 2 Videogame、LEGO Marvel Super Heroes、LEGO® The Hobbit™,感覺 NINJAGO 跟 LEGO Marvel Super Heroes還滿快上手的,其餘有的是劇情看不懂稍微複雜就卡關了。由於樂高很經典,再加上上述遊戲都稱得上老遊戲了,在網路上還滿多攻略,甚至 bilibili 還有玩家闖關的影片紀錄,若擔心不會玩,可以先查一下

此外,在 NETFLIX 上剛好有很多樂高小電影,也可以搭配播來看看,像是 NETFLIX - 樂高玩電影 2

其他推薦遊戲:
扣掉樂高後,迪士尼大冒險跟 Woodle Tree Adventures 都是很超值的遊戲!有興趣可以嘗試迪士尼大冒險,裡頭的遊戲可以玩上好一陣子。以上的價錢都是台幣。

有興趣的可以一週逛個 XBox Store 優惠區一下 https://www.xbox.com/zh-tw/games/all-games?cat=onsale ,看看有沒有想收藏的遊戲。只是,現在 Switch 當道,不需刻意為了玩這些遊戲買 XBox ,親子遊戲還是 Switch 比較多。

2021年7月7日 星期三

MacBook Pro 13吋 換喇叭揚聲器 Speaker @ A1502, Retina, 13-inch, Early 2015,

MacBook 13 A1502 Speaker

Macbook 已經用了五年多了,大概年初開始發現喇叭破音,有看到很多坊間解法,像是拆掉喇叭後,試圖找到破點,甚至塗上一點薄薄的白膠來修復,但我第一次看拆喇叭的影片,就懶了 XD

接著原本單邊正常,撐了兩三個月終於一起壞光光了... 於是乎進入藍牙小喇叭的用法,結果因為疫情關係要看個直播又要開啟藍芽喇叭,久而久之,還是覺得不方便。且不知道是不是外接喇叭久了,換了揚聲器後,反而覺得內建音效很差...耳朵被慣壞了!

就這樣...隨意找了一下,用 A1502 (自己的 MacBook 型號) 加 喇叭 或 揚聲器 這關鍵字,就可以輕鬆找到哪邊有在賣,一對大概都賣台幣 1100 以上。再加上本身就已經敗過 米家精修螺絲起子套裝組 ,這時需要的工具都備齊了。

最後就拆一下筆電,斷一下電源,在輕輕的把螺絲卸下,並且照拆除的順序再裝回去即可。不是太大的問題。

由於 Youtube 上面已經有許多教學影片,非常建議先看過一次,留意拆法,像是喇叭要拆離主機板時該怎樣做。

MacBook 13 A1502 Speaker

MacBook 13 A1502 Speaker

[Linux] 修復 Ubuntu 12.04 無法安裝軟體 - W: Failed to fetch 404 @ Ubuntu 12.04

老機器環境:

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 12.04.5 LTS
Release: 12.04
Codename: precise

主要是在 server 運行 apt-get update 時,會出現一堆 404 問題:

W: Failed to fetch http://archive.ubuntu.com/ubuntu/dists/precise-backports/multiverse/binary-i386/Packages 404

追了一下應當是 12.04 已經不維護了。運氣不錯,有找到 old-releases.ubuntu.com 位置:
  • https://archive.ubuntu.com/ubuntu/
  • https://old-releases.ubuntu.com/ubuntu/
接著就動手換掉 /etc/apt/sources.lis 即可,包括來源 server 換完後,記得要再把 /var/lib/apt/lists 先移除:

$ sudo mv /var/lib/apt/lists /var/lib/apt/lists-backup
$ sudo mkdir -p /var/lib/apt/lists
$ sudo apt-get update

如此就會修復相關資訊,可以在靠 apt-get 安裝軟體了。

此外,若剛好在除錯時,刪除了已安裝過的軟體,接著做了上述後,又要安裝時,會碰到資訊對不上的問題,如:

dpkg:error processing package XXX (--configure):
subprocess installed post-installation script returned error exit status 10

解法就是清除 /var/lib/dpkg/info/ 下相關套件的資料後,重新安裝或是跑完後續流程:

$ sudo mkdir /var/lib/dpkg/info/backup
$ sudo mv /var/lib/dpkg/info/XXX* /var/lib/dpkg/info/backup/
$ sudo apt-get install -f

2021年7月3日 星期六

[開箱] 電子書閱讀器 Kobo Elipsa 10.3 吋

Kobo 10.2吋 電子書閱讀器

在 Kobo 也花了五千買電子書了 XD 一開始還滿喜歡用 PC app 觀看,想說還是買一台電子書閱讀器來用用吧!之前買過 Kindle 6吋的經驗,加上沒外出看書的需求,還是想大尺寸一點,剛好跟朋友哈拉 Kobo 沒出過大尺寸的,沒想到哈拉後沒幾週就看到推出 10.3吋的閱讀器,當時很快就下單,沒有想太多。

Kobo 10.2吋 電子書閱讀器

把玩的心得:
  • 裸機不含保護殼還滿輕的: 384g
  • 加上保護殼跟筆: 744g
Kobo 10.2吋 電子書閱讀器

大概 2010年 前研究電子書領域時,就曾跟某書店高層開會過,分享過心得,認為一瓶水 600g 是可以接受的範圍(替換掉女生包包內的東西),因此很期待 iPad 問世,我覺得加保護殼真的會覺得小重 QQ 但一台破萬也讓人不得不保護啊 XD 或許這就是 amazon kindle 6吋主流的原因吧?最終還是方便為主。

Kobo 10.2吋 電子書閱讀器

回過頭來,我自己使用 Kobo Elipsa 10.3 吋的心得,小缺點:
  • 首次登入 kobo 時,會輸入帳密,輸入後發現螢幕上會有鍵盤的殘影(但不影響使用)
  • 把 Kindle Paperwhite 6 翻頁速度快些,但跟 iPad 體驗上還是有一段落差
  • 觸控筆採用 AAAA 6號電池,稍微不好買,網路訂最方便(2個50元)
  • 據說 Kobo 一直以來都有各個裝置同步不即時的問題,甚至已經接近無法同步的體驗
優點:
  • 比 iPad 輕,只有讀書跟寫筆記功能,專心讀書
  • 不加保護殼時,質感不錯(可惜實務上應當還是會裝著保護殼)
  • 搭配 Kobo 觸控筆,比我想像中驚艷不少,像是梳理閱讀時可以隨便畫,很讚(看起來 iPad app 看不到?)
Kobo 10.2吋 電子書閱讀器

其他意外...則是把電子書擺在桌上,小孩拿起 kobo 筆畫一畫,就發生慘案了 <囧> 只能說這就是意外吧...目前看起來還不影響閱讀,讓他去吧...還是把電子書當作耗材,書中的資訊才是核心啊。

Kobo 電子書 刮傷

2021年6月27日 星期日

Node.js 筆記 -以 HTTP API 提供 ffmpeg 轉檔,以 jpg 轉成 png 為例 @ node.js v10.24.1, macOS 11.4

最近接獲一個任務研究了一下 Javascript 與 H264 的解決方案,首先會立馬想到 ffmpeg 方案,就研究了以下 JS 專案:
其中 ffmpeg.wasm 無法在 node.js v10 運行。在不考慮 node.js 升級的前提上,就來把玩其他三者,而 tinyh264 的源頭是 h264bsd 專案,在 h264bsd 專案中有 js solution 可以挖出來跑,包括裡頭有 github.com/oneam/h264bsd/blob/master/js/test_node.js 這隻程式很小巧美麗,可以欣賞一下。而裡頭把玩最多的是 ffmpeg.js 這專案,有興趣可以看看他的 Makefile ,很簡單清楚,比較特別是他分別產出 ffmpeg-webm.js 和 ffmpeg-mp4.js ,兩者不同的地方是 ffmpeg 支援的 encoder/muxer 不同,可能是為了精簡 js code size 因此就拆成 webm 跟 mp4 的用法。這邊把玩的是善用 ffmpeg.js Makefile 去重編 ffmpeg-*.js 增加 demuxer/decoder 和 encoder/muxer。

這邊滿適合看一下 ffmpeg 框架:


接著,工作上的任務是讓 input 支援 h264,而 output 支援 jpg/png 等。這邊把範圍限縮到 input 支援 jpg ,輸出支援 png 來筆記。最後包裝個 HTTP API 出來筆記一下,結果花最多時間是在確認如何從檔案系統讀出檔案資料,以及透過 http.request 將圖片資料下載回來 Orz 還派上 md5sum 來檔案內容是否正常。

而 ffmpeg.js 真的設計得不錯,用起來很方便,可以參考 local-file-cmd.js 用法:

const ffmpeg = require('./ffmpeg-20210624_1550/ffmpeg-mp4.js');
//const ffmpeg = require('./ffmpeg-default/ffmpeg-mp4.js');
const fs = require('fs');
const crypto = require('crypto');

let from_local_file = '/tmp/test.jpg';
let raw_data = fs.readFileSync(from_local_file);
let testData = Uint8Array.from(raw_data);
const md5sum = crypto.createHash('md5');
md5sum.update(raw_data);
console.log('[INFO][from_local_file][size:'+raw_data.length+'][md5:'+md5sum.digest('hex')+'][path:'+from_local_file+']');

const result = ffmpeg({
MEMFS: [{name: 'test.jpg', data: testData}],
arguments: ['-i', 'test.jpg', 'test.png']
})
const out = result.MEMFS[0];
const outputContent = Buffer.from(out.data);

console.log('[INFO] Done. Input size: ' + raw_data.length +', testData size: '+testData.length+', output size: '+outputContent.length);

更多資訊:changyy/study-wasm-ffmpeg-jpg-to-png

2021年6月12日 星期六

Node.js 筆記 - 使用 Puppeteer 監控瀏覽網頁的 Requests 跟 HTML Code @ macOS 11.4, node v14.14.0, npm 6.14.5

緣起於在追蹤網站架構時,每次都靠 chrome browser 開發人員工具去追蹤特定的 request 還好,若需要大量瀏覽找尋一些蛛絲馬跡時,就會很煩!以前很閒時,就會跳下去寫 chrome extension 或是熱衷於 C++ 時,就會寫一點點  Chromium Embedded Framework 來寫個小型瀏覽器,最後想起來試試看 Puppeteer 吧!

使用 Puppeteer 很輕鬆,除了是很常見的 Javascript 語言外,Puppeteer 套件提供了網頁端常見的事件偵測、所發的 Request 清單。就這樣拼拼湊湊即可完工:

(async () => {
const browser = await puppeteer.launch({headless: false});
//const page = await browser.newPage();
const context = await browser.createIncognitoBrowserContext();
const page = await context.newPage();
await page.emulate(device);
await page.setRequestInterception(true);

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-domcontentloaded
// https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event
//
// The DOMContentLoaded event fires when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.
//
page.on('domcontentloaded', () => {
console.log('on.domcontentloaded');
});

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-domcontentloaded
// https://developer.mozilla.org/en-US/docs/Web/API/Window/load_event
//
// The load event is fired when the whole page has loaded, including all dependent resources such as stylesheets and images. This is in contrast to DOMContentLoaded, which is fired as soon as the page DOM has been loaded, without waiting for resources to finish loading.
//
page.on('load', async () => {
console.log('on.load');
watchTags(page, watch_tags);
});

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-framenavigated
// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-class-frame
page.on('framenavigated', frame => {
console.log('on.framenavigated: '+frame.url());
});

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-request
// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-class-httprequest
page.on('request', request => {
watchRequest(page.url(), request.url());
if (skip_resource_type[ request.resourceType() ])
request.abort();
else
request.continue();
});

// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-event-response
// https://pptr.dev/#?product=Puppeteer&version=v10.0.0&show=api-class-httpresponse
page.on('response', response => {
//console.log('on.response: '+response.url());
});

while (true) {
if (url_init.length > 0) {
const target_url = url_init.shift();
await page.goto(target_url, {
waitUntil: 'networkidle0',
});
console.log('networkidle0');
}
if (url_init.length > 0)
await sleep(5000);
else
await sleep(50);
}
await browser.close();
})();

如此,搭配 command line 使用,就可很快的監控想要的 request 規則,或是 HTML DOM Tree 的變化,例如監控 <script> 的讀取位置在哪:

% node index.js -url "https://tw.yahoo.com" -tag "script.src"
...
on.load
[WATCH][TAG] script: src
Web URL: [https://tw.yahoo.com/]
[
  { src: 'https://s.yimg.com/wi/ytc.js' },
  {
    src: 'https://tw.mobi.yahoo.com/polyfill.min.js?features=array.isarray%2Carray.prototype.every%2Carray.prototype.foreach%2Carray.prototype.indexof%2Carray.prototype.map%2Cdate.now%2Cfunction.prototype.bind%2Cobject.keys%2Cstring.prototype.trim%2Cobject.defineproperty%2Cobject.defineproperties%2Cobject.create%2Cobject.freeze%2Carray.prototype.filter%2Carray.prototype.reduce%2Cobject.assign%2Cpromise%2Crequestanimationframe%2Carray.prototype.some%2Cobject.getownpropertynames%2Clocale-data-en-us%2Cintl%2Clocale-data-zh-hant-tw&version=2.1.23'
  },
  { src: 'https://s.yimg.com/aaq/yc/2.9.0/zh.js' },
  {
    src: 'https://s.yimg.com/ud/fp/js/vendor.174522d6d76b51858b93.min.js'
  },
  { src: 'https://s.yimg.com/ud/fp/js/common.js' },
  { src: 'https://s.yimg.com/oa/consent.js' },
  { src: 'https://consent.cmp.oath.com/cmpStub.min.js' },
  { src: 'https://consent.cmp.oath.com/cmp.js' },
  { src: 'https://s.yimg.com/ss/rapid3.js' },
  { src: 'https://s.yimg.com/rq/darla/boot.js' },
  {
    src: 'https://s.yimg.com/ud/fp/js/main.6622c6aeda051386afaa.min.js'
  },
  { src: 'https://mbp.yimg.com/sy/os/yaft/yaft-0.3.22.min.js' },
  {
    src: 'https://mbp.yimg.com/sy/os/yaft/yaft-plugin-aftnoad-0.1.3.min.js'
  },
  { src: 'https://s.yimg.com/aaq/vzm/perf-vitals_1.0.0.js' },
  {
    src: 'https://fc.yahoo.com/sdarla/php/client.php?dm=1&lang=zh-Hant-TW'
  },
  {
    src: 'https://s.yimg.com/aaq/c/e7fecc0.caas-abu_highlander.min.js'
  }
]
...

2021年6月4日 星期五

[Linux] 升級 Red Hat Enterprise Server 的 OpenSSL, OpenSSH 服務 @ Red Hat Enterprise Linux Server release 5.3

這是一台滿舊的機器,大概 10 年前吧。同事想要 git clone 時,出現了失敗問題,追蹤一下是機器的 openssl 過舊,由於機器有他的任務在,不好意思亂更新系統資源,怕爆!所以採用 tarball 的安裝方式。

openssl 太舊時,連用 wget/curl 去下載 https 來源時都會失敗的,只好靠 http 或是 scp 搬東西進去。

$ lsb_release -a
Distributor ID: RedHatEnterpriseServer
Description: Red Hat Enterprise Linux Server release 5.3 (Tikanga)
Release: 5.3
Codename: Tikanga

$ openssl version
OpenSSL 0.9.8e-fips-rhel5 01 Jul 2008

只好挑個 /opt/tarball 安裝啦,沒想到要編譯 openssl 又會碰到 perl 版本不夠新:

$ wget http://www.cpan.org/src/5.0/perl-5.10.0.tar.gz
$ cd ~/perl-5.10.0
$ ./configure.gnu --prefix=/opt/tarball
$ make -j4 install

接著再安裝個 zlib ,採用 static 方式方便後續大家使用:

$ wget http://zlib.net/zlib-1.2.11.tar.gz
$ cd ~/zlib-1.2.11
$ ./configure --prefix=/opt/tarball --static
$ make install

編譯 openssl:

$ wget http://www.openssl.org/source/openssl-1.1.1k.tar.gz 
$ cd ~/openssl-OpenSSL_1_1_1k 
$ PATH=/opt/tarball/bin:$PATH ./config --prefix=/opt/tarball
$ make -j4 install

編譯 openssh 工具包:https://github.com/openssh/openssh-portable/releases

$ cd ~/openssh-portable-V_8_6_P1
$ autoreconf
$ LD_LIBRARY_PATH=/opt/tarball/lib:$LD_LIBRARY_PATH ./configure --prefix=/opt/tarball/ --with-ssl-dir=/opt/tarball
$ LD_LIBRARY_PATH=/opt/tarball/lib:$LD_LIBRARY_PATH make -j4 install
$ file /opt/tarball/bin/ssh
/opt/tarball/bin/ssh: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), for GNU/Linux 2.6.9, stripped

接著偷懶拉 code 方式:

$ GIT_SSH_COMMAND="LD_LIBRARY_PATH=/opt/tarball/lib:$LD_LIBRARY_PATH /opt/tarball/bin/ssh" git clone ...

或是在環境變數添加好:

LD_LIBRARY_PATH=/opt/tarball/lib:$LD_LIBRARY_PATH 
PATH=/opt/tarball/bin:$PATH

搞定!

2021年5月9日 星期日

[韓劇] 認識的妻子

前幾天想說好久沒用愛奇藝,上去逛了一下,想說看一下「消失的情人節」,結果幾分鐘被朋友提醒可以去 NETFLIX 看高畫質 XD 就這樣又遠離了愛奇藝。倒是在 NETFLIX 看完後,又被推薦服務推坑「認識的妻子」,主因是去年底看過韓劇「Start-Up」,內裡的另一位反派恰好也在這齣戲裡演出。

這是一部 2018 年初的韓劇。這故事跟那時很夯的穿越劇有點關係,但真正吸引到我的,卻是第一集描述雙薪家庭養小孩的困境,真的養了小孩更有感的,感受到劇情安排的那種生活壓力。

不知是不是年紀過 35 的關係,對於大叔熟女演的戲都特別有興趣看完 XD 就這樣花個一天追了 12 集,完食!隨後逛了一下男女主角的 IG ,才發現現實的靜態的照片反而吸引不了我,看來還是劇情關係,讓人喜歡笑起來古靈精怪的女主角,且我不太愛學生時代的裝扮,反而是 OL 時期的精明搭配特有的笑容令人印象深刻。

此時會觀看這齣戲也滿有緣分的,像是幾天前聽股癌 Podcast 回憶起在軍中看了 about time ,讓我回想起對時間的印象,像是電影裡提到小孩出生後,就不適合回到過去,避免改變到未來的小孩狀態。接著恰逢表弟的第一個小孩來到世上,為了體貼老婆,包括月子中心住滿30天、放棄親餵等規劃,著實已把生活的目標跟品質都規劃好也定位好,以及另一位年收稅率達 20% 級距的網路名人在哀哀叫初乳擠得多痛苦和 20% 後就沒了眾多社會福利,政府這稅務規劃到底是助生還阻生 XD

總總點滴,都讓人好好回憶著家庭、小孩、婆媳、雙薪、工作的天秤究竟要怎樣平衡著,就這樣一口氣就看完了這戲!

2021年4月22日 星期四

[PHP] 下載 Maxmind GeoIP Legacy Databases 和 ngx_http_geoip_module 相關處理 @ macOS 11.2, PHP 7.3.24

由於 Maxmind 在推 GeoIP2 ,不開放 .dat 的 GeoIP DB 了,以前下載位置:

  • http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
  • http://geolite.maxmind.com/download/geoip/database/GeoLiteCity.dat.gz
現在官方都說要改用 GeoLite2 DB ,其格式:
  • GeoIP2 Binary (.mmdb)
  • GeoIP2 CSV 
目前先偷懶不處理 nginx ,因為 nginx 採用 GeoIP Legacy Databases。

有找到一個網站 https://www.miyuru.lk/geoiplegacy,從中下載:
連續動作:

% php -v
WARNING: PHP is not recommended
PHP is included in macOS for compatibility with legacy software.
Future versions of macOS will not include PHP.
PHP 7.3.24-(to be removed in future macOS) (cli) (built: Dec 21 2020 21:33:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.24, Copyright (c) 1998-2018 Zend Technologies

% php composer.phar require geoip/geoip:~1.16

% wget https://dl.miyuru.lk/geoip/maxmind/city/maxmind4.dat.gz
% gunzip -d maxmind4.dat.gz

% cat test-ip-via-geoip.php
<?php
require 'vendor/autoload.php';
$gi = geoip_open("maxmind4.dat",GEOIP_STANDARD);
$country = geoip_country_code_by_addr($gi, $argv[1]);
echo "lookup [".$argv[1]."], result: [$country]\n";

% php test-ip-via-geoip.php 8.8.8.8
lookup [8.8.8.8], result: [US]

% nslookup tw.yahoo.com
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
tw.yahoo.com canonical name = atsv2-fp-shed.wg1.b.yahoo.com.
Name: atsv2-fp-shed.wg1.b.yahoo.com
Address: 180.222.102.201
Name: atsv2-fp-shed.wg1.b.yahoo.com
Address: 180.222.102.202

% php test-ip-via-geoip.php 180.222.102.202
lookup [180.222.102.202], result: [IN]

% curl ipinfo.io/180.222.102.202
{
  "ip": "180.222.102.202",
  "hostname": "media-router-fp74.prod.media.vip.tp2.yahoo.com",
  "city": "Taoyuan City",
  "region": "Taiwan",
  "country": "TW",
  "loc": "24.9937,121.2970",
  "org": "AS24506 YAHOO! TAIWAN",
  "timezone": "Asia/Taipei",
  "readme": "https://ipinfo.io/missingauth"
}

% nslookup facebook.com
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
Name: facebook.com
Address: 31.13.87.36

% curl ipinfo.io/31.13.87.36
{
  "ip": "31.13.87.36",
  "hostname": "edge-star-mini-shv-01-tpe1.facebook.com",
  "city": "Hong Kong",
  "region": "Central and Western",
  "country": "HK",
  "loc": "22.2783,114.1747",
  "org": "AS32934 Facebook, Inc.",
  "timezone": "Asia/Hong_Kong",
  "readme": "https://ipinfo.io/missingauth"
}

% nslookup www.gov.tw
Server: 8.8.8.8
Address: 8.8.8.8#53

Non-authoritative answer:
Name: www.gov.tw
Address: 223.200.155.55

% php test-ip-via-geoip.php 223.200.155.55
lookup [223.200.155.55], result: [TW]

% curl ipinfo.io/223.200.155.55
{
  "ip": "223.200.155.55",
  "hostname": "223-200-155-55.hinet-ip.hinet.net",
  "city": "Hualien City",
  "region": "Taiwan",
  "country": "TW",
  "loc": "23.9769,121.6044",
  "org": "AS4782 Data Communication Business Group",
  "timezone": "Asia/Taipei",
  "readme": "https://ipinfo.io/missingauth"
}

2021年4月9日 星期五

[PHP] 透過 Maxmind GeoIP DB 統計用戶資訊 @ macOS 11.2, PHP 7.3.24

真是超級久沒用 maxmind.com 的 GeoIP DB 了,一時之間還以為沒有免費使用的方式,追了一下是要註冊帳號才能下載,而 maxmind 也有提供自動化每天更新 GeoIP DB 的機制。

在此對一份類似 Access Logs 做分析,將其轉成 CSV 格式來分析,其中 CSV 裡頭有 id 跟 remote_ip 兩個欄位,將 remote_ip 分析完後直接歸類屬於哪個 country_code,程式碼很簡單:

% php -v

WARNING: PHP is not recommended
PHP is included in macOS for compatibility with legacy software.
Future versions of macOS will not include PHP.
PHP 7.3.24-(to be removed in future macOS) (cli) (built: Dec 21 2020 21:33:25) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.3.24, Copyright (c) 1998-2018 Zend Technologies

% cat composer.json
{
  "require": {
    "geoip2/geoip2": "~2.0"
  }
}

% php composer.phar install
No lock file found. Updating dependencies instead of installing from lock file. Use composer update over composer install if you do not have a lock file.
Loading composer repositories with package information
Updating dependencies
Lock file operations: 4 installs, 0 updates, 0 removals
- Locking composer/ca-bundle (1.2.9)
- Locking geoip2/geoip2 (v2.11.0)
- Locking maxmind-db/reader (v1.10.0)
- Locking maxmind/web-service-common (v0.8.1)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 4 installs, 0 updates, 0 removals
- Installing composer/ca-bundle (1.2.9): Extracting archive
- Installing maxmind/web-service-common (v0.8.1): Extracting archive
- Installing maxmind-db/reader (v1.10.0): Extracting archive
- Installing geoip2/geoip2 (v2.11.0): Extracting archive
2 package suggestions were added by new dependencies, use `composer suggest` to see details.

Generating autoload files
1 package you are using is looking for funding.
Use the `composer fund` command to find out more!

% cat job.php
<?php

require 'vendor/autoload.php';

// https://github.com/maxmind/GeoIP2-php
use GeoIp2\Database\Reader;
$reader = new Reader('GeoLite2-City.mmdb');

// https://www.php.net/manual/en/function.fgetcsv.php
$row = 1;
$header_row = NULL;
$header = array();
$country_group = array(); 
$lookup = array();

$tracking_time_cost_per_run = microtime(true);
echo "[".date("Y-m-d H:i:s")."]\tinit\n";
if (($handle = fopen("access_log.csv", "r")) !== FALSE) {
while (($data = fgetcsv($handle, 10240, ",")) !== FALSE) {
if (is_null($header_row)) {
$header_row = $data;
foreach($header_row as $index => $name) {
$header[$name] = $index;
}
continue;
}
$remote_ip = $data[ $header['remote_ip'] ];
$data_id = $data[ $header['id'] ];

try {
$record = $reader->city($remote_ip);
} catch (Exception $e) {
continue;
}
if (!is_null($record) && property_exists($record, 'country') && !is_null($record->country) && isset($record->country->isoCode)) {
if (!isset($country_group[$record->country->isoCode]))
$country_group[$record->country->isoCode] = array();
$hash_key = $remote_ip.'-'.$record->country->isoCode;
if (!isset($lookup[$hash_key])) {
$lookup[$hash_key] = 1;
array_push($country_group[$record->country->isoCode], $data_id);
}
}
++$row;
if ($row % 100000 == 0) {
echo "[".date("Y-m-d H:i:s")."]\t".number_format($row).", time cost: ".(microtime(true) - $tracking_time_cost_per_run)."\n";
$tracking_time_cost_per_run = microtime(true);
file_put_contents('/tmp/lookup-result.json', json_encode($country_group));
}
}
fclose($handle);

echo "[".date("Y-m-d H:i:s")."]\t".number_format($row)."\n";
echo "Total #: $row\n";
print_r($header);
file_put_contents('lookup-result.json', json_encode($country_group));
}

產出:
% time php job.php
[2021-04-09 21:03:51] init
[2021-04-09 21:05:18] 100,000, time cost: 86.867056131363
[2021-04-09 21:06:41] 200,000, time cost: 83.395349025726 
...

% jq '' /tmp/lookup-result.json | head -n10

{
  "TW": [
    "1",
    "68",
    "101",
    "121",
    "147",
    "193",
    "236",
    "259", 
... 

就把一堆 record id 擺在某個 country_code 下面,每 85秒處理完 10萬筆資料。

2021年3月14日 星期日

[動漫] 排球少年!!

 

 來源:WIKI

前幾天晚上想測試一下愛奇藝 CDN 不在台灣後,影響有多大時(實際上愛奇藝沒了台灣這邊的CDN後,速度真的受到不小影響,一直緩衝,幾乎不可用)當下就交叉比對了 NETFLIX ,就挑了 排球少年 這動畫。共有四季,就這樣一週多一點,都用 1.5倍速 完食!

老實說,我並沒有很喜歡這畫風,論運動的話,大家更愛看帥哥眾多的網球少年。會一直看下去的主因是自己國高中也打了六年排球,在班際排球賽上,我也常常負責舉球手這個位置,原因沒有別的,因為排球風氣還不盛行,湊起六人後,隊友大多都是打籃球的,跳得高但基本動作較不熟,就非常適合當主攻手,由相對球齡長的我,角色就是幫忙托球給予攻擊機會。當初國一會選擇排球也滿有趣的,因為國小自己點太多技能在躲避球,幾乎沒在打籃球,一進入國中後對大家一窩蜂衝籃球沒有太大興趣,就挑了排球社。還記得第一天社團活動時,跑去遠一點的撿球,就...故意高吊遠傳兩三個排球場距離,球卻不偏不倚的砸中排球社高中學姊,不認識的學姊還很好心的騙我說眼鏡是自己壞的 XD 當下很蠢以為真的是自己壞,事後想起真是人太好!(後來...好像被詛咒一樣,每當自己把球往高空拋超高時,很容易打中別人的頭 Orz 高中時期又一次打到自己的高中好友)

記得自己國高中垂直跳60cm左右,周邊一票人都 80cm 或是身高 175cm 以上,最扯的是有一位 170cm 卻可以垂直跳 94cm 的學長,所以,對於排球少年的故事很有共鳴的!舉凡跳發、飄浮球都打過,我自己應該是主要用飄浮球發球的。甚至大一體育課也可以靠發球吃香得個幾分 XD

這動畫對我來說,滿多球員內心戲,例如為何打排球、為何不想努力衝刺一番、為何不想認輸、為何想偷懶等等的,滿好玩的,有些心境跟當年自己玩排球有像,甚至上了大學後,短暫加入系隊幾個月有感。像是看到別人身體條件很優,就開始想偷懶,自己找理由畫地自限。

真的想起了很多青春歲月,雖然沒有像故事裡不斷去追逐競賽,但打排球的時間,卻填滿了我國高中至少三四年眾多的下課活動,幾乎每天下課等車前,都會玩個30分以上。可惜上大學後,心態越來越疲憊?無動力,就真的很快退出系隊,幾乎沒在打了。

最後,對於這齣動畫共鳴滿滿,但也難抵擋後面稍微膩的慢步調 XD 像是第三季 10 集動畫就只有一場比賽,卻佔了一季啊。有機會再來看看漫畫了,有興趣想看看排球少年動畫的,可以在 愛奇藝瘋動畫(僅第四季)NETFLIX 逛逛囉!

2021年3月4日 星期四

[開箱] EZCast Beam V3 ,順便與 EZCast Beam J2 體積比較

 EZCast Beam V3

最近想找個伴手禮送人,想著想著就想起投影機很適合給家裡有小孩的人。挑了偏中低價位的投影機,至少有 720P 的輸出效果。剛好手中也有台 EZCast Beam J2 就拿來比較一下大小。

EZCast Beam V3

EZCast Beam V3

比完才發現 EZCast Beam J2 真的是袖珍版的微投影機 XD

這台 EZCast Beam V3 的服務項目:

  • HDMI 輸入 (投影機最基本需求)
  • USB 輸入
    • 支援 iPhone/iPad 透過有線完成 Airplay Mirror 投放
    • 支援 Android 透過有線完成 Chromecast Mirror 
    • 支援從隨身碟讀取資料來播放,如影片/照片/音樂
  • 無線輸入
    • 由於支援2.4G/5G 無線網路連線,有無線投放功能,如 DLNA / Miracast / Google Home Mirror / Airplay ,甚至官網還有提到 Youtube app 也可以搜尋到此裝置來投放
送人著重在體驗,力求極速上手,個人比較看重 USB 輸入,讓手機內容投放出去,方便第一!

更多資訊可以到官網 EZCast Beam V3 或是 EZCast網站購物 或是官方直營的 EZCast蝦皮商店 逛逛。

2021年3月1日 星期一

初探保險 - MY83保險網 / Finfo保險資訊站

來源:FINFO.TW 30歲罐頭保單的年費變化

差不多該研究保險了,之前一直偷懶是認為健保已非常夠用了!然而,對於家庭角色來說,保險有其特別的意義,如果有玩過遊戲 太空戰士七 的話,其寶石搭配的設計非常豐富,其中有一招是被敵人KO時,招喚不死鳥讓全體成員復活的機制,保險就是類似這種效果,核心設計是降低身旁親人的壓力,在經濟許可上,播一些擺在保險上當作理財規劃的一環作為保護。

這次研究保險時,主要透過了 MY83保險網FINFO保險資訊站 ,兩者的罐頭保單體驗都在伯仲之間,後來就留言跟專業的保險員互動,果真,很多事還是交給專業的來吧!保險員他們有的優勢:
  • 考過試
  • 比一般人熟悉保單設計、優惠和組合搭配
  • 有些專屬保險產品不是一般人可以買到的,屬於獨家商品
其實,這就跟開證券戶一樣,在網路可以痛快的申請完畢,但取得的交易手續費是公定價。如果透過證券營業員幫忙開戶,就可以拿到優惠的交易手續費。而透過保險員,有一些保險產品是獨家販售的。

我認為有興趣的可以大膽一點留訊息,實在是這些網站為了給網站使用者有更佳的服務體驗,有做了以下事務:
  • 網站平台透過考試/面試挑選配合的保險業務員
  • 每個業務員是被網站平台分配到客戶的
  • 網站平台還會時時數據追蹤,以確認保險業務員成交的轉換率為何
  • 保險業務員本身需要這類網站來取得來源
透過上述架構,有機會被分配到不錯的保險業務員,且業務員為了能夠有成交/業績/永續配分配到客戶,服務品質不會太差的。當然,也要花點心思去看對方配置的保險方案,去理解保險員配置思維。例如有次保單保險員配了兩個台灣人壽的主約,仔細去看才發現配置兩個主約的目的是讓其中一個附約的保障金額可以拉高,有些附約的保障金額限制不能超過主約保障金額的五倍價格,而通常主約都保得很低,靠附約搭配提升不少值。這時拆分多個主約就有其設計架構。

這些便是我這次接觸保險的心得,套句以前有當過保險業務的長輩分享,可以著重意外跟失能即可,而實支實付也可以考慮,但投資理財保單則不用花太多心思,因為投資類的方式有太多了,像是自我投資也是一種。

最後一提,失能可以分成意外失能跟疾病失能,後者的保費貴跟條件嚴謹,像是身體狀況有定期回診的,通常就無法用一般合理的保險額度來保疾病失能,但也存在非常昂貴的疾病失能險或是不保證能年年續保的失能險可挑選的。有興趣可以多多利用 FINFO保險資訊站 服務,可以觀看年紀與保費變化走勢囉。

2021年2月21日 星期日

[動畫] 弱角友崎同學 , 輕小說

農曆年節開始玩起了 DOS GAME 大富翁三 ,溫習了一下 XD 有了社會歷練後,玩起大富翁遊戲時,停留在銀行時,立馬辦貸款、辦信用卡並借好借滿(遊戲內是幾乎無風險的操作),經過股市立刻買好買滿,隨著戰況激烈,物價指數攀升,當然,股價也有對應的成長的。這大概就是長大後才懂的事,小時玩這遊戲還沒這種企圖心。

接著就回憶起在 bilibili 看到的 動畫 弱角友崎同學 這齣輕小說 動漫化的作品,提醒自己要好好進攻現實生活 XD 

這故事讓人想起自己國中時期,國一時段考都考個四百多名吧,全校 670 人,也就是全班 60人中,大概 35名之後。成天也在玩 PC Game ,倒也想不起自己有沒認真唸書過。但是,進入國二後,很神奇地在 物理 得到了解題興趣,彷彿不用花太多時間,只需理解又不用花時間背,極爽 XD 就這樣因為單一科物理的學習優勢,讓我整體學習心態改變,開始會用力花時間也把歷史和地理也背好,如此讓政體成績進入到全校排名 100 內。

這些過程,就像小說/漫畫 弱角友崎同學 的情節有那麼一點點近似,在人生中找到一點回饋感,進而把握去攻略人生。一樣的思維在十多年前也可以用在寫 Blog、研究技術等等,要再來找找生活回饋感了!

2021年2月18日 星期四

[開箱] ADONIT NOTE+ 觸控筆 與 iPad 8 ,使用 SketchBook app 測試傾斜效果

之前買了像 小米液晶手寫板 給小孩塗鴉,非常開心。最近在想怎樣引導小孩產生高質量的塗鴉,想著想著手邊就蹦出一台 iPad 8 跟第三方觸控筆 ADONIT NOTE+ 了!!說真的太久沒買 iPad ,第一次買是2011年的 iPad2 (代號 A1395) ,第二次買是 2013 年的 iPad Air (代號 A1474),最近才發現 iPad 第七代已經來到 10.2 吋。而添購 iPad 8 的主因之一是追求觸控筆支援,以及福利機價差不多,就乾脆買新的了!

試用了一下還滿順的,可惜的 2013 年 iPad Air 如預期的完全無法用,不然我也想添購一隻筆來輔助一下 XD 像是變成另一個畫板等等

2021年2月9日 星期二

Clubhouse 體驗心得


大概六天前剛拿到朋友的邀請,上去體驗了一下,非常新鮮。

聽到了海外大陸人在跟台灣人聊政治,且是很有理性的論述民主的好壞,像是台灣人說民主也沒因此比較多選擇(藍綠兩黨),還是會被媒體給包裝起來,但是!若選錯市長時,民主至少可以罷免!對於過去不認同的政治,可以身體力行去表達自己的心聲。而常說的民主會拖累經濟的議題,恰好有位在德國的大陸人在分享北上廣深的 GDP 其實跟台灣差不多,再加上自己老鄉連廁所都沒有,以此驗證民主也不會拖累經濟,講得還滿不錯的。

聽了在紐約的大陸人,在聊在美國或 App 朋友交際認識,不如線上遊戲那般親,線上遊線連討論個攻略,大家都毛起來教學 XD 而其他則是提到自己遠離曼哈頓,特不愛曼哈頓的急促、到時都要記得去抗爭(類似被欺負要去哪邊回報?!),聊仍在疫情中,跟大家介紹 crowdcow.com 可以買好吃又便宜的牛肉 XD 以及自己住的地方偏郊區,卻有什麼類似快捷大眾運輸,快速到市中心以及還可以轉乘到機場,且交通費便宜啊。是個很讚的聊天內容。

聽了一位台灣女生到北京外商 SaaS 發展,之前看了幾篇質量還不錯的科普文章,沒想到一年多沒更新是到北京發展了!聊了不少在 SaaS 的步調,包括公司估值方式或為何 SaaS 總是高估值、聊了訂閱制的經營方式,會有客戶經理在負責維護客戶忠誠度等等,也滿有意思的。途中也吸引到有一位從事投資的,回饋他在業界中常見的估值法,如同業比較法、評估新創的事業成長率等等,這段就還滿像 Youtuber 財經網紅 CK 的部分,就 CK 是一名在澳洲精算師,前陣子除了分享對總體經濟外,也分享她是怎樣評估股價(市值),過程就包括猜測成長率、套用估值公式等等,因此聽負責投資其他間公司的VC說話,特別有共鳴。

聽了葉丙成教授在那邊台語五四三,也滿逗趣的。不知哪個時間點自己也會變成那樣,喜歡跟幾位老友聊是非。讓我想起以前在工研院的老闆,英文很強,但總是愛台語,還會請特休去參加抗議活動,實在熱血 XD

聽了財報狗產品經理在分享他的工作方式,包括大老闆很挺他,也讓他去經營數個月的 podcast ,最終因為數據上並沒有顯著帶給財報狗對應的流量,再加上一集至少要五小時處理,每週一集的時間消耗也是很恐怖的。比較特別的是財報狗 PM 提到了一切都是數據導向,就算花兩週開發服務,最後的數據是差的,仍是會捨棄不上線的!而在招聘 RD 時,從一開始就會溝通可能高達 2/3 的 coding 都不會上線!大量專注在實驗、數據、決策等 growth hack 路線,對我而言是滿強化一些已知的手段,非常醒腦。而聊天提到了一些粗略的表單,沒想增加設計美感後,出現轉換率變差時,該怎樣去檢驗一直猜想,若拉不起來也不會上線的!核心 KPI 就是轉換率,轉換率沒變好就不會採用。非常專注。

也聽了一些早晨的新聞台,這我反而記憶最淺,而快速體驗後,感受到網路上的財經網紅會盡可能想搶流量,也不少在該領域非第一的網紅很努力地連續幾天都在開講,我一想到上班日都在做工作的事情,週末還要跑去聽工作相關的,而聊天品質又無法確定,實在累。

我想,嘗試過 clubhouse 後,對於一些尚未深入到 podcast 用戶,應當會感受非常新鮮。但對於已熟悉 podcast 來源者,可能會像我一樣又回味起 podcast 的高品質體驗。包括 podcast 可以快轉、可以看別人的評價,而 clubhouse 就是直播,且很有機會快速跟名人親近,是一種老早就存在的需求,但又重新被詮釋出來。然而 clubhouse 滿殺時間的,若參加到一場不錯的聊天,那心中會很充實,但如果加入到一場主持人不佳,講者也佳,想詮釋自己的想法也說不清時,那真的會聽不下去的 XD

果真核心項目是有限的時間資源,大家都在搶,搶人眼球、搶人耳朵、搶人時間,早已進入到任何的新創服務,就是要硬生生地佔領用戶的時間資源。而我,應該還是會回歸到 podcast 服務上,像是對岸的 得到app 也滿不錯的。這種專業的聲音產出內容,有那麼一種可預期的體驗感,有那麼可以快轉省時間的沾沾自喜感,而資訊/知識,其實也不打折的。

2021年2月3日 星期三

Flutter 開發文件之 iOS 與 Android 實作筆記 - 使用 Admob 以 Interstitial ads 為例


關於 flutter 使用 Admob 的架構,可參考網路上數篇文章:
此例筆記 iOS & Android 的設定方式,並且記錄碰到的錯誤訊息。

Android app 設定方式:

1. 更新 flutter_app/pubspec.yaml 添加 dependencies (記得要運行 flutter pub get)

firebase: ^8.0.0
firebase_admob: ^0.11.0+1

2. 安置從 Firebase 建立專案時得到的 google-services.json 檔案,擺在 flutter_app/android/app/ 中

3. 更新 flutter_app/android/build.gradle 的 buildscript -> dependencies 設定

//classpath 'com.android.tools.build:gradle:3.5.0'
// 為了修正錯誤訊息:error: unexpected element <queries> found in <manifest>.
classpath 'com.android.tools.build:gradle:4.0.0'
classpath 'com.google.gms:google-services:4.3.3'

4. 更新 flutter_app/android/gradle/wrapper/gradle-wrapper.properties 的 gradle 版本至 6.1.1

#為了修正錯誤訊息:Minimum supported Gradle version is 6.1.1. Current version is 5.6.2. If using the gradle wrapper
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip

5. 增加 flutter_app/app/build.gradle 的 apply 項目

apply plugin: 'com.google.gms.google-services'

6. 更新 flutter_app/android/app/src/main/AndroidManifest.xml 添加 meta data (僅範例程式數值)

<meta-data android:name="com.google.android.gms.ads.APPLICATION_ID" android:value="ca-app-pub-3940256099942544~3347511713"/>

7. 更新 flutter_app/lib/main.dart (此改自於範例程式,讓 add Button 可以產生一個插頁廣告)

iOS app 設定方式:

1. 更新 flutter_app/pubspec.yaml 添加 dependencies(記得要運行 flutter pub get)

firebase: ^8.0.0
firebase_admob: ^0.11.0+1

2. 安置從 Firebase 建立專案時得到的 GoogleService-Info.plist ,用 Xcode 打開專案,把 GoogleService-Info.plist 拖拉擺入跟 Info.plist 的同階目錄位置。

% open flutter_app/ios/Runner.xcworkspace
% ls flutter_app/ios/Runner/GoogleService-Info.plist

3. 更新 Info.plist 內容,添加資訊 GADApplicationIdentifier 等資訊(僅範例程式數值)

<key>GADApplicationIdentifier</key>
<string>ca-app-pub-3940256099942544~1458002511</string>
<key>SKAdNetworkItems</key>
  <array>
    <dict>
      <key>SKAdNetworkIdentifier</key>
      <string>cstr6suwn9.skadnetwork</string>
    </dict>
  </array>

4. 更新 Podfile ,上方添加 platform :ios, '10.0'

為了修正錯誤訊息: 
Automatically assigning platform `iOS` with version `9.0` on target `Runner` because no platform was specified. Please specify a platform for this target in your Podfile.

5. 更新 flutter_app/lib/main.dart (此改自於範例程式,讓 add Button 可以產生一個插頁廣告)

如此就收工啦!而 flutter_app/lib/main.dart 內容如下:

import 'package:flutter/material.dart';

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_admob/firebase_admob.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  MobileAdTargetingInfo targetingInfo = MobileAdTargetingInfo(
    keywords: <String>['flutterio', 'beautiful apps'],
    contentUrl: 'https://flutter.io',
    childDirected: true,
    nonPersonalizedAds: true,
  );

  InterstitialAd _interstitialAd;

  InterstitialAd createInterstitialAd() {
    return InterstitialAd(
      adUnitId: InterstitialAd.testAdUnitId,
      targetingInfo: targetingInfo,
      listener: (MobileAdEvent event) {
        print("InterstitialAd event $event");
      },
    );
  }

  @override
  void initState() {
    super.initState();
    FirebaseAdMob.instance.initialize(appId: FirebaseAdMob.testAppId);
    //_interstitialAd = createInterstitialAd()..load();
  }

  @override
  void dispose() {
    _interstitialAd?.dispose();
    super.dispose();
  }

  void _incrementCounter() {
    _interstitialAd?.dispose();
    _interstitialAd = createInterstitialAd()..load()..show();
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

 

2021年1月23日 星期六

Chromecast with Google TV - 透過網頁安裝 愛奇藝 TV app 與 愛奇藝手機程式投放到 Chromecast


前陣子不小心買了 Chromecast with Google TV ,想說來試試看,結果裡頭一堆台灣內容提供者 App 不能安裝,如:

  • 動畫瘋
  • 愛奇藝
  • CATCHPLAY+
就明明在 應用程式 -> 娛樂 都有顯示一堆台灣的內容服務,卻說因為國家地區而無法安裝 Orz 推論可能是手上這台是美國貨...有地區限制吧?真是殘念

Updated @ 2021-02-03:

主要是我使用的 Google account 的地區落在美國。可以在 https://play.google.com/store/paymentmethods 的付款方式,查看網頁底部的 地 區資訊 

後來在找了一下,嘿嘿,原來可以靠網頁來安裝程式,立即測試了一下!但不是每一款都能安裝。

在 2021/01/23 成功者:
在 2021/01/23 失敗者:
後續再等看看會不會有什麼變化。

另外,愛奇藝其實有分新加坡(香港)跟北京兩個 App ,其中新加坡(香港)的手機程式可以直接投放到 Chromecast 上,而北京的不行,有興趣可以去找找!

2021年1月22日 星期五

Flutter 開發文件之 iOS 與 Android 實作筆記 - 使用 Firebase 回報 App 使用人數


先說一下,有出類似文件教人怎樣使用 Firebase - Admob 部分,在此僅流水帳紀錄引用 firebase 的用法,體驗一下透過 Flutter 同時開發 ios 和 android app 時,並且使用額外函式庫的過程。

原理:

1. 使用 Android Studio + Flutter plugin 來建立 Flutter 專案
- 在此 package name 使用 org.changyy.study.flutter
- 自動產生 android Package Name = org.changyy.study.flutter_app
- 自動產生 iOS Bundle ID = org.changyy.study.flutterApp
- 假定專案位於 ~/AndroidStudioProjects/flutter_app

2. 建立 Firebase 專案
- 在此使用 org-changyy-study-flutter 作為 firebase project name

3. iOS app
- 使用 Xcode 置入 firebase 的設定檔案 GoogleService-Info.list
- 用指令打開專案 % open ~/AndroidStudioProjects/flutter_app/ios/Runner.xcworkspace/
- 使用 CocoaPods 添加 Firebase 函式庫
- 在 AppDelegate.swift 置入 Firebase 啟動程式碼

4. Android app
- 依照 Firebase 專案建置過程安置 google-service.json 檔案
- 在 <專案>/build.gradle 設置相依套件 com.google.gms:google-service:4.3.4
- 在 <專案>/app/build.gradle 添加 plugin 和引入 com.google.firebase:firebase-analytics

5. 回到 Flutter 專案,分別叫出 iOS simulator 跟 Android emulator 運行


若沒意外,就可以同時在這兩個模擬器上跑出來,並且在 Firebase 專案後臺,看到兩個使用者:



2021年1月14日 星期四

[Javascript] 複寫 XMLHttpRequest 來紀錄 Network Request URL @ Chrome Browser DevTools

最近 podcast 很夯,來研究 <audio> 。之前研究過 <video> 就有發現現在都用很夯 blob 的播放方式,透過 JS 片段下載資料交給 <video> 播放,以至於從 JS 調閱出 <video> 元件時,看不到真實的影片來源,而是一連串 blob (Binary Large Object) 記憶體位置。

原本想說在 Chrome DevTools 下,能不能靠 JS 取得 network request 發送清單來做應用,看著看著突然腦筋一轉,乾脆就用 XMLHttpRequest 好了,多包一層就可以收集了。

用法:

//
// https://stackoverflow.com/questions/7775767/javascript-overriding-xmlhttprequest-open
//
(function() {
var proxied = window.XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function() {
// console.log( arguments );
//
// 只關注 m3u8 來源
//
if (arguments.length >= 2 && arguments[1].indexOf('.m3u8') > 0) {
console.log( arguments );
// return;
}
return proxied.apply(this, [].slice.call(arguments));
};
})();

2021年1月9日 星期六

[開箱] Chromecast with Google TV - 2020/09/30 發表

Chromecast with Google TV

沒想到一進入 2021年,突然又手癢了!立馬買了 Chromecast with Google TV 來把玩。之前沒那麼想入手是因為台灣還買不到,以及自己已經有 Chromecast Ultra,興趣缺缺,但人果真很善變,過了幾個月就突然想起,又快速下手買了一個 XD 這次嘗試粉色系,連內附的電池外裝也粉色!

Chromecast with Google TV

這次體驗的心情,就像 Apple TV 的感受一樣,有個遙控器,並且跟市面上的新電視或電視和一樣支援語音輸入(語音搜尋),我之前對 Apple TV / Android TV 沒這麼愛的主因是輸入難用並認為手機就是本體,凡事都走投影來釋放手機資源,但對 Google TV 可以安裝 App 仍感興趣,這次測試了一下 NETFLIX 後,感覺 NETFLIX 的 UX 體驗在各平台皆非常相近,當下也不會覺得不方便(感興趣都在首頁,滑一滑點一下就可以觀看)

之前有朋友很想要觀看 NETFLIX 4K 內容,查了一下 Chromecast with Google TV 是有支援的,若真的可以用 49.99 美金買到,真的物超所值。這在 NETFLIX 官方文件也有提到:

以下 Chromecast 型號目前支援 Netflix 超高畫質串流功能:

Chromecast Ultra

Chromecast with Google TV

ref: 如何在 Chromecast 上使用 Netflix 

回過頭來,聊聊裝置的啟用,就包括會經過 Google Home app 掃描電視上的 QRCode,而手機過程也可以看到能多訂閱很多項目、音控辨識是否個人化等等,其中有個過程是會配對遙控器紅外線跟電視聲音的控制,十分貼心,因為許多類似設備就算有遙控器也沒規劃用紅外線去控制電視的聲音。

Chromecast with Google TV

感受比較深的是多了很多內容,以前用投影的方式必須自己去尋找有哪些內容,而採用 Google TV 則是大家為主動擠到這個 Android TV Google play 上,像是美國已上線的 Disney+ 等等,有趣的,我反而在 Android TV Google play 上,無法下載 myVideo, LiTV 這類本土程式,不知道是不是 Chromecast with Google TV 內有地區資訊,若是這種綁定關係,在意的就只能再等等吧?不知是不是本土商只在 TW region 上架,還是哪邊可以更改 Chromecast with Google TV 設備的所在地:

Chromecast with Google TV

Updated @ 2021/01/23:使用 Google Play 網頁版安裝愛奇藝

Updated @ 2021/02/03:主要是我使用的 Google account 的地區落在美國。可以在 https://play.google.com/store/paymentmethods 的付款方式,查看網頁底部的 地區 資訊

其餘細節是這款要求外部電源供應,直接插電視 USB 會提醒這是行不通的:

Chromecast with Google TV

最後,Google TV 上面的預設首頁也會隨著使用過的內容 App 節錄出該 App 的熱門內容,效果跟 NETFLIX 首頁很像,這樣一眼可以得知哪些感興趣可以點擊:

Chromecast with Google TV

相關文章:

2021年1月8日 星期五

[Linux] 使用 FlyVPN 與 EZCast EZC-5200 Hotspot 設置跳板開發服務(RTL8821CU Linux Driver) @ Ubuntu 20.04 Desktop

ezcast ezc-5200 ic

最近要模擬俄羅斯客戶使用產品上碰到的問題,由於產品是需要聯網的,手頭上的 AWS/GCP 都沒有俄羅斯節點,只好透過 FlyVPN 翻牆到莫斯科,而 FlyVPN 操作方式是非常簡單的,並且提供了各類平台的運行程式。

若服務邏輯可以在 PC 上執行,那就很搭配 FlyVPN 快驗證完,但如果是要在聯網裝置驗證,下一刻就得研究如何讓設備也處於莫斯科環境了!想了想,就是拿一台 Ubuntu Desktop 建立 Hostspot 環境,再讓 IoT 等設備透過 Hotspot 聯外就對了。

整個過程就是先從 EZCast 官網左下角的產品型錄(Product Catalog),得知 EZCast EZC-5200 是採用瑞昱的 RTL8821CU 晶片,接著研究一下 RTL8821CU Driver,就屬 brektrou/rtl8821CU 維護這份最不錯(很奇怪為何不在 RealTek 的官網找到 XD 或是類似官方 github.com/rtlwifi-linux 找到),且各處的程式碼編譯上都可能有問題,例如 linux kernel 大於 5.8 會踩到一些重複    定義的 structure 問題等等,總之,這次在 github.com/brektrou/rtl8821CU 得到很好的編譯體驗。

而挑選 Ubuntu Desktop 是比較適合打包成 VM ,方便傳遞給需要的同事當作測試環境,類似的做法在 Windows 也是能對應實現的(在 Windows 跑 FlyVPN 、建置熱點等等)。在此使用 VirutalBox 並且把 USB 設定自動掛在這 EZC-5200 網卡

virtualbox-usb-device-auto-mount

流水帳 - 環境建置:

$ sudo apt update 

$ sudo apt upgrade

$ sudo apt autoremove

$ sudo apt autoclean

$ sudo apt install net-tools git curl wget tmux vim dkms build-essential bc unzip linux-headers-$(uname -r) -y

流水帳 - 編譯 Linux Driver:

$ git clone https://github.com/brektrou/rtl8821CU.git

$ cd rtl8821CU

$ sudo ./dkms-install.sh

$ sudo usb_modeswitch -KW -v 0bda -p 1a2b

$ sudo systemctl start bluetooth.service

$ sudo vim /lib/udev/rules.d/40-usb_modeswitch.rules

在 LABEL="modeswitch_rules_end" 之前添加

# Realtek 8211CU Wifi AC USB

ATTR{idVendor}=="0bda", ATTR{idProduct}=="1a2b", RUN+="/usr/sbin/usb_modeswitch -K -v 0bda -p 1a2b"

$ sudo reboot

接著進入系統後,從右上角就可以看到無線網路了,進入無線網路的設定,想要開啟熱點時,會收到 "系統原則禁止做為熱點" ,查了一下貌似 Ubuntu 20.04 的 bug ?主因是權限不夠大,但這段應當要有用 sudo 來運行的方式才對?總之,就改用 root 登入即可,而 GUI 預設是不允許 Root 登入的。

ubuntu-20.04-wifi-hotspot-error

流水帳 - 啟用 Root 登入 GUI 方式:

先替 Root 設定密碼:

$ sudo passwd

開放 Root 登入權限:

$ sudo vim /etc/gdm3/custom.conf

添加

AllowRoot = true

$ sudo /etc/pam.d/gdm-password

添加 # 註解下段限制

# auth   required        pam_succeed_if.so user != root quiet_success

$ sudo reboot

最後在 GUI 登入介面上,挑選其他帳號(點擊"沒有列出來?"),就可以靠輸入帳號密碼的方式,用 root 登入,登入後就可以順利建制 Hotspot 出來使用

流水帳 - 查看是否有偵測到 USB WIFI:

$ lsusb

Bus 001 Device 003: ID 0bda:XXXX Realtek Semiconductor Corp. 802.11ac NIC

...

Ubuntu 20.04 Hotspot 建立方式:

右上角網路Icon -> 無線網路未連線 -> Wi-Fi 設定值 -> 新視窗的右上角 ... -> 開啟 Wi-Fi 熱點 -> 設定 SSID 跟 PSK 就可以完成啟動

ubuntu-20.04-wifi-setup

關於 FlyVPN 的啟動方式,就參考官網即可,建議先完成 FlyVPN 的連線後,再開啟 Hotspot ,例如用手機連上 Hotspot 時,可以開瀏覽器瀏覽 ipinfo.io 看看顯示的地區是否有正確更換。

流水帳 - FlyVPN :

$ ./flyvpn login

$ ./flyvpn list

$ sudo .flyvpn connect "Moscow #13"

選 tcp 即可

如此在 Ubuntu 可以另外再開個 terminal 運行 curl ipinfo.io 就得知 IP 是否有變換到莫斯科了