74
67

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 5 years have passed since last update.

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

Posted at

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アプリは最前面、背景透明で起動しているので、他のアプリケーションと合わせて実行することが可能です。
弊社ではこのアプリを全員が見えるところに配置した巨大なモニタに映し出して使用しています。

74
67
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
74
67

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?