2016年7月28日 星期四

原來 macOS Beta 也會導致提交失敗


今天在提交 iOS 版本時出現問題。出現 ITMS-90167 錯誤碼。有人說可能是打包時出錯,於是重新打包。三次的結果一樣...。我試過使用 Xcode Beta 版本無法提交,自此以後不再使用 Beta 版。然而今次的錯誤卻指是因為使用了 Beta 版。仔細一看,原來不只是 Xcode,連 macOS 是 Beta 版也無法提交!正好,我是使用 macOS Sierra Beta 3 版本!由於 Mac 沒有降級的機制;如要還原到上一個 macOS 版本,得重灌整個系統及檔案。可是我卻沒有這個打算,只好找來退役了的 MacBook Air,安裝最新的 macOS 及 Xcode 進行提交工作...。

2016年7月14日 星期四

擷取天氣資料


前天編寫的抓取天氣數據程式中,沒有當月的數據,只有上月及之前的資料;而我希望的是伺服器每天儲存昨天的溫度記錄,所以要找過一條新的數據源。打開天文台的網頁,找到了昨天天氣資料的版面。若用 Chrome 觀察當中的連線,會發現數據來自 http://www.hko.gov.hk/cis/dailyExtract/dailyExtract_201607.xml 這個檔。打開惠會看到當月的天氣數據,正是我想要的東西。那麼,只要簡單改寫一下程式,便能把數據載入、擷取、儲存。
<?php
//----------------------------------------------------------------------------------------
//   Weather Data Parser
//----------------------------------------------------------------------------------------
//   Platform: PHP
//   Written by Pacess HO
//   Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------

header("Access-Control-Allow-Origin: https://home.pacess.com");
header("Access-Control-Allow-Methods: POST");

header("Content-type: application/json");
header("Cache-Control: no-cache, must-revalidate");
header("Expires: Tue, 07 Nov 2000 00:00:00 GMT");

date_default_timezone_set("Asia/Hong_Kong");
// mb_internal_encoding("UTF-8");
ini_set("memory_limit", "-1");
set_time_limit(0);

//----------------------------------------------------------------------------------------
require("./library/constants.php");
require("./library/DBConnection.php");
require("./library/weatherDataHongKong.php");

//----------------------------------------------------------------------------------------
//  Weather data come from Hong Kong Observatory
$urlFormat = "http://www.hko.gov.hk/cis/dailyExtract/dailyExtract_YYYYMM.xml";

$timeStamp = time()-(60*60*24);
$year = date("Y", $timeStamp);
$month = date("m", $timeStamp);
$day = intval(date("d", $timeStamp));

echo("\n");
echo("-----------------------------------------------------------\n");
echo("--  Weather Data Parser Version 1.00                     --\n");
echo("--  Written by Pacess HO                                 --\n");
echo("--  Copyright 2016 Pacess Studio.  All rights reserved.  --\n");
echo("-----------------------------------------------------------\n\n");

//  Prepare save file
$savePath = "./files/dailyExtract_$year$month.xml";

//  Construct download URL
$url = str_replace("YYYY", $year, $urlFormat);
$url = str_replace("MM", $month, $url);

//  Get content from URL
echo("Data source: $url\n");
$content = file_get_contents($url);

//  Save it to local folder
$result = file_put_contents($savePath, $content);

//  Parse content
echo("Parsing date: $year-$month-$day\n");
$jsonObject = json_decode($content);
$dataArray = $jsonObject->stn->data;
foreach ($dataArray as $data)  {

   $dataMonth = $data->month;
   if ($month != $dataMonth)  {continue;}

   //  Looping here because avoid non-linear ordering
   $monthData = $data->dayData;
   foreach ($monthData as $dayData)  {

      if ($dayData[0] != $day)  {continue;}
      echo("Data: ".json_encode($dayData)."\n");

      //  Parse data to variables
      $i = 1;
      $meanPressure = $dayData[$i++];
      $absoluteDailyMaxDegree = $dayData[$i++];
      $meanDegree = $dayData[$i++];
      $absoluteDailyMinDegree = $dayData[$i++];
      $meanDewPointDegree = $dayData[$i++];
      $meanRelativeHumidityPercentage = $dayData[$i++];
      $meanAmountOfCloudPercentage = $dayData[$i++];
      $totalRainfallMM = $dayData[$i++];

      //  Save to database
      $dictionary = array(
         "year"=>$year,
         "month"=>$month,
         "day"=>$day,
         "meanPressure"=>$meanPressure,
         "absoluteDailyMaxDegree"=>$absoluteDailyMaxDegree,
         "meanDegree"=>$meanDegree,
         "absoluteDailyMinDegree"=>$absoluteDailyMinDegree,
         "meanDewPointDegree"=>$meanDewPointDegree,
         "meanRelativeHumidityPercentage"=>$meanRelativeHumidityPercentage,
         "meanAmountOfCloudPercentage"=>$meanAmountOfCloudPercentage,
         "totalRainfallMM"=>$totalRainfallMM);

      $weatherDataHongKong = new weatherDataHongKong();
      if ($weatherDataHongKong != null)  {

         $dictionary = $weatherDataHongKong->insertOrUpdateRecord($dictionary);
         if ($dictionary != null)  {

            $affectedRows = $dictionary["affectedRows"];
            echo("Data saved with result #$affectedRows\n\n");
         }  else  {

            //  Unable to save data, maybe query problem?
            echo("### Unable to save data...\n\n");
         }
      }  else  {

         //  Unable to create database instance
         echo("### Unable to create database instance...\n\n");
      }

      //  Release database instance
      unset($weatherDataHongKong);
      break;
   }
   break;
}
?>

2016年7月13日 星期三

比較兩個目錄內的檔案及內容


臨危受命去處理一個項目的上線工作。由於程序是由前人編寫,在沒有交帶、沒有文件之下要處理無誤,需要細心的計劃。為了能有效及成功地完成工作,我編寫了一個簡單的程式,用來找出 UAT 及 Production 伺服器內不同內容的檔案:
<?php
//----------------------------------------------------------------------------------------
//  File Compare
//----------------------------------------------------------------------------------------
//  Written by Pacess HO
//  Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------
 
//----------------------------------------------------------------------------------------
function getDirectoryArray($directory)  {
   $resultArray = array(); 
   $fileArray = scandir($directory); 
   foreach ($fileArray as $key => $value)  {
 
      //  Skip . & ..
      if (in_array($value, array(".", "..")))  {continue;}
 
      $filePath = $directory.DIRECTORY_SEPARATOR.$value;
      if (is_dir($filePath) == false)  {array_push($resultArray, $filePath);}
      else  {
 
         $subdirectoryArray = getDirectoryArray($filePath);
         $resultArray = array_merge($resultArray, $subdirectoryArray);
      }
   } 
   return $resultArray; 
} 
 
//=========================================================================================
//  Main program
//----------------------------------------------------------------------------------------
echo("\n");
echo("-----------------------------------------------------------\n");
echo("--  File Compare v1.00                                   --\n");
echo("--  Written by Pacess HO                                 --\n");
echo("--  Copyright 2016 Pacess Studio.  All rights reserved.  --\n");
echo("-----------------------------------------------------------\n");

$folder01 = "uat";
$folder02 = "production";

$fileArray01 = getDirectoryArray($folder01);
$fileArray02 = getDirectoryArray($folder02);

sort($fileArray01);
sort($fileArray02);

$fileCount01 = count($fileArray01);
$fileCount02 = count($fileArray02);
echo("\nFile count: $fileCount01 vs $fileCount02\n\n");

