【Python】AdminLTEとWebSocketでチャット機能を作ってみる その1(とりあえず版)

2018年9月29日Python,開発

おはようございます。

昨日に引き続き、チャット機能の実装をしていきます。

予定通り、今回は Python の Tornado を使って
実際にメッセージのやり取りができるような仕組みを実装します。

スポンサーリンク

フォルダ構成

新規で Pythonプロジェクトを作成し、先日のサンプルを移植、処理を実装していきます。

SampleChat

│  Main.py
├─static
│ ├─css
│ │ │ AdminLTE.css
│ │ │ AdminLTE.min.css
│ │ │ bootstrap.min.css
│ │ │ font-awesome.min.css
│ │ │ style.css
│ │ │
│ │ └─skins
│ │    skin-blue.css
│ │    skin-blue.min.css
│ │
│ ├─img
│ │ konatsu.jpg
│ │ koume.jpg
│ │ riku.jpg
│ │ sora.jpg
│ │ umi.jpg
│ │
│ └─js
│    adminlte.min.js
│    bootstrap.min.js
│    jquery-ui.min.js
│    jquery.min.js
│    moment.min.js
│    script.js

└─templates
main.html

画面

メイン画面

main.html

CSSやJavascript、イメージなどのパスの指定と、微妙に修正した箇所があります。

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
	<meta charset="utf-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta http-equiv="content-type" content="text/html; charset=UTF-8">
	<title>チャットサンプル</title>
	<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" name="viewport">
	<link rel="stylesheet" href="{{ static_url('css/bootstrap.min.css') }}">
	<link rel="stylesheet" href="{{ static_url('css/font-awesome.min.css') }}">
	<link rel="stylesheet" href="https:////maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
	<link rel="stylesheet" href="{{ static_url('css/AdminLTE.min.css') }}">
	<link rel="stylesheet" href="{{ static_url('css/style.css') }}">
	<link rel="stylesheet" href="{{ static_url('css/skins/skin-blue.min.css') }}">
	<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700,300italic,400italic,600italic">
