0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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で書いたほうが扱いやすいかも。

0
1
0

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
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?