質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
87.20%
JWT(JSON Web Token)

JWT(JSON Web Token)とは、JSONをベースとしたアクセストークンの仕様。電子署名付きのURL safeなJSONのことを指します。電子署名が付いているため、改ざんをチェックできる点がメリットです。

HttpWebRequest

HttpWebRequestとは.NETにおけるクラスであり、WebRequestクラスをHTTPに導入するものです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Google Analytics API

Google Analytics APIは、アクセス解析機能が行える API(Application Programming Interface)です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

解決済

【GAS】Pythonならきちんと動いてるLINE Ads APIのHTTPリクエストをGASでも動かしたい

退会済みユーザー

退会済みユーザー

総合スコア0

JWT(JSON Web Token)

JWT(JSON Web Token)とは、JSONをベースとしたアクセストークンの仕様。電子署名付きのURL safeなJSONのことを指します。電子署名が付いているため、改ざんをチェックできる点がメリットです。

HttpWebRequest

HttpWebRequestとは.NETにおけるクラスであり、WebRequestクラスをHTTPに導入するものです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Google Analytics API

Google Analytics APIは、アクセス解析機能が行える API(Application Programming Interface)です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1回答

0評価

0クリップ

424閲覧

投稿2022/09/19 03:28

解決したいこと

開発の背景

  • LINE広告のパフォーマンスレポートをスプレッドシートに自動で書き出したい
  • 他にGoogle広告やYahoo!広告のデータ抽出もすでにGASで実装しているため、できればGASで書きたい

発生しているエラー

Request failed for https://ads.line.me returned code 401. Truncated server response: {"errors":[{"reason":"Unauthorized","message":"Request digest is invalid."}]}

エラーコードをドキュメントで調べてみると、

401 The token specified in 'Authorization' header is invalid.

「Authorization ヘッダーで指定されたトークンが無効」だと言われます。

ただ、Authorization ヘッダーに渡しているtoken という変数の中身は、PythonでもGASでも一致しているんです...。ここが今一番混乱しているところです。

以下が「レスポンスが返ってくるPythonのコード」と「エラーで返ってくるGASのコード」です。
変数名も全体の構成もなるべく同じにしています。

Python

Python

import base64 import datetime import hashlib import hmac from inspect import signature import json import urllib.request from datetime import datetime, timedelta def calc_sha256_digest(content: str) -> str: sha256 = hashlib.new('sha256') sha256.update(content.encode()) return sha256.hexdigest() def encode_with_base64(value: bytes) -> str: return base64.urlsafe_b64encode(value).decode() if __name__ == '__main__': # Setting parameters for your request today = datetime.now() yesterday = (today - timedelta(1)).strftime('%Y-%m-%d') accountId = "A00000000000" access_key = "XXXXXXXXXXXXXXXX" secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" method = "POST" canonical_url = "/api/v3/adaccounts/" + accountId + "/pfreports" url_parameters = "" request_body = { "level":"ADACCOUNT", "since": yesterday, "until": yesterday, "filtering":{ "idType":"ADACCOUNT", "ids":[accountId] }, "fileFormat":"CSV" } has_request_body = request_body is not None endpoint = 'https://ads.line.me' + canonical_url + url_parameters request_body_json = json.dumps(request_body) if has_request_body else "" content_type = 'application/json' if has_request_body else "" jws_header = encode_with_base64( json.dumps({ "alg": "HS256", "kid": access_key, "typ": "text/plain" }).encode() ) hex_digest = calc_sha256_digest(request_body_json) payload_date = today.strftime('%Y%m%d') payload = "%s\n%s\n%s\n%s" % (hex_digest, content_type, payload_date, canonical_url) jws_payload = encode_with_base64(payload.encode()) signing_input = "%s.%s" % (jws_header, jws_payload) signature = hmac.new( secret_key.encode(), signing_input.encode(), hashlib.sha256 ).digest() encoded_signature = encode_with_base64(signature) token = "%s.%s.%s" % (jws_header, jws_payload, encoded_signature) print(token) http_headers = { "Date": today.strftime('%a, %d %b %Y %H:%M:%S GMT'), "Authorization": "Bearer %s" % token } if has_request_body: http_headers["Content-Type"] = content_type req = urllib.request.Request(endpoint, data=request_body_json.encode(), headers=http_headers, method=method) else: req = urllib.request.Request(endpoint, headers=http_headers, method=method) with urllib.request.urlopen(req) as res: resp = res.read() print(resp.decode())

GAS

JavaScript

