###前提・実現したいこと
下記のサイトを参考にgoogle App Script(GAS)を使用してBotを制作しています.サイトの丁寧な説明もあり,テキストのツイートまではできたのですが,画像つきのツイートの方法がわからずに困っております.
画像はGoogleDriveもしくは他のオンライン上にあるものを使用したいと考えております.
サイト
http://yukiblg777.blog.fc2.com/blog-entry-47.html
https://kijtra.com/article/twitter-api-for-google-apps-script-without-oauthconfig/
お力添え,よろしくお願い致します.
###該当のソースコード
メインのスクリプト
1// 最初にこの関数を実行し、ログに出力されたURLにアクセスしてOAuth認証する 2function twitterAuthorizeUrl() { 3 Twitter.oauth.showUrl(); 4} 5 6// OAuth認証成功後のコールバック関数 7function twitterAuthorizeCallback(request) { 8 return Twitter.oauth.callback(request); 9} 10 11// OAuth認証のキャッシュをを削除する場合はこれを実行(実行後は再度認証が必要) 12function twitterAuthorizeClear() { 13 Twitter.oauth.clear(); 14} 15 16 17var Twitter = { 18 projectKey: "このprojectのProjectKey", 19 20 consumerKey: "TwitterのconsumerKey", 21 consumerSecret: "TwitterのconsumerSecret", 22 23 apiUrl: "https://api.twitter.com/1.1/", 24 25 oauth: { 26 name: "twitter", 27 28 service: function(screen_name) { 29 // 参照元:https://github.com/googlesamples/apps-script-oauth2 30 31 return OAuth1.createService(this.name) 32 // Set the endpoint URLs. 33 .setAccessTokenUrl('https://api.twitter.com/oauth/access_token') 34 .setRequestTokenUrl('https://api.twitter.com/oauth/request_token') 35 .setAuthorizationUrl('https://api.twitter.com/oauth/authorize') 36 37 // Set the consumer key and secret. 38 .setConsumerKey(this.parent.consumerKey) 39 .setConsumerSecret(this.parent.consumerSecret) 40 41 // Set the project key of the script using this library. 42 .setProjectKey(this.parent.projectKey) 43 44 45 // Set the name of the callback function in the script referenced 46 // above that should be invoked to complete the OAuth flow. 47 .setCallbackFunction('twitterAuthorizeCallback') 48 49 // Set the property store where authorized tokens should be persisted. 50 .setPropertyStore(PropertiesService.getUserProperties()); 51 }, 52 53 showUrl: function() { 54 var service = this.service(); 55 if (!service.hasAccess()) { 56 Logger.log(service.authorize()); 57 } else { 58 Logger.log("認証済みです"); 59 } 60 }, 61 62 callback: function (request) { 63 var service = this.service(); 64 var isAuthorized = service.handleCallback(request); 65 if (isAuthorized) { 66 return HtmlService.createHtmlOutput("認証に成功しました.このタブは閉じても問題ありません"); 67 } else { 68 return HtmlService.createHtmlOutput("認証に失敗しました"); 69 } 70 }, 71 72 clear: function(){ 73 OAuth1.createService(this.name) 74 .setPropertyStore(PropertiesService.getUserProperties()) 75 .reset(); 76 } 77 }, 78 79 api: function(path, data) { 80 var that = this, service = this.oauth.service(); 81 if (!service.hasAccess()) { 82 Logger.log("先にOAuth認証してください"); 83 return false; 84 } 85 86 path = path.toLowerCase().replace(/^//, '').replace(/.json$/, ''); 87 88 var method = ( 89 /^statuses/(destroy/\d+|update|retweet/\d+)/.test(path) 90 || /^media/upload/.test(path) 91 || /^direct_messages/(destroy|new)/.test(path) 92 || /^friendships/(create|destroy|update)/.test(path) 93 || /^account/(settings|update|remove)/.test(path) 94 || /^blocks/(create|destroy)/.test(path) 95 || /^mutes/users/(create|destroy)/.test(path) 96 || /^favorites/(destroy|create)/.test(path) 97 || /^lists/[^/]+/(destroy|create|update)/.test(path) 98 || /^saved_searches/(create|destroy)/.test(path) 99 || /^geo/place/.test(path) 100 || /^users/report_spam/.test(path) 101 ) ? "post" : "get"; 102 103 var url = this.apiUrl + path + ".json"; 104 var options = { 105 method: method, 106 muteHttpExceptions: true 107 }; 108 109 if ("get" === method) { 110 if (!this.isEmpty(data)) { 111 url += '?' + Object.keys(data).map(function(key) { 112 return that.encodeRfc3986(key) + '=' + that.encodeRfc3986(data[key]); 113 }).join('&'); 114 } 115 } else if ("post" == method) { 116 if (!this.isEmpty(data)) { 117 options.payload = Object.keys(data).map(function(key) { 118 return that.encodeRfc3986(key) + '=' + that.encodeRfc3986(data[key]); 119 }).join('&'); 120 121 if (data.media) { 122 options.contentType = "multipart/form-data;charset=UTF-8"; 123 } 124 } 125 } 126 127 try { 128 var result = service.fetch(url, options); 129 var json = JSON.parse(result.getContentText()); 130 if (json) { 131 if (json.error) { 132 throw new Error(json.error + " (" + json.request + ")"); 133 } else if (json.errors) { 134 var err = []; 135 for (var i = 0, l = json.errors.length; i < l; i++) { 136 var error = json.errors[i]; 137 err.push(error.message + " (code: " + error.code + ")"); 138 } 139 throw new Error(err.join("\n")); 140 } else { 141 return json; 142 } 143 } 144 } catch(e) { 145 this.error(e); 146 } 147 148 return false; 149 }, 150 151 error: function(error) { 152 var message = null; 153 if ('object' === typeof error && error.message) { 154 message = error.message + " ('" + error.fileName + '.gs:' + error.lineNumber +")"; 155 } else { 156 message = error; 157 } 158 159 Logger.log(message); 160 }, 161 162 isEmpty: function(obj) { 163 if (obj == null) return true; 164 if (obj.length > 0) return false; 165 if (obj.length === 0) return true; 166 for (var key in obj) { 167 if (hasOwnProperty.call(obj, key)) return false; 168 } 169 return true; 170 }, 171 172 encodeRfc3986: function(str) { 173 return encodeURIComponent(str).replace(/[!'()]/g, function(char) { 174 return escape(char); 175 }).replace(/*/g, "%2A"); 176 }, 177 178 //Twitterに画像を投稿します。 179//引数 text・・・画像と一緒に投稿するテキスト 180// picture・・・投稿する画像 181 iamge_upload: function(text,picture) { 182 var that = this, service = this.oauth.service(); 183 if (!service.hasAccess()) { 184 Logger.log("先にOAuth認証してください"); 185 return false; 186 } 187 188 189 var boundary = "cuthere"; 190 var requestBody = Utilities.newBlob( 191 "--"+boundary+"\r\n" 192 + "Content-Disposition: form-data; name=\"status\"\r\n\r\n" 193 + text+"\r\n"+"--"+boundary+"\r\n" 194 + "Content-Disposition: form-data; name=\"media[]\"; filename=\""+picture.getName()+"\"\r\n" 195 + "Content-Type: " + picture.getContentType()+"\r\n\r\n").getBytes(); 196 197 requestBody = requestBody.concat(picture.getBytes()); 198 requestBody = requestBody.concat(Utilities.newBlob("\r\n--"+boundary+"--\r\n").getBytes()); 199 200 var options = { 201 method: "post", 202 contentType: "multipart/form-data; boundary="+boundary, 203 payload: requestBody, 204 muteHttpExceptions: true 205 }; 206 207 var url = "https://api.twitter.com/1.1/statuses/update_with_media.json"; 208 209 try { 210 var result = service.fetch(url, options); 211 var json = JSON.parse(result.getContentText()); 212 } catch(e) { 213 214 } 215 216 return json; 217 }, 218 //画像ツイートここまで 219 220 init: function() { 221 this.oauth.parent = this; 222 return this; 223 } 224}.init(); 225 226 227/******************************************************************** 228以下はサポート関数 229*/ 230 231// ツイートする 232Twitter.tweet = function(data, reply) { 233 var path = "statuses/update"; 234 if ("string" === typeof data) { 235 data = {status: data}; 236 } else if(data.media) { 237 path = "statuses/update_with_media "; 238 } 239 240 if (reply) { 241 data.in_reply_to_status_id = reply; 242 } 243 244 return this.api(path, data); 245}; 246
ツイート用のスクリプト
1function Tweet(){ 2 Twitter.tweet("Test Tweet"); 3}
###試したこと
iamge_uploadに引数として画像のURLやbase64に変換した画像のデータを与えてみましたが,何もツイートされませんでした.

回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。