Google+ Followers

2018年4月20日 星期五

[SEO] Google search console 與使用 wget 建立 sitemap @ macOS 10.13.14

最近公司換了網域,導流量過程中需要安置個 sitemap 來增加曝光,就先查了一下 Google 牌的定義:

https://support.google.com/webmasters/answer/183668?hl=zh-Hant

不錯不錯,可以提交純文字,那就搭配個 wget 吧!

$ time wget --spider --recursive --no-verbose --output-file=log.txt https://example.com/
...
real 87m25.965s
user 0m1.836s
sys 0m4.081s


真久,接著再靠 grep 跟 awk 即可:

$ grep -op "URL:http[s]://\(.*\) " ~/log.txt | awk '{print(substr($1,5));}' > sitemap.txt

搞定!

2018年4月3日 星期二

[PHP] 微信開放平台開發筆記 - WeChat Web 應用與 OAuth2 client

今年才感到 Wechat app 崛起,得知用戶已破10億,甚至微信支付交易筆數早已超車支付寶1.5倍,已乃覺三十里 Orz 想要用境外身份申請,才發現很多都辦不成,像是境外要用公司身份才行,甚至騰訊公開平台的管理者都還得綁有中國銀行卡(金融卡)才搞得定 <囧> 多虧對岸同事的幫忙,可以小試身手一下。

騰訊公開平台的 OAuth2 文件: https://open.weixin.qq.com/cgi-bin/showdocument?action=dir_list&t=resource/res_list&verify=1&id=open1419316505&token=&lang=zh_CN

需要的留意的是騰訊公開平台的 Web 應用只允許填入一個 hostname/domain 作為 callback handler ,所以,也有人想得很快,透過在統一地方處理完畢後,在拋去其他地方,如:https://github.com/HADB/GetWeixinCode

在此利用一樣的架構,不過是在 backend 處理 XD 主要是服務常分成 production/alpha/dev site,就把它統一建構在 Production site ,但 Dev site 要登入時,跳去 production site 處理後,再把資料回拋到 dev site(當然也可以申請多個 WeChat Web 應用,每個 site 擁有自己的 Web app)

整個過程都算順利,唯一不太順的地方是 OAuth state 這個參數的使用,原先埋了要跳去其他地方的 callback url 或其他資訊,但測了幾輪發現 state 內若有一些敏感資訊(&)時,回來都會出錯,於是就多加個 base64_encode 來解了(也可以考慮把必要的資料儲存在 cookie)

發動處:

<?php

$init_url = 'https://open.weixin.qq.com/connect/qrconnect?'. http_build_query( array(
        'scope' => 'snsapi_login,snsapi_userinfo',
        'response_type' => 'code',
        'redirect_uri' => 'https://auth.my-domain/wechat/callback',
        'state' => base64_encode( http_build_query(array(
                'give_me_code' => 1,
                'callback' => 'https://service.my-domain/wechat/handle-code',
        )) ),
        'appid' => $weixin_app_id,
));


負責處理 WeChat OAuth2 回傳資料(code):https://auth.my-domain/wechat/callback

<?php

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $code,
'grant_type' => 'authorization_code',
)));
$ret = @json_decode(curl_exec($ch), true);
             
$state = $_GET['state'];
$state_param = array();
if (!empty($state)) {
$state = @base64_decode($state);
parse_str($state, $state_param);
}

isset($state_param['callback']) && (
preg_match('#http[s]://(.*?)\.my-domain/#i', $state_param['callback'], $match)
) ) {
if (!empty($_GET['code'])) {
if (isset($state_param['give_me_code']) && $state_param['give_me_code'] == '1')
header('Location: '.$state_param['callback'].'?'.http_build_query(array('code' => $_GET['code'])) );
// ...
} else {
header('Location: '.$state_param['callback'].'?'.http_build_query(array('error' => 'no code')) );
}
return;
}


處理 code:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/oauth2/access_token');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'appid' => $weixin_app_id,
'secret' => $weixin_app_secret,
'code' => $ret_code,
'grant_type' => 'authorization_code',
)));

