Help us understand the problem. What is going on with this article?

Slack + Electron + Socket.ioを使ってニコニコ動画風にメッセージを表示する

More than 1 year has passed since last update.

nicotv.gif

はじめに

Slack APIを使ってみたく、ただメッセージを表示するだけではつまらないので、
ニコニコ動画風にメッセージを表示するアプリケーションを作成してみました。

構成

  • Slack:Slack Events APIを使って、メッセージ受信時にサーバーに通知
  • サーバー(EC2):Events APIを受けっとって、Electronアプリにメッセージを送信
  • Electron:Socket.ioでサーバーからメッセージを受け取りニコニコ動画風に描画 image.png

必要なもの

  • PC
  • サーバー(EC2)
  • Slackのアカウント

参考にしたサイト

作り方

サーバー(EC2)

Node.jsのExpressを使ってWebサーバーを立ち上げる。
Electronアプリとの接続用にSocket.io通信も設定する。
ここではポート3000番に起動しているので、必要に応じて変更する。

app.js
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const PORT = process.env.PORT || 3000;

const bodyParser = require('body-parser');

app.use(bodyParser.json());

io.on('connection', function (socket) {
    socket.on('message', function (msg) {
        io.emit('message', msg);
    });
});

app.post('/slack', function (req, res) {

    const { type, event } = req.body;

    if (type === 'challenge') {

    } else if (type === 'event_callback') {
        io.emit('message', event.txt);
    }

    res.status(200).json(req.body);

});

http.listen(PORT, function () {
    console.log('server listening. Port:' + PORT);
});
npm i -S http express socket.io body-parser
node app.js & #バックグラウンドで実行

Slackの設定

  • https://api.slack.com/apps
  • Create New Apps image.png
  • App Name:任意(Nicotv)
  • Development Slack Workspace:ワークスペースを指定
  • [Create App] image.png
  • 左下の[Event Subscriptions]から
  • Request URLに先ほど設定したサーバーのURLを設定 image.png
  • Bot Eventsを下記のように設定 image.png
  • 右下の[Save Changes]
  • [Install App]から手順に従いSlackにアプリをインストール

Electronアプリの作成

超シンプルなHTMLファイル

index.html
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title>Nico TV</title>
    <script src="https://xxx.xxx.com/socket.io/socket.io.js"></script>
    <script>
        window.jQuery = window.$ = require('./lib/jquery-3.3.1.min.js');
    </script>
    <link href="./css/style.css" rel="stylesheet">
</head>

<body>

    <div class="msgbox"> </div>

    <script>
        require('./renderer.js')
    </script>

</body>

</html>

Socket.ioに接続し、メッセージを受け取ったらニコニコ風に描画

renderer.js
const colors = [
    'lime',
    'aqua',
    'yellow',
    'red',
    'fuchsia'
];

const colorsLen = colors.length;

const layerCnt = 6;

const winWidth = window.innerWidth;
const socketio = io('https://xxx.xxx.com');

const layerIds = [];

const $msgbox = $('.msgbox');

for (let i = 0; i < layerCnt; i++) {
    $('<div>', { class: 'layer' }).appendTo($msgbox);
    layerIds.push(0);
}

const $layers = $('.layer');

let msgId_ = 1;

socketio.on('message', function (msg) {

    let msgId = msgId_++;

    let minId = msgId;
    for (let layerId of layerIds) {
        if (layerId < minId) {
            minId = layerId;
        }
    }

    let index = layerIds.findIndex((id) => id === minId);

    layerIds[index] = msgId;

    const $layer = $layers.eq(index);
    const $msg = $('<div>', { class: 'msg', text: msg })
        .css('color', colors[Math.floor(Math.random() * colorsLen)])
        .appendTo($layer);

    let right = -$msg.width();

    let intervalId = setInterval(function () {

        right += 1;
        $msg.show().css('right', right);

        if (right > winWidth) {

            $msg.remove();

            if (layerIds[index] === msgId) {
                layerIds[index] = 0;
            }

            clearInterval(intervalId);

        }

    });

});
style.css
html {
    height: 100%;
    width: 100%;
}

body {
    margin: 0;
    height: 100%;
    width: 100%;
    background-color: rgba(0, 0, 0, 0);
    overflow: hidden;
}

.msgbox {
    font-size: 128px;
}

.layer {
    position: relative;
    width: 100%;
    height: 128px;
    margin: 8px 0;
}

.msg {
    position: absolute;
    display: none;
    right: 0;
    white-space: nowrap;
    font-weight: 700;
}

Electronアプリを最大化、フレームなし、透明化、リサイズ不可、常に最前面で起動する。

main.js
// Modules to control application life and create native browser window
const electron = require('electron');
const { app, BrowserWindow } = electron;

// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainWindow;

function createWindow() {

    // const screen = require('screen');
    const { screen } = electron;
    let size = screen.getPrimaryDisplay().size;

    // Create the browser window.
    mainWindow = new BrowserWindow({
        left: 0,
        top: 0,
        width: size.width,
        height: size.height,
        frame: false,
        show: true,
        transparent: true,
        resizable: false,
        alwaysOnTop: true
    });

    mainWindow.setIgnoreMouseEvents(true);
    mainWindow.maximize();

    // and load the index.html of the app.
    mainWindow.loadFile('index.html');

    // Open the DevTools.
    // mainWindow.webContents.openDevTools()

    // Emitted when the window is closed.
    mainWindow.on('closed', function () {
        // Dereference the window object, usually you would store windows
        // in an array if your app supports multi windows, this is the time
        // when you should delete the corresponding element.
        mainWindow = null;
    });

}

// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow);

// Quit when all windows are closed.
app.on('window-all-closed', function () {
    // On OS X it is common for applications and their menu bar
    // to stay active until the user quits explicitly with Cmd + Q
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', function () {
    // On OS X it's common to re-create a window in the app when the
    // dock icon is clicked and there are no other windows open.
    if (mainWindow === null) {
        createWindow();
    }
});

// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

electron-packagerを使ってExeファイルを作成する。

electron-packager . nicotv --platform=win32 --arch=x64 --asar

使い方

  • メッセージを表示したいPCでElectronアプリを起動
  • SlackでNicotv宛てにダイレクトメッセージを送ると、Electronアプリ上でメッセージが表示される

さいごに

Slack連携のアプリケーションが意外に簡単に作れました。
SlackのBot Eventsを変更することで、チャネルの投稿をトリガーにメッセージを表示するなどということも可能です。
Electronアプリは最前面、背景透明で起動しているので、他のアプリケーションと合わせて実行することが可能です。
弊社ではこのアプリを全員が見えるところに配置した巨大なモニタに映し出して使用しています。

valuesccg
「インターネット行動ログ分析サービス」など、デジタルマーケティング領域での新たな価値創造を通し、各企業の成長支援を行っています。エンジニア募集中。
https://www.valuesccg.com/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした