先日の記事でも少し触れましたが…Amazonの製品カタログ上の画像を利用したアソシエイト用リンクを取得するため2017年にひっそり公開したChrome拡張「Associates Link Generator」のPA-API v5対応が完了。旧バージョンからの移行による混乱を防ぐためアイコンを変え、新しいアイテムとして公開しました。
想定ユーザはかなり限定されますが、ブログ記事の執筆等でAmazon商品の画像やリンクの挿入に時間を割かれている方の負担軽減になれば幸いです。
先日の記事でも少し触れましたが…Amazonの製品カタログ上の画像を利用したアソシエイト用リンクを取得するため2017年にひっそり公開したChrome拡張「Associates Link Generator」のPA-API v5対応が完了。旧バージョンからの移行による混乱を防ぐためアイコンを変え、新しいアイテムとして公開しました。
想定ユーザはかなり限定されますが、ブログ記事の執筆等でAmazon商品の画像やリンクの挿入に時間を割かれている方の負担軽減になれば幸いです。
Amazonの製品カタログデータにアクセスできる公式API「Product Advertising API」(以下、PA-API)のバージョン5.0が昨年公開され、来月にも完全移行(旧バージョンでの利用が不可になる)が予定されています。
ブログ等を書く際の使い勝手向上のため自分本位に開発・公開しているChrome拡張「Associates Link Generator」も今のままでは使用出来なくなってしまうので新版リリースの準備を始めたのですが、Amazonが用意してくれているScratchpad(≠実働サンプル)にはJAVA、PHP、cURLあたりの実装例しか載っていなかったのでPHP版を参考にChrome拡張で使えるJavaScriptのClassを作成。
お約束ですが…参考にされる場合、自己責任でお願いします。(フォローやサポートは致しかねます)
'use strict';
export class AwsV4 {
constructor(accessKey, secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
this.path = null;
this.regionName = null;
this.serviceName = null;
this.httpMethodName = null;
this.queryParametes = [];
this.awsHeaders = [];
this.payload = "";
this.HMACAlgorithm = 'AWS4-HMAC-SHA256';
this.aws4Request = 'aws4_request';
this.strSignedHeader = null;
this.xAmzDate = this._getTimeStamp();
this.currentDate = this._getDate();
}
setPath(path) {
this.path = path;
}
setServiceName(serviceName) {
this.serviceName = serviceName;
}
setRegionName(regionName) {
this.regionName = regionName;
}
setPayload(payload) {
this.payload = payload;
}
setRequestMethod(method) {
this.httpMethodName = method;
}
addHeader(headerName, headerValue) {
this.awsHeaders[headerName] = headerValue;
}
_prepareCanonicalRequest() {
let canonicalURL = '';
canonicalURL += this.httpMethodName + "\n";
canonicalURL += this.path + "\n" + "\n";
let signedHeaders = '';
for (let key in this.awsHeaders) {
signedHeaders += key + ";";
canonicalURL += key + ":" + this.awsHeaders[key] + "\n";
}
canonicalURL += "\n";
this.strSignedHeader = signedHeaders.slice(0, - 1);
canonicalURL += this.strSignedHeader + "\n";
canonicalURL += this._generateHex(this.payload);
return canonicalURL;
}
_prepareStringToSign(canonicalURL) {
let stringToSign = '';
stringToSign += this.HMACAlgorithm + "\n";
stringToSign += this.xAmzDate + "\n";
stringToSign += this.currentDate + "/" + this.regionName + "/" + this.serviceName + "/" + this.aws4Request + "\n";
stringToSign += this._generateHex(canonicalURL);
return stringToSign;
}
_calculateSignature(stringToSign) {
const signatureKey = this._getSignatureKey (this.secretKey, this.currentDate, this.regionName, this.serviceName);
const signature = this._hash_hmac(stringToSign, signatureKey);
const strHexSignature = CryptoJS.enc.Hex.stringify(signature).toLowerCase();
return strHexSignature;
}
_ksort(obj) {
const keys = Object.keys(obj).sort();
let sortedObj = {};
for (let i in keys) {
sortedObj[keys[i]] = obj[keys[i]];
}
return sortedObj;
}
getHeaders() {
this.awsHeaders['x-amz-date'] = this.xAmzDate;
this.awsHeaders = this._ksort(this.awsHeaders);
const canonicalURL = this._prepareCanonicalRequest();
const stringToSign = this._prepareStringToSign(canonicalURL);
const signature = this._calculateSignature(stringToSign);
if (signature) {
this.awsHeaders ['Authorization'] = this._buildAuthorizationString(signature);
return this.awsHeaders;
}
}
_buildAuthorizationString($strSignature) {
return this.HMACAlgorithm + " " + "Credential=" + this.accessKey + "/" + this._getDate () + "/" + this.regionName + "/" + this.serviceName + "/" + this.aws4Request + "," + "SignedHeaders=" + this.strSignedHeader + "," + "Signature=" + $strSignature;
}
_generateHex(data) {
return CryptoJS.enc.Hex.stringify(CryptoJS.SHA256(data)).toLowerCase();
}
_hash_hmac(s, k) {
return CryptoJS.HmacSHA256(s, k);
}
_getSignatureKey(key, date, regionName, serviceName) {
const kSecret = "AWS4" + key;
const kDate = this._hash_hmac(date, kSecret);
const kRegion = this._hash_hmac(regionName, kDate);
const kService = this._hash_hmac(serviceName, kRegion);
const kSigning = this._hash_hmac(this.aws4Request, kService);
return kSigning;
}
_getTimeStamp() {
return new Date().toISOString().replace(/-|:|\.[0-9]{3}/g, "");
}
_getDate() {
return new Date().toISOString().replace(/-/g, "").substr(0,8);
}
}動作には別途CryptoJSが必要です。
三井住友カードの券面デザインが30年ぶり変更になったことを受け事前に申し込んでおいた新カードが到着。
わたしが利用しているゴールドカードの場合、ゴールド主体とグリーン主体の2種類のカラーが用意されていて好きな方を選ぶことが出来たので今回はグリーンをチョイスしました。

