イラストなどを投稿した自分のポストを異なる時間帯(昼や深夜など)にセルフRPして宣伝できるツールです script.google.com/macros/s/AKfycbwnbvG1hSPgYJOWz4yyH76WxJU3TEOmZIBk_3CeaImYNVn4uRpz21VlEjNPs06FojkJLQ/exec
bluesky gas

Compare changes

Choose any two refs to compare.

+8 -3
README.md
··· 1 1 # SkySpread 2 + URL: https://script.google.com/macros/s/AKfycbwnbvG1hSPgYJOWz4yyH76WxJU3TEOmZIBk_3CeaImYNVn4uRpz21VlEjNPs06FojkJLQ/exec 3 + by GAS(Google Apps Script) 4 + 5 + # Description 6 + ## English 7 + This tool spread your post(e.g. containing illustration or announcement) by automated repost (by your own account) at different times of day(such as daytime or midnight). 8 + 9 + ## Japanese 2 10 イラストなどを投稿した自分のポストを異なる時間帯(昼や深夜など)にセルフRPして宣伝できるツールです 3 - 4 - サイトURL: https://script.google.com/macros/s/AKfycbwnbvG1hSPgYJOWz4yyH76WxJU3TEOmZIBk_3CeaImYNVn4uRpz21VlEjNPs06FojkJLQ/exec 5 - GAS(Google Apps Script)を使用
+2 -2
appsscript.json
··· 4 4 "exceptionLogging": "STACKDRIVER", 5 5 "runtimeVersion": "V8", 6 6 "webapp": { 7 - "executeAs": "USER_DEPLOYING", 8 - "access": "ANYONE_ANONYMOUS" 7 + "executeAs": "USER_ACCESSING", 8 + "access": "ANYONE" 9 9 } 10 10 }
+79 -12
code.js
··· 1 + /** 2 + * @OnlyCurrentDoc 3 + */ 4 + 1 5 const HOUR_LIST = [[6, 8], //Morning 2 6 [11, 13],//Lunch 3 7 [17, 19],//Evening 4 8 [23, 25]]//MidNight 5 - const HOURS_NAME = ["朝", "昼", "夜", "深夜"]; 9 + const HOURS_NAME_JA = ["朝", "昼", "夜", "深夜"]; 10 + const HOURS_NAME_EN = ["Morning", "daytime", "evening", "midnight"]; 6 11 const HOURS_NUM = 4;//HOUR_LIST.length() 7 12 13 + function doGet(e) { 14 + const template = HtmlService.createTemplateFromFile('index'); 8 15 9 - function doGet(e) { 16 + if (Session.getActiveUserLocale().startsWith("ja")) { 17 + template.title = "自動化セルフRPで宣伝!"; 18 + template.description = "自分のポストを異なる時間帯(昼や深夜など)にセルフRPして宣伝できるツールです。"; 19 + template.appPassword = "アプリパスワード"; 20 + template.appPasswordDescription = `※Blueskyのパスワードでは<a href="https://scrapbox.io/Bluesky/Bluesky%E3%82%92%E5%A7%8B%E3%82%81%E3%82%8B%E4%B8%8A%E3%81%A7%E3%81%93%E3%82%8C%E3%81%A0%E3%81%91%E3%81%AF%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%84%E3%81%9F%E6%96%B9%E3%81%8C%E3%81%84%E3%81%84%E3%81%93%E3%81%A8">ありません</a>` 21 + template.postUrlDescription = "宣伝したいポストのURL" 22 + template.setSelfRP = "セルフRPを設定" 23 + template.support = `質問・要望などあれば<a href="https://bsky.app/profile/did:plc:wwqlk2n45es2ywkwrf4dwsr2">開発者(@lamrongol)</a>にお気軽に問い合わせください。` 24 + } else { 25 + template.title = "Spread your posts by automated self repost!"; 26 + template.description = "This tool reposts your post(by your own account) at different times of day(such as daytime or midnight)"; 27 + template.appPassword = "App Password"; 28 + template.appPasswordDescription = `※This is NOT Bluesky password.` 29 + template.postUrlDescription = "Post url you want to spread" 30 + template.setSelfRP = "Set self repost" 31 + template.support = `If you have question or request, please tell <a href="https://bsky.app/profile/did:plc:wwqlk2n45es2ywkwrf4dwsr2">developer(@lamrongol)</a>` 32 + } 33 + 10 34 const userProperties = PropertiesService.getUserProperties(); 11 35 const username = userProperties.getProperty("username"); 12 36 const appPassword = userProperties.getProperty("appPassword"); 13 37 const timerError = userProperties.getProperty("timerError"); 14 - const template = HtmlService.createTemplateFromFile('index'); 15 - template.deployURL = ScriptApp.getService().getUrl();; 16 - template.username = username 38 + template.deployURL = ScriptApp.getService().getUrl(); 39 + template.username = username; 17 40 template.appPassword = appPassword; 18 41 template.timerError = timerError; 19 - const htmlOutput = template.evaluate().setTitle("SkySpread - 自動化セルフRPで宣伝!"); 42 + const htmlOutput = template.evaluate() 43 + .setTitle("SkySpread - " + template.title) 44 + // .addMetaTag("og:type", "website") 45 + // .addMetaTag("og:title", template.title) 46 + // .addMetaTag("og:description", template.description); 47 + 20 48 return htmlOutput; 21 49 } 22 50 ··· 34 62 return htmlOutput; 35 63 } 36 64 65 + function resetData() { 66 + const userProperties = PropertiesService.getUserProperties(); 67 + userProperties.deleteAllProperties() 68 + } 69 + 37 70 function confirmTemplate(e) { 71 + const isJa = Session.getActiveUserLocale().startsWith("ja"); 72 + 38 73 const template = HtmlService.createTemplateFromFile('confirm'); 39 74 const postUrl = e.parameter.postUrl 40 75 41 76 template.username = e.parameter.username; 42 77 template.deployURL = ScriptApp.getService().getUrl(); 43 78 template.postUrl = postUrl; 79 + if (isJa) { 80 + template.message = "以下の時刻でセルフRPします" 81 + } else { 82 + template.message = "Repost by your own account at following time" 83 + } 44 84 template.errMessage = ""; 45 85 46 86 const sessionData = createSession(e.parameter.username, e.parameter.appPassword); 47 87 gasLog(sessionData); 48 88 if (!sessionData.did) { 49 - template.errMessage = "ログインに失敗しました: " + sessionData.message; 89 + if (isJa) { 90 + template.errMessage = "ログインに失敗しました: " + sessionData.message; 91 + } else { 92 + template.errMessage = "login failed: " + sessionData.message; 93 + } 50 94 const htmlOutput = template.evaluate(); 51 95 return htmlOutput; 52 96 } ··· 66 110 67 111 const post = getPost(sessionData.accessJwt, sessionData.did, postUrl) 68 112 if (post == null) {// (post.author.did != did) { 69 - template.errMessage = "ポストが存在しない、または違う人のポストです"; 113 + if (isJa) { 114 + template.errMessage = "ポストが存在しない、または違う人のポストです"; 115 + } else { 116 + template.errMessage = "Post doesn't exist, or it is not yours"; 117 + } 70 118 const htmlOutput = template.evaluate(); 71 119 return htmlOutput; 72 120 } ··· 76 124 const repo = { uri: post.uri, cid: post.cid } 77 125 // gasLog(post) 78 126 const unixTimeZero = new Date(post.record.createdAt); 79 - const hour = unixTimeZero.getHours(); 127 + 128 + const hour = getLocalHour(unixTimeZero); 80 129 var minHourDiff = 24; 81 130 var nearestIdx = -1; 82 131 var idx = 0; ··· 98 147 idx++; 99 148 } 100 149 101 - idx = (nearestIdx + 1) % HOURS_NUM; 150 + idx = nearestIdx; 102 151 // var hourNames = []; 103 152 var rpList = JSON.parse(userProperties.getProperty("rpList")); 104 153 gasLog(rpList) ··· 115 164 idx = (idx + 1) % HOURS_NUM 116 165 const startHour = (HOUR_LIST[idx][0] + getRandomInt(2)) % 24; 117 166 // hourNames.push(HOURS_NAME[idx]); 118 - tableHtml += `<tr><td>${HOURS_NAME[idx]}</td><td>${HOUR_LIST[idx][0]}時から${HOUR_LIST[idx][1]}時まで</td>` + "\n"; 167 + if (isJa) { 168 + tableHtml += `<tr><td>${HOURS_NAME_JA[idx]}</td><td>${HOUR_LIST[idx][0]}時から${HOUR_LIST[idx][1]}時まで</td>` + "\n"; 169 + } else { 170 + tableHtml += `<tr><td>${HOURS_NAME_EN[idx]}</td><td>${HOUR_LIST[idx][0]}:00~${HOUR_LIST[idx][1]}:00</td>` + "\n"; 171 + } 119 172 rpList.push({ repo: repo, hour: startHour }) 120 173 } 121 174 userProperties.setProperty("rpList", JSON.stringify(rpList)); ··· 267 320 } 268 321 userProperties.setProperty("sessionData", JSON.stringify(sessionData)); 269 322 270 - const nowHour = new Date().getHours(); 323 + const nowHour = getLocalHour(new Date()); 271 324 var rpList = JSON.parse(userProperties.getProperty("rpList")); 272 325 var idx = 0 273 326 for (const rpInfo of rpList) { ··· 289 342 rpList.splice(idx, 1); 290 343 gasLog(rpList.length) 291 344 userProperties.setProperty("rpList", JSON.stringify(rpList)); 345 + } 346 + 347 + const getLocalHour = (date) => { 348 + //https://stackoverflow.com/a/74683660/3809427 349 + const offset = Number(Intl.DateTimeFormat("ia", { 350 + timeZoneName: "shortOffset", 351 + //https://stackoverflow.com/a/24764307/3809427 352 + timeZone: CalendarApp.getDefaultCalendar().getTimeZone() 353 + }) 354 + .formatToParts() 355 + .find((i) => i.type === "timeZoneName").value // => "GMT+/-hh:mm" 356 + .slice(3)); 357 + 358 + return (date.getUTCHours() + offset + 24) % 24; 292 359 } 293 360 294 361 const gasLog = (obj) => {
+4 -2
confirm.html
··· 9 9 10 10 <body> 11 11 <div class="container" style="max-width: 600px;"> 12 - <h2 class="text-center m-4">以下の時刻でセルフRPします</h2> 12 + <h2 class="text-center m-4"> 13 + <?= message ?> 14 + </h2> 13 15 <form class="mb-5" method="POST" action="<?= deployURL ?>"> 14 16 <div class="input-group mb-3"> 15 17 <span class="input-group-text" id="basic-addon1">@ ··· 17 19 </span> 18 20 </div> 19 21 20 - <p class="mb-3">ポストURL: 22 + <p class="mb-3">URL: 21 23 <?= postUrl ?> 22 24 </p> 23 25 <? output._ = listHtml ?>
+21 -14
index.html
··· 5 5 <base target="_top"> 6 6 <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" 7 7 integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM" crossorigin="anonymous"> 8 - <!-- <meta property="og:url" content="https://lamrongol.github.io/gakumas_auto_calculator/" /> --> 9 - <meta property="og:type" content="website"> 10 - <meta property="og:title" content="SkySpread - 自動化セルフRPで宣伝!"> 11 - <meta property="og:description" content="イラストなどを投稿した自分のポストを異なる時間帯(昼や深夜など)にセルフRPして宣伝できるツールです"> 12 8 <!-- <meta property="og:image" content="thumb.jpg" /> --> 13 9 <!-- <meta name="twitter:card" content="summary"> --> 14 10 <meta name="twitter:site" content="@lamrongol" /> ··· 16 12 17 13 <body> 18 14 <div class="container" style="max-width: 600px;"> 19 - <h2 class="text-center m-4">自動化セルフRPで宣伝!</h2> 20 - <p>自分のポストを異なる時間帯(昼や深夜など)にセルフRPして宣伝できるツールです。</p> 15 + <h2 class="text-center m-4"> 16 + <?= title ?> 17 + </h2> 18 + <p> 19 + <?= description ?> 20 + </p> 21 21 <form class="mb-5" method="POST" action="<?= deployURL ?>"> 22 22 <div class="input-group mb-3"> 23 23 <span class="input-group-text" id="basic-addon1">@</span> ··· 25 25 value="<?= username ?>" required> 26 26 </div> 27 27 <div class="input-group mb-3"> 28 - <input type="password" class="form-control" placeholder="xxxx-xxxx-xxxx-xxxx(アプリパスワード)" name="appPassword" 29 - aria-describedby="appPasswordHelp" value="<?= appPassword ?>" required> 30 - <div id="appPasswordHelp" class="form-text">※Blueskyのパスワードでは<a 31 - href="https://scrapbox.io/Bluesky/Bluesky%E3%82%92%E5%A7%8B%E3%82%81%E3%82%8B%E4%B8%8A%E3%81%A7%E3%81%93%E3%82%8C%E3%81%A0%E3%81%91%E3%81%AF%E7%9F%A5%E3%81%A3%E3%81%A6%E3%81%8A%E3%81%84%E3%81%9F%E6%96%B9%E3%81%8C%E3%81%84%E3%81%84%E3%81%93%E3%81%A8">ありません</a> 28 + <input type="password" class="form-control" placeholder="xxxx-xxxx-xxxx-xxxx(<?= appPassword ?>)" 29 + name="appPassword" aria-describedby="appPasswordHelp" value="<?= appPassword ?>" required> 30 + <div id="appPasswordHelp" class="form-text"> 31 + <? output.append(appPasswordDescription) ?> 32 32 </div> 33 33 </div> 34 34 35 - <p class="mb-3">宣伝したいポストのURL:</p> 35 + <p class="mb-3"> 36 + <?= postUrlDescription ?>: 37 + </p> 36 38 <div class="input-group mb-3"> 37 39 <input type="text" class="form-control" name="postUrl" 38 40 placeholder="https://bsky.app/profile/user-name.bsky.social/post/xxxxxxxxxxx" required> 39 41 </div> 40 - <button type="submit" class="btn btn-outline-primary" name="confirm" value="true">セルフRPを設定</button> 42 + <button type="submit" class="btn btn-outline-primary" name="confirm" value="true"> 43 + <?= setSelfRP ?> 44 + </button> 41 45 <p class="mb-3"> 42 46 <strong> 43 47 <?= timerError ?> 44 48 </strong> 45 49 </p> 46 50 <p> 47 - 質問・要望などあれば<a href="https://bsky.app/profile/did:plc:wwqlk2n45es2ywkwrf4dwsr2">開発者(@lamrongol)</a>にお気軽に問い合わせください。 51 + <? output.append(support) ?> 48 52 </p> 49 53 <p> 50 - <a href="https://tangled.org/did:plc:wwqlk2n45es2ywkwrf4dwsr2/SkySpread">ソースコード</a> 54 + <a href="https://tangled.org/did:plc:wwqlk2n45es2ywkwrf4dwsr2/SkySpread">Source Code</a> 51 55 </p> 56 + <script> 57 + 58 + </script> 52 59 </body> 53 60 54 61 </html>