function main() { const today = new Date(); const yesterday = getYesterday(new Date()); const accountId = "A00000000000"; const access_key = "XXXXXXXXXXXXXXXX"; const secret_key = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"; const method = "POST"; const canonical_url = `/api/v3/adaccounts/${accountId}/pfreports`; const url_parameters = ""; const request_body = { "level":"ADACCOUNT", "since":yesterday, "until":yesterday, "filtering": { "idType": "ADACCOUNT", "ids": [accountId] }, "fileFormat": "CSV" }; const has_request_body = request_body const endpoint = "https://ads.line.me" + canonical_url + url_parameters; const request_body_json = JSON.stringify(request_body).replace(/:/g, ': ').replace(/,/g, ', '); const content_type = "application/json"; const jws_header = encode_with_base64(JSON.stringify({ "alg":"HS256", "kid":access_key, "typ":"text/plain" }).replace(/:/g, ': ').replace(/,/g, ', ')); const hex_digest = calc_sha256_digest(request_body_json); const payload_date = Utilities.formatDate(today, 'JST', 'yyyyMMdd'); const payload = `${hex_digest}\n${content_type}\n${payload_date}\n${canonical_url}`; const jws_payload = encode_with_base64(payload); const signing_input = `${jws_header}.${jws_payload}`; const encoded_signature = hmacSha256(secret_key, signing_input); const token = `${jws_header}.${jws_payload}.${encoded_signature}`; console.log(token); const http_headers = { "Date": Utilities.formatDate(today, 'GMT', 'E, dd MMM yyyy HH:mm:ss z'), "Authorization": `Bearer ${token}`, "Content-Type":content_type } const resp = UrlFetchApp.fetch(endpoint, { "data": request_body_json, "headers": http_headers, "method": method, }); console.log(resp.getContentText()); } /** * SHA 256でハッシュ化 * @param {string} input - ハッシュ化する文字列 * @return {string} - ハッシュ値 */ function calc_sha256_digest(input) { const rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, input, Utilities.Charset.UTF_8); let txtHash = ''; for (i = 0; i < rawHash.length; i++) { let hashVal = rawHash[i]; if (hashVal < 0) { hashVal += 256; } if (hashVal.toString(16).length == 1) { txtHash += '0'; } txtHash += hashVal.toString(16); } return txtHash } /** * base64エンコード * @param {string} input - エンコードしたい文字列 * @return {string} - base64エンコードされた文字列 */ function encode_with_base64(input) { return Utilities.base64Encode(input, Utilities.Charset.UTF_8) } /** * HMAC SHA 256でハッシュ化 * @param {string} key - ハッシュ化するシークレットキー * @param {string} text - ハッシュ化する文字列 * @return {string} - ハッシュ値 */ function hmacSha256(key, text) { const rowHash = Utilities.computeHmacSignature(Utilities.MacAlgorithm.HMAC_SHA_256, text, key); return Utilities.base64Encode(rowHash); }; /** * 昨日の日付を計算 * @param {date} date - 今日の日付 * @return {date} - 昨日の日付(YYYY-MM-DD形式) */ function getYesterday(date) { date.setDate(date.getDate() - 1); const yesterday = Utilities.formatDate(date, 'JMT', 'yyyy-MM-dd'); return yesterday }

エラーの原因っぽいところ

エラーメッセージ的にはtoken が間違っているみたいなんですが...。
Pythonでprint(tokne) とした結果と、GASでconsole.log(token) とした結果は完全に一致していました。

だとすると最後のHTTPリクエストのところなのかなと...。
Pythonだと

Python

resp = urllib.request.Request(endpoint, data=request_body_json.encode(), headers=http_headers, method=method)

と書いていて、GASでは

JavaScript

const resp = UrlFetchApp.fetch(endpoint, { "data": request_body_json, "headers": http_headers, "method": method, });

と書いているのですが、このあたりは自信がないです。

「Pythonはrequest_body_json をエンコードしてるけどこれってバイト列に変えてるってこと...?GAS(JavaScript)にバイト列ってあるっけ...?これ変換しないとだめなのか...?」となっています。

Pythonに関してはかなり知識不足なところがありますが、上記誤っている箇所がお分かりになる方いらっしゃいましたら、ご教示いただけるととても勉強になります...。よろしくお願いいたします...。

補足

この「LINE Ads APIのPython→GAS変換」は以前にも当サイトで質問させていただいたことがありました。
今回と直接関係するか分かりませんが、こちらも記載させていただきます。

Pythonで書かれたLINE Ads APIのサンプルコードをGAS(JavaScript)に翻訳したい

良い質問の評価を上げる

以下のような質問は評価を上げましょう

  • 質問内容が明確
  • 自分も答えを知りたい
  • 質問者以外のユーザにも役立つ

評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

  • プログラミングに関係のない質問
  • やってほしいことだけを記載した丸投げの質問
  • 問題・課題が含まれていない質問
  • 意図的に内容が抹消された質問
  • 過去に投稿した質問と同じ内容の質問
  • 広告と受け取られるような投稿

評価を下げると、トップページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

odataiki

2022/09/19 06:42

一つずつ値をチェックしていくしかないと思います。 getYesterdayが想定している値になっているのでしょうか? JMT → GMTまたはJST? ``` function getYesterday(date) { date.setDate(date.getDate() - 1); const yesterday = Utilities.formatDate(date, 'JMT', 'yyyy-MM-dd'); return yesterday } ```

まだ回答がついていません

会員登録して回答してみよう

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
87.20%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問

同じタグがついた質問を見る

JWT(JSON Web Token)

JWT(JSON Web Token)とは、JSONをベースとしたアクセストークンの仕様。電子署名付きのURL safeなJSONのことを指します。電子署名が付いているため、改ざんをチェックできる点がメリットです。

HttpWebRequest

HttpWebRequestとは.NETにおけるクラスであり、WebRequestクラスをHTTPに導入するものです。

JavaScript

JavaScriptは、プログラミング言語のひとつです。ネットスケープコミュニケーションズで開発されました。 開発当初はLiveScriptと呼ばれていましたが、業務提携していたサン・マイクロシステムズが開発したJavaが脚光を浴びていたことから、JavaScriptと改名されました。 動きのあるWebページを作ることを目的に開発されたもので、主要なWebブラウザのほとんどに搭載されています。

Google Analytics API

Google Analytics APIは、アクセス解析機能が行える API(Application Programming Interface)です。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。