LoginSignup
1

More than 1 year has passed since last update.

Alfred WorkflowでSlack投稿いろいろ

Last updated at Posted at 2022-05-15

TL;DR

  • Slackのあるチャンネルに任意の文字列を投稿する。
    image.png

  • 投稿するチャンネルを選ぶ
    image.png

  • 投稿する時間を選ぶ
    image.png

  • あるチャンネルの特定のスレッドに返信する
    image.png

  • 絵文字を自動でつける
    画面収録-2022-05-15-13.45.03.gif

  • 匿名で投稿する
    2022-07-09-14-46-14.gif

How to install

https://github.com/kentoak/slack_post_alfred/blob/main/slack_post.alfredworkflow
のView raw をクリック
image.png

Slack APIを準備

https://api.slack.com/apps/ でBotをつくる。

OAuth & PermissionsのBot Token Scopesは以下
image.png
BotではなくWorkspace(自分)が投稿したいならUser Token Scopesの方で。

Reinstall to Workspace して、Tokenをメモ
image.png

メモしたトークンは、Sourceの{YOUR TOKEN}に直書きするか、[x]のところで設定する(後述)。

※追記 匿名投稿するには、chat:write.customizehttps://api.slack.com/scopes/chat:write.customize) が必要

Source

image.png

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の
image.png
[x]のところで設定するか、直に書き換える。

image.png

特定の文字列が含まれる投稿に返信する

使用法: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}&timestamp=${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で書いたほうが扱いやすいかも。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1