※当メディアのリンクにはアフィリエイト広告が含まれています

Unity(C#)でAmazon Product Advertising API(PA-API)を呼び出す

Amazon Product Advertising API(PA-API)の開発者ガイドにはJavaとPHPのサンプルしかないので、Unityで使用するにはC#で同様の処理を作成する必要があります。

AWSのUnity向けSDKを使用すれば苦労することはないのかもしれませんが、SDKを入手するためにAWSに登録するのもなんかなぁと思ったので、開発者ガイドにあるサンプルを参考にUnityで実行できるようにC#化してみました。

開発環境

  • Windows10 Pro
  • Unity 2021.3.11f1

コード

メインソース

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;

public class PAAPISample : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(PAAPIExcecute());
    }

    [Serializable]
    private class Payload
    {
        public string Keywords;
        public string PartnerTag;
        public string PartnerType;
        public string Marketplace;
    }

    private IEnumerator PAAPIExcecute()
    {
        Payload payload = new Payload
        {
            Keywords = "検索したいキーワード",
            PartnerTag = "トラッキングID",
            PartnerType = "Associates",
            Marketplace = "www.amazon.co.jp"
        };

        string serviceName = "ProductAdvertisingAPI";
        string region = "us-west-2";
        string accessKey = "アクセスキー";
        string secretKey = "シークレットキー";
        string requestPayload = JsonUtility.ToJson(payload);
        string host = "webservices.amazon.co.jp";
        string uriPath = "/paapi5/searchitems";

        AwsV4 awsV4 = new AwsV4(accessKey, secretKey);
        awsV4.SetRegionName(region);
        awsV4.SetServiceName(serviceName);
        awsV4.SetPath(uriPath);
        awsV4.SetPayload(requestPayload);
        awsV4.SetRequestMethod("POST");

        awsV4.AddHeader("host", host);
        awsV4.AddHeader("content-type", "application/json; charset=UTF-8");
        awsV4.AddHeader("x-amz-target", "com.amazon.paapi5.v1.ProductAdvertisingAPIv1.SearchItems");
        awsV4.AddHeader("content-encoding", "amz-1.0");

        using UnityWebRequest request = new UnityWebRequest("https://" + host + uriPath, UnityWebRequest.kHttpVerbPOST)
        {
            uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(requestPayload)),
            downloadHandler = new DownloadHandlerBuffer()
        };

        Dictionary<string, string> headers = awsV4.GetHeaders();
        foreach (KeyValuePair<string, string> header in headers)
        {
            request.SetRequestHeader(header.Key, header.Value);
        }

        yield return request.SendWebRequest();
        switch (request.result)
        {
            case UnityWebRequest.Result.InProgress:
                Debug.Log("Wait...");
                break;

            case UnityWebRequest.Result.Success:
                Debug.Log(request.downloadHandler.text);
                break;

            case UnityWebRequest.Result.ConnectionError:
            case UnityWebRequest.Result.ProtocolError:
            case UnityWebRequest.Result.DataProcessingError:
                Debug.Log(request.error);
                Debug.Log(request.downloadHandler.text);
                break;

            default:
                throw new ArgumentOutOfRangeException();
        }
    }
}

署名情報作成ソース

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;

public class AwsV4
{
    private string accessKey = null;
    private string secretKey = null;
    private string path = null;
    private string regionName = null;
    private string serviceName = null;
    private string httpMethodName = null;
    private Dictionary<string, string> awsHeaders = new Dictionary<string, string>();
    private string payload = "";

    private string HMACAlgorithm = "AWS4-HMAC-SHA256";
    private string aws4Request = "aws4_request";
    private string strSignedHeader = null;
    private string xAmzDate = null;
    private string currentDate = null;

    public AwsV4(string accessKey, string secretKey)
    {
        this.accessKey = accessKey;
        this.secretKey = secretKey;
        this.xAmzDate = GetTimeStamp();
        this.currentDate = GetDate();
    }

    public void SetPath(string path)
    {
        this.path = path;
    }

    public void SetServiceName(string serviceName)
    {
        this.serviceName = serviceName;
    }

    public void SetRegionName(string regionName)
    {
        this.regionName = regionName;
    }

    public void SetPayload(string payload)
    {
        this.payload = payload;
    }

    public void SetRequestMethod(string method)
    {
        this.httpMethodName = method;
    }

    public void AddHeader(string headerName, string headerValue)
    {
        this.awsHeaders[headerName] = headerValue;
    }