$ret = @json_decode(curl_exec($ch), true);

if (isset($ret['access_token']) && isset($ret['refresh_token']) && isset($ret['openid']) && isset($ret['unionid'])) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.weixin.qq.com/sns/userinfo');
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query( array(
'access_token' => $ret['access_token'],
'openid' => $ret['openid'],
)));
$profile_ret = @json_decode(curl_exec($ch), true);
}

// $profile_ret['nickname']
// $profile_ret['headimgurl']


原先在 callback handler 上,順便做完 code 檢驗,但後來覺得這樣要傳出去的資料太多(且感到不安),所以還是統一把 code 拋出去,缺點是其他服務就必須都紀錄 WeChat Web 應用的資料(app_id/secret_key)等等,但可以避免受到 DNS 攻擊而把用戶的 access_token 給了出去。

[Android] 使用 Chrome Browser - Web Inspector 查看 App WebView 行為

之前用了一陣子,然後又忘了,還是寫一下筆記:

  1. 要把 Android app 內使用的 Webview 開啟 setWebContentsDebuggingEnabled
    • WebView.setWebContentsDebuggingEnabled(true);
  2. Android phone 也要開啟 developer mode 和 adb debug
  3. 使用 Chrome browser -> 開發人員選項 -> more tools -> remote devices

順利的話,就會看到手機彈跳出一些視窗,按一按授權就看在 chrome browser 上追蹤 App 內 webview 的行為,像是追蹤其他國家內的網頁資源有被有被 ban 掉 XD

請直接參考:https://developers.google.com/web/tools/chrome-devtools/remote-debugging/?hl=zh-tw

未來可以規劃 release apk 不開啟 setWebContentsDebuggingEnabled ,而 dev apk 再開啟即可。

2018年4月1日 星期日

[Linux] Let's Encrypt 與 Wildcard 憑證,申請免費 SSL 憑證/HTTPS服務 @ Ubuntu 14.04

*.changyy.org

之前看強者同事已經幫公司網域申請完畢了,我才想到自己的網站一直沒打通 XD 趁個週日練一下吧!

看一下 Let's encrypt 網站簡介:https://letsencrypt.org/docs/client-options/
建議用 Certbot !

挑了一下 Nginx 與 Ubuntu server:

$ sudo apt-get install software-properties-common
$ sudo add-apt-repository ppa:certbot/certbot
$ sudo apt-get update
$ sudo apt-get install python-certbot-nginx


然後就只要指道幾令即可:

$ sudo certbot --manual certonly  -d *.changyy.org --server https://acme-v02.api.letsencrypt.org/directory

過程會要求你添加 DNS TXT record,添加完再按 Enter 驗證。

最後產出:

/etc/letsencrypt/live/changyy.org/fullchain.pem
/etc/letsencrypt/live/changyy.org/privkey.pem


來個 /etc/nginx/conf.d/ssl.changyy.org.conf