</head>
<body class="hold-transition fixed">
	<section class="content container-fluid">
		<div class="row">
			<!-- Left col -->
			<div class="col-xs-8">
				<!-- /.box -->
				<div class="row">
					<div class="col-xs-8">
						<!-- DIRECT CHAT -->
						<div id="chat-panel" class="box box-warning direct-chat direct-chat-warning box-solid" style="display:none;">
							<div class="box-header with-border">
								<h3 class="box-title">チャットメッセージ</h3>
								<div class="box-tools pull-right">
									<span id="status" class="status"></span>
									<span data-toggle="tooltip" title="3 New Messages" class="badge bg-yellow">3</span>
									<button type="button" class="btn btn-box-tool" data-widget="collapse"><i class="fa fa-minus"></i></button>
									<button type="button" class="btn btn-box-tool" data-toggle="tooltip" title="Contacts" data-widget="chat-pane-toggle">
										<i class="fa fa-comments"></i></button>
									<button type="button" class="btn btn-box-tool" data-widget="remove"><i class="fa fa-times"></i></button>
								</div>
							</div>
							<!-- /.box-header -->
							<div class="box-body">
								<!-- Conversations are loaded here -->
								<div class="direct-chat-messages">
									<!-- Message. Default to the left -->
									<div class="direct-chat-msg">
										<div class="direct-chat-info clearfix">
											<span class="direct-chat-name pull-left">こなつ</span>
											<span class="direct-chat-timestamp pull-right">2018/09/25(月) 02:00</span>
										</div>
										<!-- /.direct-chat-info -->
										<img class="direct-chat-img" src="static/img/konatsu.jpg" alt="message user image">
										<!-- /.direct-chat-img -->
										<div class="direct-chat-text">
											そら最近どうしてる?
										</div>
										<!-- /.direct-chat-text -->
									</div>
									<!-- /.direct-chat-msg -->
									<!-- Message to the right -->
									<div class="direct-chat-msg right">
										<div class="direct-chat-info clearfix">
											<span class="direct-chat-name pull-right">そら</span>
											<span class="direct-chat-timestamp pull-left">2018/09/25(月) 02:05</span>
										</div>
										<!-- /.direct-chat-info -->
										<img class="direct-chat-img" src="static/img/sora.jpg" alt="message user image">
										<!-- /.direct-chat-img -->
										<div class="direct-chat-text">
											相変わらずだよ。<BR>
											あいつらの面倒で手一杯でさ。
										</div>
										<!-- /.direct-chat-text -->
									</div>
									<!-- /.direct-chat-msg -->
									<!-- Message. Default to the left -->
									<div class="direct-chat-msg">
										<div class="direct-chat-info clearfix">
											<span class="direct-chat-name pull-left">こなつ</span>
											<span class="direct-chat-timestamp pull-right">2018/09/25(月) 05:37</span>
										</div>
										<!-- /.direct-chat-info -->
										<img class="direct-chat-img" src="static/img/konatsu.jpg" alt="message user image">
										<!-- /.direct-chat-img -->
										<div class="direct-chat-text">
											一番のお兄さんだから大変ね。<BR>
											私は一人で快適な暮らしを送っているわ(^^♪
										</div>
										<!-- /.direct-chat-text -->
									</div>
									<!-- /.direct-chat-msg -->
									<!-- Message to the right -->
									<div class="direct-chat-msg right">
										<div class="direct-chat-info clearfix">
											<span class="direct-chat-name pull-right">そら</span>
											<span class="direct-chat-timestamp pull-left">2018/09/25(月) 06:10</span>
										</div>
										<!-- /.direct-chat-info -->
										<img class="direct-chat-img" src="static/img/sora.jpg" alt="message user image">
										<!-- /.direct-chat-img -->
										<div class="direct-chat-text">
											え、なにそれ自慢ですか?
										</div>
										<!-- /.direct-chat-text -->
									</div>
									<!-- /.direct-chat-msg -->
								</div>
								<!--/.direct-chat-messages-->
								<!-- Contacts are loaded here -->
								<div class="direct-chat-contacts">
									<ul class="contacts-list">
										<li>
											<a href="#">
												<img class="contacts-list-img" src="static/img/konatsu.jpg" alt="User Image">
												<div class="contacts-list-info">
													<span class="contacts-list-name">
														こなつ
														<small class="contacts-list-date pull-right">2018/09/25(月)</small>
													</span>
													<span class="contacts-list-msg">早く新しい家に引っ越ししたい。</span>
												</div>
												<!-- /.contacts-list-info -->
											</a>
										</li>
										<li>
											<a href="#">
												<img class="contacts-list-img" src="static/img/umi.jpg" alt="User Image">
												<div class="contacts-list-info">
													<span class="contacts-list-name">
														うみ
														<small class="contacts-list-date pull-right">2018/09/25(月)</small>
													</span>
													<span class="contacts-list-msg">誰かブラッシングしてくれないかしら。</span>
												</div>
												<!-- /.contacts-list-info -->
											</a>
										</li>
										<li>
											<a href="#">
												<img class="contacts-list-img" src="static/img/koume.jpg" alt="User Image">
												<div class="contacts-list-info">
													<span class="contacts-list-name">
														こうめ
														<small class="contacts-list-date pull-right">2018/09/24(日)</small>
													</span>
													<span class="contacts-list-msg">ちゅーるちゅーるちゃおちゅーるー</span>
												</div>
												<!-- /.contacts-list-info -->
											</a>
										</li>
										<li>
											<a href="#">
												<img class="contacts-list-img" src="static/img/riku.jpg" alt="User Image">
												<div class="contacts-list-info">
													<span class="contacts-list-name">
														りく
														<small class="contacts-list-date pull-right">2018/09/12(水)</small>
													</span>
													<span class="contacts-list-msg">ごはんまだ?</span>
												</div>
												<!-- /.contacts-list-info -->
											</a>
										</li>
										<!-- End Contact Item -->
									</ul>
									<!-- /.contatcts-list -->
								</div>
								<!-- /.direct-chat-pane -->
							</div>
							<!-- /.box-body -->
							<div class="box-footer">
								<form action="#" method="post">
									<div class="input-group">
										<input id="message" type="text" name="message" placeholder="Type Message ..." class="form-control">
										<span class="input-group-btn">
											<button id="sendButton" type="button" class="btn btn-warning btn-flat">Send</button>
										</span>
									</div>
								</form>
							</div>
							<!-- /.box-footer-->
						</div>
						<!--/.direct-chat -->
					</div>
					<!-- /.col -->
				</div>
				<!-- /.col -->
			</div>
		</div>
	</section>
	<script src="{{ static_url('js/jquery.min.js') }}"></script>
	<script src="{{ static_url('js/jquery-ui.min.js') }}"></script>
	<script src="{{ static_url('js/bootstrap.min.js') }}"></script>
	<script src="{{ static_url('js/adminlte.min.js') }}"></script>
	<script src="{{ static_url('js/moment.min.js') }}"></script>
	<script src="{{ static_url('js/script.js') }}"></script>
	<script>
		$(document).ready( function () {
			initialize();
		} );
	</script>
	</body>
</html>

 

スタイル

style.css

.form-control {
    ime-mode: active;
}

#status {
    font-size: 10px;
}

 