    private string PrepareCanonicalRequest()
    {
        string canonicalURL = "";
        canonicalURL += this.httpMethodName + "\n";
        canonicalURL += this.path + "\n" + "\n";
        string signedHeaders = "";
        foreach (KeyValuePair<string, string> header in this.awsHeaders)
        {
            signedHeaders += header.Key.ToLower() + ";";
            canonicalURL += header.Key.ToLower() + ":" + header.Value.Trim() + "\n";
        }
        canonicalURL += "\n";
        this.strSignedHeader = signedHeaders.Substring(0, signedHeaders.Length - 1);
        canonicalURL += this.strSignedHeader + "\n";
        canonicalURL += GenerateHex(this.payload);
        return canonicalURL;
    }

    private string PrepareStringToSign(string canonicalURL)
    {
        string stringToSign = "";
        stringToSign += this.HMACAlgorithm + "\n";
        stringToSign += this.xAmzDate + "\n";
        stringToSign += this.currentDate + "/" + this.regionName + "/" + this.serviceName + "/" + this.aws4Request + "\n";
        stringToSign += GenerateHex(canonicalURL);
        return stringToSign;
    }

    private string CalculateSignature(string stringToSign)
    {
        byte[] signatureKey = GetSignatureKey(this.secretKey, this.currentDate, this.regionName, this.serviceName);
        byte[] bytesToSign = Encoding.UTF8.GetBytes(stringToSign);
        using (HMACSHA256 hmacSha256 = new HMACSHA256(signatureKey))
        {
            byte[] hashBytes = hmacSha256.ComputeHash(bytesToSign);
            return BitConverter.ToString(hashBytes).Replace("-", "").ToLower();
        }
    }

    public Dictionary<string, string> GetHeaders()
    {
        awsHeaders["x-amz-date"] = xAmzDate;
        awsHeaders = awsHeaders.OrderBy(x => x.Key).ToDictionary(x => x.Key, x => x.Value);

        string canonicalURL = PrepareCanonicalRequest();
        string stringToSign = PrepareStringToSign(canonicalURL);
        string signature = CalculateSignature(stringToSign);
        if (signature != null)
        {
            awsHeaders["Authorization"] = BuildAuthorizationString(signature);
            return awsHeaders;
        }

        return null;
    }

    private string BuildAuthorizationString(string strSignature)
    {
        return HMACAlgorithm + " " + "Credential=" + accessKey + "/" + GetDate() + "/" + regionName + "/" + serviceName + "/" + aws4Request + "," + "SignedHeaders=" + strSignedHeader + "," + "Signature=" + strSignature;
    }

    private string GenerateHex(string data)
    {
        byte[] bytes = Encoding.UTF8.GetBytes(data);
        SHA256Managed sha256 = new SHA256Managed();
        byte[] hash = sha256.ComputeHash(bytes);
        return BitConverter.ToString(hash).Replace("-", "").ToLower();
    }

    private byte[] GetSignatureKey(string key, string date, string regionName, string serviceName)
    {
        byte[] kSecret = Encoding.UTF8.GetBytes("AWS4" + key);
        byte[] kDate = HMACSHA256(date, kSecret);
        byte[] kRegion = HMACSHA256(regionName, kDate);
        byte[] kService = HMACSHA256(serviceName, kRegion);
        byte[] kSigning = HMACSHA256(aws4Request, kService);

        return kSigning;
    }

    private byte[] HMACSHA256(string data, byte[] key)
    {
        HMACSHA256 hmac = new HMACSHA256(key);
        return hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
    }

    private string GetTimeStamp()
    {
        return DateTime.UtcNow.ToString("yyyyMMddTHHmmssZ");
    }

    private string GetDate()
    {
        return DateTime.UtcNow.ToString("yyyyMMdd");
    }
}

解説的な何か

基本的にPA-APIの開発者ガイドにあるサンプルをそれぞれC#に書き換えただけです。

使用するときはPayloadクラスのKeywordsに検索したいキーワード、PartnerTagにトラッキングIDを指定し、Amazonアソシエイトのページで作成したアクセスキーとシークレットキーを指定して実行すれば商品情報が取得できると思います。

最後に

AndroidならUnityで使う場合Javaのサンプルを流用してネイティブプラグインにするという強引な方法もあったんですけどね、それだと美しくないかなぁと思ってC#化してみました。

あくまでも開発者ガイドにあるサンプルをC#化しただけなので、このプログラムを使用して何かしらの問題が発生しても責任は取れません。