+8
-3
README.md
+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
+2
-2
appsscript.json
+79
-12
code.js
+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
+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
+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>