server {
    listen       80;
    listen  443 ssl;
    ssl_certificate /etc/letsencrypt/live/changyy.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/changyy.org/privkey.pem;

    server_name  ssl.changyy.org;

    access_log  /var/log/nginx/ssl.changyy.org-access.log  main;

    location / {
        root   /usr/share/nginx/html;
        index  index.php index.html index.htm;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}


收工!若是使用 Cloudflare 託管 DNS 的話,也可以透過 Certbot 一口氣都做完事喔:

Welcome to certbot-dns-cloudflare’s documentation!

另外,對我而言,這個服務最想用的就是 private ip server 啦!像是 NAS 等等的,只要透過手動設定即可搞定!

2018年3月10日 星期六

[Python] Google Voice Kit 與 Raspberry Pi 3

AIY4

上一次 Pi 1 開機已經想不起來了,大概是 2013 年的事吧,那年在研究 Raspbmc / Airplay 吧,大概是配置的 SDCard 太慢了,再加上已經長期投資 Linode 後,就漸漸把機器擺進防潮箱了,最近則是因為智慧喇吧的興起,想挑一款產品試試,想著想著,就想到去年就曾看到 Google Voice Kit ,而當初買不下去是因為美國才賣 10 美金,但台灣賣超過 30 美金,更別說美國那邊可是 35 美金含 Pi 3 的方案啊 XD

後來想通後,把多出來的價碼當作代購吧 Orz 何必為了一點還可以承擔的錢,延遲或放棄學習的機會?時間可是不等人的!總之,就這樣噴了將近快台幣 3000 吧,一張近似裸裝 Pi 3 (13XX) + Google Voice Kit (900) + microSD (300),各別都還要加上運費和轉帳費。(若單純對智慧音箱有興趣的,建議可以直接買 Google Home mini ,在露天代購不用兩千,甚至接近一千五,跟定價 49.99 美金非常接近)

AIY3

安裝硬件跟紙盒還滿快的,15 分鐘內應當可以搞定,接下來是軟體與線上服務的搭配,開通 GOOGLE ASSISTANT API 及 OAuth 認證後,就可以執行非常簡單的 demo 機制。

寫到這,的確可以穩穩體驗一下,但突然很反骨的想自己試試一些“中文”,需要搞定的項目為:
  • 語音輸入
  • 語音播放
  • 語音辨識
  • 語音合成(Text to Speech)
整個流程就是善用資源,哪邊有 code 要翻出來拼裝,像是語音輸入跟播放就用 Google 寫好的 aiy.audio,而語音辨識則是 SpeechRecognition ,語音合成則是 gTTS。比較特別的是語音播放是用 wav 格式,而 gTTS 的語音合成產出是 mp3 格式,所以再透過 pydub 轉成 wav (底層是 ffmpeg)

其中找資料的過程,主要是從 ~/Desktop/check_audio.desktop 進而研究 AIY-projects-python/checkpoints/check_audio.py 的寫法,找出底層實作的方式(command line!):

AIY-projects-python/src/aiy/audio.py
aiy._drivers._player
aiy._drivers._recorder
aiy._drivers._tts

AIY-projects-python/src/aiy/_drivers/_recorder.py
$ arecord --version
arecord: version 1.1.3 by Jaroslav Kysela <[email protected]>

AIY-projects-python/src/aiy/_drivers/_player.py
$ aplay --version
aplay: version 1.1.3 by Jaroslav Kysela <[email protected]>

AIY-projects-python/src/aiy/_drivers/_tts.py
$ pico2wave --usage
Usage: pico2wave [-?] [-w|--wave=filename.wav] [-l|--lang=lang] [-?|--help] [--usage] <words>


如果有興趣想追 GOOGLE ASSISTANT API 的實作,也可以多看看這些:

~/AIY-projects-python/src/examples/voice/assistant_library_with_button_demo.py
~/AIY-projects-python/src/examples/voice/assistant_library_demo.py
~/AIY-projects-python/src/aiy/assistant/library.py


很快就會追到 google.assistant.library,接著發現無法欣賞他如何實作 https://github.com/googlesamples/assistant-sdk-python/issues/77

最後,嘗試中文的 Speech to text 跟 text to speech ,再順便加一點點天氣回應,有興趣再到這邊看看,最大的關鍵點是用 AIY 的元件(arecord / aplay) 跟其他 gTTS / pydub 亂串時,發現有一些 async 狀態(要播放剛剛製成的音訊資料時,發現資料未完全寫入檔案,最後靠 time.sleep(3) 來解掉):

https://github.com/changyy/GoogleVoiceKitStudy/blob/master/detect-and-echo.py

2018年1月27日 星期六

[PHP] 使用 PHP built-in web server 及 PHP CodeIgniter framework

回想起來,我大概斷斷續續用了 CodeIgniter 七年了,最近才準備在本地開發 XD 開發上就在想 node.js 都有 webpack 等工具了,為啥 PHP 開發上還要先架設個 Web server 而感到納悶。

然而,其實 PHP 早已有 built-in web server 了,雖然只是 single-thread 但在開發上已經非常夠用,就來摸一下怎樣整再一起

$ cd project_web_document_root
$ php -S localhost:8000


接著就在 http://localhost:8000 就可以運行了!
然而,許多 web framework 都靠 routing 把 requests 統一集中到一支程式判斷,似乎已經是個非常基本的設計概念,那在 php built-in web server 也是可以的,他可以設定 routing 機制

$ cd project/web
$ cat ../tools/routing.php
$BASE_DIR = __DIR__ . '/../web' ;
if (file_exists($BASE_DIR . $_SERVER['REQUEST_URI']))
return false;
$_SERVER['SCRIPT_NAME'] = '/index.php';
include_once ($BASE_DIR . '/index.php');
$ php -S localhost:8000 ../tools/routing.php
PHP 7.0.27 Development Server started at Tue Jan 23 12:31:32 2018
Listening on http://localhost:8000
Document root is C:\Users\user\Desktop\ci-project\web
Press Ctrl-C to quit.


更多筆記:changyy/codeiginiter-and-php-built-in-web-server

2018年1月20日 星期六

AWS 筆記 - 使用 Ubuntu server 更換 Windows server 2012 帳號狀態

AWS Windows server

公司在三年前在 AWS Beijing 開了一台 Windows server 2012 運行一些套裝軟體,然而,負責的 IT 在整理系統帳號時,把 guest 關閉了。接著發生很多奇妙事件,最恐怖的是機器無法遠端登入了 Orz

一開始想朝更換 Administrator 密碼試試,結果一看過程步驟也不少:
使用 EC2Config 重置 Windows 管理员密码 -
https://docs.aws.amazon.com/zh_cn/AWSEC2/latest/WindowsGuide/ResettingAdminPassword_EC2Config.html
接著,反而朝惡搞的方式進去研究,例如:如何修改機碼啟用 guest 帳號


最後,使用了前一份(一年前版本)的全系統備份,開了一台機器,把他的 C:/Windows/System32/config/SAM 複製出來,覆蓋掉無法登入的機器上,如此一來就搞定了!

情況簡介:
A server: 壞掉的 Windows server 2012
B server: 從 A server 現狀建置 AMI 而開啟的機器
C server: 前一版次備份的 Windows server
D server: Ubuntu 14.04 server
A 計畫:
將 B server 的 C 槽卸下,並掛到 C server 上進行 B server 的 C槽 資料修改,改完後再卸下,重裝到 B server,把 B server 重新啟動,看看是否正常

然而,A 計劃失敗了,在 AWS console 可以看到系統無法正常通過檢驗,於是啟動了 B 計畫

B 計畫:
再次建置乾淨的 B server,將 B server 的 C 槽卸下,也把 C server - C 槽卸下,並一起掛到 D server 上,拿著 C server - C 槽關鍵資訊,直接覆蓋掉 B server - C 槽 資料,修改完後再卸下,重裝到 B server,再把 B server 重新啟動
果真 B 計劃成功了 :p 猜測 A方案 可能因為修改的是 Windows OS level 資訊,修改完可能也改變了 B server - C 槽狀態,以至於重新啟用時反而資訊對不上而無法開機。

AWS Windows server

其他筆記:

  1. Windows 需要 stop 機器,才能卸下指定硬碟,請開啟 “終止保護“ 避免不小心做錯事
  2. Windows 的系統碟(C:)根设备用 /dev/sda1 資訊,卸下後要重新裝上時,記得要用原本的 /dev/sda1 才行
  3. 在 EC2 - EBS 管理介面要卸下某硬碟時,建議把 Name 帶點原本的機器資訊,方便找尋
  4. 在 Ubuntu 要 mount NTFS 都還算簡單,sudo mount /dev/xsgf1 /mnt/Win2012CDisk 
  5. Windows 的帳號資訊擺在 C:\Windows\System32\config\SAM 檔案,可以單純做 cp /mnt/C_server_c_diskt/Windows/System32/config/SAM /mnt/B_server_c_diskt/Windows/System32/config/SAM 
  6. 驗證完畢後,就可以安心地把 A server 先暫停 -> 把系統槽卸下 -> 掛到 Ubuntu server -> mount 起來 -> 覆蓋掉 SAM -> 卸下 -> 掛回 A server -> A server 重新開機