$index01 = 0;
$index02 = 0;
$counter = 0;
while ($counter < 10000)  {
   $counter++;

   $filePath01 = "";
   $filePath02 = "";

   if ($fileCount01 > $index01)  {$filePath01 = $fileArray01[$index01];}
   if ($fileCount02 > $index02)  {$filePath02 = $fileArray02[$index02];}

   //  Compare file name first
   if ($filePath01 == "" && $filePath02 == "")  {break;}

   $filename01 = str_replace($folder01, "", $filePath01);
   $filename02 = str_replace($folder02, "", $filePath02);

   $result = strcmp($filename01, $filename02);
   if ($result != 0)  {

      //  File name not match, so ... 
      if ($result < 0)  {
         if ($filePath01 == "")  {
            echo("$filePath02 ... [$folder02 only]\n");
            $index02++;
         }  else  {
            echo("$filePath01 ... [$folder01 only]\n");
            $index01++;
         }
      }  else  {
         if ($filePath02 == "")  {
            echo("$filePath01 ... [$folder01 only]\n");
            $index01++;
         }  else  {
            echo("$filePath02 ... [$folder02 only]\n");
            $index02++;
         }
      }
      continue;
   }

   //  Same file name, then check file size first
   $index01++;
   $index02++;

   $fileSize01 = filesize($filePath01);
   $fileSize02 = filesize($filePath02);
   if ($fileSize01 != $fileSize02)  {
      echo("$filePath01 ... [Size: $fileSize01 vs $fileSize02]\n");
      continue;
   }

   //  Same file name with same file size
   if ($fileSize01 == 0)  {
      echo("$filePath01 ... [Size: 0]\n");
      continue;
   }

   //  Same file name with same file size, then compare content
   $md5File01 = md5_file($filePath01);
   $md5File02 = md5_file($filePath02);

   $result = strcmp($md5File01, $md5File02);
   if ($result != 0)  {
      echo("$filePath01 ... [Content mismatch]\n");
      continue;
   }
}
echo("~ End ~\n\n");
?>

2016年7月12日 星期二

抓取天氣數據


香港天文台的氣候資料服務提供了由 1885 起的天氣數據。內容以 JSON 格式儲存。要抓取數據作為分析之用,最理想是把資料放進數據庫。但在這個動作之前,我希望先把數據按照原本的形式儲存,之後才解讀當中的內容。

利用 Chrome 的 Network 工具得出天氣數據儲存在一個叫 dailyExtract_2016.xml。檔案儲存了一整年的數據。於是我編寫了以下 PHP 程式把所有年份的 XML 檔案下載到伺服器:
<?php
//----------------------------------------------------------------------------------------
//  Weather Data Grabber Version 1.00
//----------------------------------------------------------------------------------------
//  Platform: PHP
//  Written by Pacess HO
//  Copyright 2016 Pacess Studio.  All rights reserved.
//----------------------------------------------------------------------------------------

date_default_timezone_set("Asia/Hong_Kong");

//  Weather data come from Hong Kong Observatory
$urlFormat = "http://www.hko.gov.hk/cis/dailyExtract/dailyExtract_####.xml";

//  Start from 1885 to now
$currentYear = date("Y");
$year = 1885;

print("\n");
print("-----------------------------------------------------------\n");
print("--  Weather Data Grabber Version 1.00                    --\n");
print("--  Written by Pacess HO                                 --\n");
print("--  Copyright 2016 Pacess Studio.  All rights reserved.  --\n");
print("-----------------------------------------------------------\n\n");

//  Download all weather data
for ($y=$year; $y<=$currentYear; $y++)  {

   //  Prepare save file
   $savePath = "./files/dailyExtract_$y.xml";

   //  Check if exists, if not then download, otherwise skip
   if (file_exists($savePath) == true)  {
      print("$savePath...skip\n");
      continue;
   }

   //  Construct download URL
   $url = str_replace("####", $y, $urlFormat);

   //  Get content from URL
   $content = file_get_contents($url);

   //  Save it to local folder
   $result = file_put_contents($savePath, $content);

   //  Print the result (file size)
   print("$savePath...$result\n");
}

?>

2016年7月11日 星期一

升級 AWS 的 PHP 至 5.6

