TL;DR
How to install
https://github.com/kentoak/slack_post_alfred/blob/main/slack_post.alfredworkflow
のView raw をクリック
Slack APIを準備
https://api.slack.com/apps/ でBotをつくる。
OAuth & PermissionsのBot Token Scopes
は以下
BotではなくWorkspace(自分)が投稿したいならUser Token Scopes
の方で。
Reinstall to Workspace して、Tokenをメモ
メモしたトークンは、Sourceの{YOUR TOKEN}に直書きするか、[x]のところで設定する(後述)。
※追記 匿名投稿するには、chat:write.customize
(https://api.slack.com/scopes/chat:write.customize) が必要
Source
Slack チャンネル情報を取得する Script Filter
import alfy from 'alfy';
const api = {YOUR TOKEN}
const token = process.env.TOKEN || api
const fetch_all_channels_url = "https://slack.com/api/conversations.list";
var obj=[];
const message = alfy.input;
(async() => {
try {
const response = await alfy.fetch(
fetch_all_channels_url,
{
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
}
)
for(let i=0; i<response.channels.length; i++) {
if (response.channels[i].is_archived || response.channels[i].num_members == 0){
continue
}
obj.push({
title: response.channels[i].name,
subtitle: response.channels[i].id,
icon: {path: './img/main04.jpg'},
arg: message+"%"+response.channels[i].id+"$"+response.channels[i].name
});
}
alfy.output(obj);
}
catch {
console.log("チャンネル情報取得に失敗しました!!!!")
}
})();
投稿時間を指定する Script Filter
import alfy from 'alfy';
var obj = []
obj.push({
title: "10秒後",
subtitle: 10,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"10"
});
obj.push({
title: "30秒後",
subtitle: 30,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"30"
});
obj.push({
title: "1分後",
subtitle: 60,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"60"
});
obj.push({
title: "3分後",
subtitle: 180,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"180"
});
obj.push({
title: "5分後",
subtitle: 300,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"300"
});
obj.push({
title: "10分後",
subtitle: 600,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"600"
});
obj.push({
title: "30分後",
subtitle: 1800,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"1800"
});
obj.push({
title: "60分後",
subtitle: 3600,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"3600"
});
obj.push({
title: "3時間後",
subtitle: 10800,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"10800"
});
obj.push({
title: "5時間後",
subtitle: 3600*5,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"18000"
});
obj.push({
title: "24時間後",
subtitle: 3600*24,
icon: {path: './img/main03.jpg'},
arg: alfy.input+"&"+"86400"
});
alfy.output(obj);
指定したチャンネルに指定した時間に投稿する
使用法:alfredの検索窓で、ttt (投稿内容)
enter (チャンネルを選ぶ)
enter (時刻を選ぶ)
なぜか https://slack.com/api/chat.scheduleMessage を使うときにcurl
のヘッダー 'Content-Type: application/json; charset=utf-8'
と、--data-binary
が必要らしく、--data-binary
がalfyになさそう?なので必要なものを書き出して最後にRun Scriptでbashコマンドを実行することで対応した。
import alfy from 'alfy';
const api = {YOUR TOKEN}
const token = process.env.TOKEN || api
const mytext = alfy.input.substring(0,alfy.input.indexOf('%'));
const channel = alfy.input.substring(alfy.input.indexOf('%')+1,alfy.input.indexOf('$'));
const channelName = alfy.input.substring(alfy.input.indexOf('$')+1,alfy.input.indexOf('&'));
const timeStamp = Number(alfy.input.substring(alfy.input.indexOf('&')+1,));
(async() => {
try {
const now0 = new Date()
const targetDate0 = new Date(now0.getFullYear(), now0.getMonth(), now0.getDate(), now0.getHours(), now0.getMinutes(), now0.getSeconds());
const targetUnixTimestanp0 = Math.floor(targetDate0.getTime() / 1000)+timeStamp
var obj = {"channel":channel,"post_at":targetUnixTimestanp0,"text":mytext}
console.log(obj)
}
catch {
console.log("投稿失敗しました.......")
}
})();
Run Scriptにて。
curl -POST 'https://slack.com/api/chat.scheduleMessage' -H 'Authorization: Bearer ${TOKEN}' -H 'Content-Type: application/json; charset=utf-8' --data-binary "$1"
ここのTOKEN
は Workflowの
[x]のところで設定するか、直に書き換える。
特定の文字列が含まれる投稿に返信する
使用法:alfredの検索窓で、sl (返信内容)
を打つ。
reminderという投稿が今日されていたとき、その最新の投稿に、日報のようなものを投稿する。(参考 https://zenn.dev/tomsan96/articles/0f96954f831f97 )
import alfy from 'alfy';
const api = {YOUR TOKEN}
const token = process.env.TOKEN || api
const channel = process.env.CHANNEL || {YOUR CHANNEL};
const word = process.env.WORD || "reminder";
const mytext = process.argv[2];
const name = process.env.URL || "from Taoka ";
const now = new Date()
// 当日0時をターゲット日時に設定(ミリ秒)
const targetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0);//100件以降はキャッチしない
//const targetDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 23, 23, 0);//new Date(year, month[, day, hour, minutes, seconds, milliseconds]);
// Unix時間(秒単位)に変換
const targetUnixTimestanp = Math.floor(targetDate.getTime() / 1000)
// oldestパラメータ:メッセージ履歴の取得開始時期を指定
const getHistoryUrl = `https://slack.com/api/conversations.history?channel=${channel}&oldest=${targetUnixTimestanp}`;
const postUrl = "https://slack.com/api/chat.postMessage";
var tsList=[];
(async() => {
try {
// 該当チャンネル内のその日に投稿されるメッセージ履歴を取得する(api: conversations.history)
const response = await alfy.fetch(
getHistoryUrl,
{
headers: {
Authorization: `Bearer ${token}`
},
}
)
// 取得したメッセージのうち、文言の一致を条件に日報報告用のメッセージと判定する
for(let i=0; i<response.messages.length; i++) {
if (response.messages[i].text==word){
tsList.push(response.messages[i].ts)
}
}
await alfy.fetch(
postUrl,
{
method: "POST",
headers: {
Authorization: `Bearer ${token}`
},
json: {
channel: channel,
thread_ts: tsList[0],
text: `日報です${name}`+"\n"+mytext
}
}
)
console.log("%sを#apiに投稿成功!!!",`日報です${name}`+mytext)
}
catch {
console.log("投稿失敗しました.....")
}
})();
emojiリアクションする
tsが必要。今回は、"remainder"と投稿された時刻。
var reaction_emojis = [
"thinking_face",
"thumbsup",
"gorilla",
"grin",
"kissing_smiling_eyes",
"sweat_smile",
"blue_heart",
"point_right",
"princess",
"microbe",
"parrot",
"pleading_face",
"turkey",
"cook",
"woman_in_lotus_position",
"merperson",
"peanuts",
"broccoli"
];
for(let i=0; i<reaction_emojis.length; i++) {
var myurl = `https://slack.com/api/reactions.add?channel=${channel}×tamp=${tsList[0]}&name=${reaction_emojis[i]}`;
await alfy.fetch(
myurl,{
method: "POST",
headers: {
Authorization : `Bearer ${token}`
}
}
)
}
参考(GASでやったやつ)
c.f. 絵文字一覧はこれ
https://qiita.com/yamadashy/items/ae673f2bae8f1525b6af
匿名で投稿する
使用法:alfredの検索窓で、tokumei (名前) (投稿内容)
を打つ。
アイコンはランダムで選ばれる。
import alfy from 'alfy';
var emojis = [
"thinking_face",
"thumbsup",
"gorilla",
"grin",
"kissing_smiling_eyes",
"sweat_smile",
"blue_heart",
"point_right",
"princess",
"microbe",
"parrot",
"pleading_face",
"turkey",
"cook",
"woman_in_lotus_position",
"merperson",
"peanuts",
"broccoli",
"sunglasses"
];
var config = {
slack: {
token: {YOUR TOKEN},
iconEmoji: emojis[Math.floor(Math.random()*emojis.length+1)],
},
channel: {YOUR CHANNEL},
};
var input = process.argv[2];
var name = input.split(" ")[0];
var text = input.split(" ")[1];
if (input.split(" ")[2]==1){
text = fujiwarify(text);
}
// console.log("name is ",name)
// console.log("text is ",text)
// console.log(config.slack.token)
// console.log(config.channel)
// console.log(config.slack.iconEmoji)
var url = 'https://slack.com/api/chat.postMessage';
var payload = {
channel: config.channel,
text: text,
username: makeHandle(name),
// attachments: [{
// text: '```' + text + '```',
// color: "good",
// mrkdwn_in: 'text'
// }],
icon_emoji: config.slack.iconEmoji
};
(async() => {
try {
await alfy.fetch(
url,
{
method: "POST",
headers: {
Authorization: `Bearer ${config.slack.token}`
},
json: payload
}
)
console.log("投稿成功!")
} catch {
console.log("投稿失敗しました!!!!")
}
})();
function stringToArray (str) {
return str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]|[^\uD800-\uDFFF]/g) || [];
}
function fujiwarify(s) {
s = s.replace(/(ください|下さい|ます|です|たい|う|た|よ)([。!!]|$)/g, function (s, a, b) {
if (a === 'よ') {
return a + 'ぉぉぉぉ' + b;
} else {
return a + 'よぉぉぉぉ' + b;
}
});
var chars = stringToArray(s);
var result = '';
chars.forEach(function (c) {
result += c + '゛';
});
return result;
}
function makeHandle (nickname) {
return nickname.replace(/◆/g, '◇')
.replace(/(#[\x20-\x7e]{1,8})$/, function (s, a) {
var salt = 'long long text here.'
var sha1 = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, salt + a);
var sha1text = Utilities.base64Encode(sha1);
//console.log('◆' + sha1text.substring(0,10))
return '◆' + sha1text.substring(0,10);
});
}
スニペットで、tokumei (名前) (投稿内容) (1)
とすれば、投稿内容が藤原竜也風になる。
参考: https://techblog.securesky-tech.com/entry/2020/01/31/
おわり
今回はAlfred Workflowを使ってみた。JavaScriptでcurlコマンドを使うために、alfy
なる(Alfred workflow を作るための)JavaScript フレームワークを使用した。 Pythonで書いたほうが扱いやすいかも。