グリーンといっても深い緑色で「ダーク・グリーン」や「ブリティッシュ・グリーン」などと表現した方がイメージし易いかも。パルテノン神殿らしきものが描かれた時代遅れなステータス感をにおわせる旧デザインからシンプルで洗練されたイメージに一新されており、賛否両論あろうかと思いますがわたしは好きです。
なお、今回のデザイン変更に併せてカードの名称も「三井住友ゴールドカード」から「三井住友カード ゴールド」に変更されているのでご注意を。
先週、仕事の打ち合わせで札幌に行ってきました。
全日空は福岡から新千歳への直行便が1日1便しかないので今回は前日入り&翌日帰りという2泊3日のゆったりとした日程。東京-福岡間を日帰りで往復することもある普段のバタバタした出張とはえらい違い。
自由な時間を与えられたからには楽しまにゃ損!というわけでテレビ搭や時計台などの定番観光スポット巡りからスープカレー、海鮮、ラーメン、シメパフェといったグルメまで2004年夏以来およそ15年半ぶりの札幌を存分に満喫してきました。
手荷物が多くちゃんとしたカメラを持っていくことが出来なかったので生憎たいした写真はありませんが参考までにいくつか。

テレビ塔からの風景。一面の銀世界…を期待していたのですが、今年は暖かく雪も僅かしか降っていないとのことで写真映えはいまいち。せめて大通り公園で週末まで行われていた雪まつりの雪像が残っていれば…と期待していましたが、そちらもあいにくすべて取り壊し中で残念無念。
USB Type-Cに対応したデバイスの普及でレガシー規格となりつつあるmicroUSB。
充電速度やケーブルの汎用性で劣るmicroUSBに未来を見出すことはもはや不可能ですし、一部の機器のためだけに複数のケーブルを持ち続けるのも面倒なのでType-Cへの一本化を段階的に…ということで、2013年に購入した容量6,000mAhのモバイルバッテリー『cheero Power Plus 2 mini』の後釜としてType-CによるPower Deliveryにも対応した『Anker PowerCore 10000 PD Redux』を購入しました。
わたしにとって山行時や屋外撮影時がモバイルバッテリーの主な活用シーン。スマホの充電や撮影中に結露を防ぐためのレンズヒーターへの給電などが使途でバッテリー容量は大きいにこしたことはありませんが、ザックや撮影機材などヘビーな装備との兼ね合いで無駄に重たく嵩張るモバイルバッテリーは持ち歩けません。
そんなわたしにとって手のひらサイズの『cheero Power Plus 2 mini』はジャストサイズで長らく重宝していたのですが、6年半近い使用でバッテリーの劣化が否めなくなってきたのでこれに代わるアイテムとして目星をつけたのが『Anker PowerCore 10000 PD Redux』になります。

バッテリー容量10,000mAhで昇降圧に伴うロスを考慮しても容量3,000mAhのMoto G7 Plusなら2回はフル充電出来ます。
寝室のエアコンを点けていると隣接する書斎にも暖かい風が届くのでこれまで別途暖房器具は置いていなかったのですが、この季節はさすがに足先の冷えが気になるので机の下に置いておける『ピエリア 人感センサー付き足元パネルヒーター (PHT-0051JGY)』を購入。
当初は立てて使うホットカーペットとでも言うようないわゆる「デスクヒーター」的な商品を検討していたのですが、Amazon.co.jpにはさくらレビューばかりの怪しい中国製品しか見当たらなかったので嫌気。大人しく近くのインテリア店に出向いて新春セールで安くなっていたこちらの商品を買ってきた次第です。