PHP 5.3.29 (cli) (built: May 12 2015 22:42:19) 
Copyright (c) 1997-2014 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2014 Zend Technologies
原本我寫了一個程式,每天讀取天氣數據並儲存起來。可惜已經遺失,只好重新編寫。反正有一年 AWS 免費的 EC2 服務,正好可放在哪兒。AWS 上的 PHP 版本是 5.3.29。而 phpMyAdmin 則要用上 PHP 5.6,所以要進行升級。步驟如下:

  • sudo service httpd stop
  • sudo yum remove php-*
  • sudo yum erase httpd httpd-tools apr apr-util
  • sudo yum install php56
  • sudo yum install php56-mysqlnd php56-common php56-gd php56-devel php56-mysql php56-mcrypt php56-mbstring mod24_ssl
  • sudo service httpd start
  • php -v

    這是升級後的 PHP 版本:
    PHP 5.6.22 (cli) (built: Jun  1 2016 21:46:41) 
    Copyright (c) 1997-2016 The PHP Group
    Zend Engine v2.6.0, Copyright (c) 1998-2016 Zend Technologies
  • 2016年7月10日 星期日

    解決 make sock: could not bind to address 0.0.0.0:80 問題


    在 AWS 上很多指令都要用 sudo 來執行,否則會出現問題。啟動 Apache、用 vi 編輯都有這個情況。

    2016年7月9日 星期六

    陳僖儀機械人


    昨晚弄起了一台在 AWS 的伺服器,今天在想它的用途時,決定重製 Facebook Messenger 機械人。找不到上次的代碼,唯有重寫。

    開發 Facebook 相關的網頁需要用上 SSL 證書,於是到 Let's Encrypt 申請一張,花了點時間才發現很多事情都要用 sudo 才能順利安裝。之後,按照 Messenger 官網的指示及格式,寫了個 PHP 版本的「陳僖儀機械人」。目前片段只是示範,人工智能還沒有這麼聰明,就像 Siri 一樣。但足以示範不同的回應,希望公司能接到這樣的工作,期待真正實踐的機會。代碼如下:
    <?php
    //----------------------------------------------------------------------------------------
    //  Sita Chan Bot (Facebook Messenger Bot)
    //----------------------------------------------------------------------------------------
    //  Written by Pacess HO
    //  Copyrights 2016 Pacess Studio.  All rights reserved.
    //----------------------------------------------------------------------------------------
    
    //  Page access token
    $_tokenPacessStudio = "Your access token here";
    
    $_verifyToken = "";
    $_mode = "";
    
    //----------------------------------------------------------------------------------------
    function callSendAPI($messageData)  {
       global $_tokenPacessStudio;
    
       //  API Url and Access Token, generate this token value on your Facebook App Page
       $url = "https://graph.facebook.com/v2.6/me/messages?access_token=".$_tokenPacessStudio;
       error_log("[FBMessengerBot] url=$url");
    
       //  Initiate cURL.
       $ch = curl_init($url);
    
       //  Tell cURL that we want to send a POST request.
       curl_setopt($ch, CURLOPT_POST, 1);
    
       //  Attach our encoded JSON string to the POST fields.
       curl_setopt($ch, CURLOPT_POSTFIELDS, $messageData);
       error_log("[FBMessengerBot] messageData=$messageData");
    
       //  Set the content type to application/json
       curl_setopt($ch, CURLOPT_HTTPHEADER, array("Content-Type:application/json"));
    
       //  Execute the request but first check if the message is not empty.
       $result = curl_exec($ch);
       error_log("[FBMessengerBot] result=$result");
       return $result;
    }
    
    //----------------------------------------------------------------------------------------
    function sendTextMessage($recipientID, $messageText)  {
       $jsonData = '{
          "recipient":{
             "id":"'.$recipientID.'"
          }, 
          "message":{
             "text":"'.$messageText.'"
          }
       }';
       callSendAPI($jsonData);
    }
    
    //----------------------------------------------------------------------------------------
    function sendImageMessage($recipientID)  {
       $jsonData = '{
          "recipient":{
             "id":"'.$recipientID.'"
          }, 
          "message":{
             "attachment":{
                "type":"image",
                "payload":{
                   "url":"https://www.pacess.com/sita-chan-bot/images/sita.jpg"
                }
             }
          }
       }';
       callSendAPI($jsonData);
    }
    
    //----------------------------------------------------------------------------------------
    function sendButtonMessage($recipientID)  {
       sendTextMessage($recipientID, "Button message is coming soon...");
    }
    
    //----------------------------------------------------------------------------------------
    function sendGenericMessage($recipientID)  {
       $jsonData = '{
          "recipient":{
             "id":"'.$recipientID.'"
          }, 
          "message":{
             "attachment":{
                "type":"template",
                "payload":{
                   "template_type":"generic",
                   "elements":[{
                      "title":"陳僖儀與媽媽 最後一次互動",
                      "subtitle":"蘋果動新聞",
                      "item_url":"http://hk.dv.nextmedia.com/actionnews/entertainment/20130417/18231328/20013358",               
                      "image_url":"http://static.apple.nextmedia.com/images/apple-photos/apple/20130417/large/1366177517_633f.jpg",
                      "buttons":[{
                         "type":"web_url",
                         "url":"http://hk.dv.nextmedia.com/actionnews/entertainment/20130417/18231328/20013358",
                         "title":"Open Web URL"
                      }, {
                         "type":"postback",
                         "title":"Save this news",
                         "payload":"Payload for first bubble",
                      }],
                   }]
                }
             }
          }
       }';
       callSendAPI($jsonData);
    }
    
    //----------------------------------------------------------------------------------------
    function sendFileMessage($recipientID)  {
       $jsonData = '{
          "recipient":{
             "id":"'.$recipientID.'"
          }, 
          "message":{
             "attachment":{
                "type":"file",
                "payload":{
                   "url":"https://www.pacess.com/sita-chan-bot/files/sita.pdf"
                }
             }
          }
       }';
       callSendAPI($jsonData);
    }
    
    //----------------------------------------------------------------------------------------
    function sendReceiptMessage($recipientID)  {
       sendTextMessage($recipientID, "Receipt message is coming soon...");
    }
    
    //----------------------------------------------------------------------------------------
    //  Webhooks functions
    //----------------------------------------------------------------------------------------
    function receivedAuthentication($messagingEvent)  {
       $senderID = $messagingEvent["sender"]["id"];
       $recipientID = $messagingEvent["recipient"]["id"];
       $messageTime = $messagingEvent["timestamp"];
    
       sendTextMessage($recipientID, "receivedAuthentication");
    }
    
    //----------------------------------------------------------------------------------------
    function receivedMessage($messagingEvent)  {
       $senderID = $messagingEvent["sender"]["id"];
       $recipientID = $messagingEvent["recipient"]["id"];
       $messageTime = $messagingEvent["timestamp"];
    
       $messagingDictionary = $messagingEvent["message"];
       $messageID = $messagingDictionary["mid"];
       $messageSequence = $messagingDictionary["seq"];
       $messageText = $messagingDictionary["text"];
       $messageAttachments = $messagingDictionary["attachments"];
    
       //----------------------------------------------------------------------------------------
       //  Base on message, do different things
       $messageText = strtolower($messageText);
    
       $index = strpos($messageText, "hi, sita");
       if ($index !== false)  {
    
          $message = "";
          $hour = date('H');
          if ($hour < 10)  {$message = "Good morning, Pacess.  What can I do for you?";}
          else if ($hour < 16)  {$message = "Good afternoon, Pacess.  What can I do for you?";}
          else if ($hour < 19)  {$message = "Good evening, Pacess.  What can I do for you?";}
          else {$message = "Good night, Pacess.  What can I do for you?";}
    
          sendTextMessage($senderID, $message);
          return;
       }
    
       $index = strpos($messageText, "image");
       if ($index !== false)  {sendImageMessage($senderID);  return;}
    
       $index = strpos($messageText, "button");
       if ($index !== false)  {sendButtonMessage($senderID);  return;}
    
       $index = strpos($messageText, "news");
       if ($index !== false)  {sendGenericMessage($senderID);  return;}
    
       $index = strpos($messageText, "receipt");
       if ($index !== false)  {sendReceiptMessage($senderID);  return;}
    
       $index = strpos($messageText, "pdf");
       if ($index !== false)  {sendFileMessage($senderID);  return;}
    
       //----------------------------------------------------------------------------------------
       //  Send normal text message
       $index = rand(0, 3);
       switch ($index)  {
          default:
          case 0: {sendTextMessage($senderID, "OK, $messageText");}  break;
          case 1: {sendTextMessage($senderID, "Hello, $messageText");}  break;
          case 2: {sendTextMessage($senderID, "Noted, $messageText");}  break;
          case 3: {sendTextMessage($senderID, "Um...$messageText");}  break;
       }
    }
    
    //----------------------------------------------------------------------------------------
    function receivedDeliveryConfirmation($messagingEvent)  {
       $senderID = $messagingEvent["sender"]["id"];
       $recipientID = $messagingEvent["recipient"]["id"];
       $messageTime = $messagingEvent["timestamp"];
    
       sendTextMessage($recipientID, "receivedDeliveryConfirmation");
    }
    
    //----------------------------------------------------------------------------------------
    function receivedPostback($messagingEvent)  {
       $senderID = $messagingEvent["sender"]["id"];
       $recipientID = $messagingEvent["recipient"]["id"];
       $timeOfPostback = $messagingEvent["timestamp"];
    
       //  The 'payload' param is a developer-defined field which is set in a postback button for Structured Messages. 
       $payload = $messagingEvent["postback"]["payload"];
    
       $message = "Received postback for user $senderID and page $recipientID with payload '$payload' at $timeOfPostback";
    
       //  When a postback is called, we'll send a message back to the sender to let them know it was successful
       sendTextMessage($senderID, $message);
    }
    
    //========================================================================================
    //  Main program
    //----------------------------------------------------------------------------------------
    if (isset($_REQUEST["hub_mode"]))  {$_mode = $_REQUEST["hub_mode"];  error_log("[FBMessengerBot] Mode=$_mode");}
    if (isset($_REQUEST["hub_verify_token"]))  {$_verifyToken = $_REQUEST["hub_verify_token"];  error_log("[FBMessengerBot] verifyToken=$_verifyToken");}
    
    //----------------------------------------------------------------------------------------
    //  Set this 'Verify Token Value' on your Facebook App 
    if ($_mode == "subscribe")  {
       if ($_verifyToken === "9b73938d049d7da4fdb600c4dcffe24f")  {
          $challenge = $_REQUEST["hub_challenge"];
          error_log("[FBMessengerBot] Subscribe webhook...$challenge");
          echo $challenge;
       }  else  {
          error_log("[FBMessengerBot] ### Unable to subscribe webhook...");
          echo("### Unable to subscribe service, make sure the validation tokens match...");
       }
       exit(0);
    }
    
    //----------------------------------------------------------------------------------------
    echo("Processing...");
    
    //  data = {
    //      "object":"page",
    //      "entry":[{
    //         "id":"98765432910",
    //         "time":1468058503315,
    //         "messaging":[{
    //            "sender":{"id":"98765432910"},
    //            "recipient":{"id":"1234567890"},
    //            "timestamp":1468058503251,
    //            "message":{
    //               "mid":"mid.1468058503242:f49cde80d52c67d427",
    //               "seq":9,
    //               "text":"Well"
    //            }
    //         }]
    //      }]
    //   }
    $data = file_get_contents("php://input");
    error_log("[FBMessengerBot] data=$data");
    $jsonData = json_decode($data, true);
    
    $object = $jsonData["object"];
    if ($object == "page")  {
       foreach ($jsonData["entry"] as $pageEntry)  {
    
          $pageID = $pageEntry["id"];
          $timeOfEvent = $pageEntry["time"];
          $messaging = $pageEntry["messaging"];
    
          if (isset($messaging) == false)  {continue;}
    
          //  Iterate over each messaging event
          foreach ($messaging as $messagingEvent)  {
          
             if (isset($messagingEvent["message"]))  {receivedMessage($messagingEvent);  continue;}
             if (isset($messagingEvent["delivery"]))  {receivedDeliveryConfirmation($messagingEvent);  continue;}
             if (isset($messagingEvent["optin"]))  {receivedAuthentication($messagingEvent);  continue;}
             if (isset($messagingEvent["postback"]))  {receivedPostback($messagingEvent);  continue;}
    
             error_log("[FBMessengerBot] ### Webhook received unknown messagingEvent:$messagingEvent");
          }
       }
    }
    
    //----------------------------------------------------------------------------------------
    echo("Program ended.");
    ?>

    2016年7月8日 星期五

    被 AWS 的 VPC 玩了兩天



    之前的 AWS 免費用戶的收費錯誤修正後,今個星期打算再次重新建立一台 EC2 服務器。按照同樣的教程,今次卻成功啟動 EC2 但無法 SSH 登入及 HTTP 瀏覽。原本以為是巧合出錯,於是刪了 EC2 再建立。問題同樣出現。搞了兩天,最終要向高手請教,得知是 VPC 的問題,但情況仍然得不到解決。之後,無意中按進了 VPC 的主板,隨手建立一個新 VPC 後,跑回 EC2 主板換上新的 VPC;今次卻成功了!我一直是在 EC2 主板中建立 Instance 時的其中一頁建立 VPC,想不到那頁建的是廢 VPC...。玩了兩晚,問題終於得到解決。下次真的要醒目一點!

    P.S.: 還有,Amazon Linux 跟機沒有 Apache,安裝要 root 但不能用 su -。要用 sudo yum install httpd。

    2016年7月3日 星期日

    E3D 塞咀


    家中的 ArrayZ 塞咀已有一段時間,心知要把打印頭逐個部份拆下來,沒有一兩小時不能完成。今天的起心肝,好好修理一下。

    原本以為是打印頭與散熱管的接縫位滲漏後凝固的問題;拆開後才發現卡著的是散熱管內。我用媒氣爐把散熱管溫度提升到攝氏 200 度,牙簽插進去,把卡著的膠料迫出。再以鑽咀手動把還附在散熱管內的膠料清走。最後用水喉水把散熱管降溫。清潔步驟完成。不過,BloodKeith 指應該用熱風槍來加熱,而不是跟媒氣明火直接接觸。

    把所有部件拼回後測試,發現還是卡著,似乎打印頭也有瘀塞情況...。這時已沒有心情處理,留待下次。

    2016年7月1日 星期五

    串流加密

    #EXTM3U
    #EXT-X-VERSION:3
    #EXT-X-TARGETDURATION:10
    #EXT-X-MEDIA-SEQUENCE:0
    #EXT-X-KEY:METHOD=AES-128,URI="mysp://kms.mps.tvb.com/kms/playback?contentid=732109&segmentid=0"
    早幾天在 Sita 群組內有絲友貼上 Sita 在 2012 年在 JSG 得獎的片段;我一直知道有這樣的事,卻沒有真真正正看過片段;於是安裝應用程式,登記免費帳號觀看。

    當然,我想以數碼非失真方式把它保存下來。看過節目的 m3u8,得到一堆 .ts 檔案。正要把 .ts 轉換成 mp4 時,卻出現了錯誤。相信是因為片源被 AES-128 加密了。 之前在 ShowMuse 開發上希望把影像內容加密,礙於知識不足、技術不足、資金不足、人手不足,最終都沒有達成。說回這裡,網上能找到解密的方法,可是得先有密鑰及它的 IV。密鑰 URI 那個 mysp:// 相個是自訂的格式,不知會如何處理。在 Charles 中找不到相關的連線,難度是 Socket 類的通訊?花了一段時間也找不到密鑰,暫時放棄一會。