プログラム

Tornadoを利用しています。
利用方法は次の記事なんかを参考にしていただければ。

【Python】スマホで読み込むと Wi-Fi に繋げられるQRコードを生成してみる

サーバー側

Main.py

# --- coding: utf-8 ---
"""
チャットサンプル
"""

import os
import signal
import logging
import json

import tornado.web
import tornado.ioloop
import tornado.websocket

from tornado.options import options
from tornado.websocket import WebSocketHandler

client = []

class MainHandler(tornado.web.RequestHandler):
    """
    初期表示処理
    """

    def initialize(self):
        logging.info("[MainHandler] initialize")

    def get(self):
        logging.info("[MainHandler] get")

        self.render("main.html")


class ChatHandler(WebSocketHandler):
    """
    チャット処理
    """

    def open(self):
        logging.info("[ChatHandler] open")

        if self not in client:
            client.append(self)

    def on_message(self, message):
        logging.info("[ChatHandler] on_message : " + message)

        for cl in client:
            cl.write_message(message)

    def on_close(self):
        logging.info("[ChatHandler] on_close")

        if self in client:
            client.remove(self)


application = tornado.web.Application([
    (r"/", MainHandler),
    (r"/chat", ChatHandler),
    ],
    template_path=os.path.join(os.getcwd(),  "templates"),
    static_path=os.path.join(os.getcwd(),  "static")
)

if __name__ == "__main__":
    tornado.options.parse_command_line()
    application.listen(8888)
    logging.info("server started")
    tornado.ioloop.IOLoop.instance().start()

 

クライアント側(Javascript)

script.js

// ソケット
var socket = new WebSocket('ws://' + location.host + '/chat');
moment.lang('ja', {
    weekdays: ["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],
    weekdaysShort: ["日","月","火","水","木","金","土"],
});

/**
 * 初期処理.
 */
function initialize() {

    // 通信ソケットオープン
    socket.onopen = function(data) {
        $("#status").css("color", "#FFFFFF");
        $("#status").text(" [オンライン]");
    }

    // 通信ソケットクローズ
    socket.onclose = function() {
        $("#status").css("color", "#999999");
        $("#status").text(" [オフライン]")
    }

    // メッセージ受信
    socket.onmessage = function(e) {
        console.log(e.data);
        var msg = e.data;

        var tag = createMessage("そら", msg.replace(/[\"]/g,""));
        $(".direct-chat-messages").append(tag);
        $(".direct-chat-messages").animate({
            scrollTop: $(".direct-chat-messages")[0].scrollHeight
        }, 500);
    }

    // ボタンにイベントを追加
    $("#sendButton").click(function () {
        sendMessage();
    });

    // チャットの表示を一番下に
    $("#chat-panel").show();
    $(".direct-chat-messages")[0].scrollTop = $(".direct-chat-messages")[0].scrollHeight;
}

/**
 * メッセージを送信.
 */
function sendMessage() {
    var msg = $("#message").val();
    if (msg != ""){
        socket.send(JSON.stringify(msg));
    }
}

/**
 * メッセージタグを作成して返します.
 */
function createMessage(userName, message) {

    var msgDiv = $("<div>", {
        "class" : "direct-chat-text"
        , "text" : message
    });

    var img = $("<img>", {
        "class" : "direct-chat-img"
        , "src" : "static/img/sora.jpg"
    });

    // とりあえず現在時刻を表示(本来は送信時刻)
    var now = new moment();
    var date = now.format("YYYY/MM/DD(ddd) hh:mm")
    var dt = $("<span>", {
        "class" : "direct-chat-timestamp pull-left"
        , "text" : date
    });

    var name = $("<span>", {
        "class" : "direct-chat-name pull-right"
        , "text" : userName
    });

    var info = $("<div>", {
        "class" : "direct-chat-info clearfix"
    });

    var chatMsg = $("<div>", {
        "class" : "direct-chat-msg right"
    });

    // タグを作成していく
    info.append(name);
    info.append(dt);

    chatMsg.append(info);
    chatMsg.append(img);
    chatMsg.append(msgDiv);

    return chatMsg;
}

 

起動してみる

初期表示
メッセージ送信

送信したメッセージが無事に戻ってきました。

まとめ

ひとまず簡単に、
Websocketを使った処理が実装できました。

次回はユーザー毎の制御でも実装していきましょうか。
それにはログイン機能が必要ですね。

ではでは。

スポンサーリンク


関連するコンテンツ

2018年9月29日Python,開発AdminLTE,CSSフレームワーク,Python,Tornado,WebSocket,プログラミング

Posted by doradora