久々のブログ更新。書きたいときに書くというスタンスです。最近、会社でも技術ブログを立ち上げたので、そちらで書いても良いかも。
今回はIRCボットの作成について書いてみる。しばらく前から社内IRCで技術関連の雑談用チャンネルを利用している。技術系のメーリングリストもあるのだけど、わざわざメールで話題にするようなことでもない、軽い内容を気軽に議論できる場が欲しかったので有志で立ち上げた。IT企業にとって技術的な雑談をできる場はとても大事だと思う。
素のIRCの場合、発言内容がサーバに保存されず、途中から参加しても話題についていけないという問題が出たので、適当なボットで対応することにしたが、やはりエンジニアがメインの会社なのだからボットも自作が基本でしょ、ということでPythonで作ってみた。まあ、作ったといっても「Python でシンプルな IRC クライアントを作成する」のサイトを参考にさせてもらったのでスクラッチから作成したわけではない。
コマンド techlog: を入れると、その日の発言がボットからのtalkで返される。ただ、最初は1日分の発言しか取れない仕様だったので、日を跨ぐとその前の発言が取得できないし、一時的にチャットを抜けた分のログが欲しいだけでも強制的に1日分の発言を取得してしまう。
そこで、SQLite3を使ってユーザ毎にログイン・ログアウト時刻を管理して、ログアウトしてからログインするまでの発言を取得できるように変更することにした。新規に参加したメンバーでも自動的にデータベースに登録される。せっかくユーザの時刻情報があるのだから、techtime:, techtimeall: というコマンドを作って、それぞれ自分の滞在時間、ユーザ全員分の滞在時間も取得できるようにしてみた。
IRCサーバ上で以下のコマンドを実行すると、IRCにボットが常駐する。
$ ./irc_bot.py > irc_bot.log &
ソースコードは以下の通り。
そこそこ社内IRCも使えるようになったのだが、最近、別プロジェクトで利用を始めたSlackがかなり便利だったので、そこに技術系チャンネルを作ったところ、ほとんどのメンバーがSlackに流れてしまった…。次はSlackでボット作成かなぁ。
今回はIRCボットの作成について書いてみる。しばらく前から社内IRCで技術関連の雑談用チャンネルを利用している。技術系のメーリングリストもあるのだけど、わざわざメールで話題にするようなことでもない、軽い内容を気軽に議論できる場が欲しかったので有志で立ち上げた。IT企業にとって技術的な雑談をできる場はとても大事だと思う。
素のIRCの場合、発言内容がサーバに保存されず、途中から参加しても話題についていけないという問題が出たので、適当なボットで対応することにしたが、やはりエンジニアがメインの会社なのだからボットも自作が基本でしょ、ということでPythonで作ってみた。まあ、作ったといっても「Python でシンプルな IRC クライアントを作成する」のサイトを参考にさせてもらったのでスクラッチから作成したわけではない。
コマンド techlog: を入れると、その日の発言がボットからのtalkで返される。ただ、最初は1日分の発言しか取れない仕様だったので、日を跨ぐとその前の発言が取得できないし、一時的にチャットを抜けた分のログが欲しいだけでも強制的に1日分の発言を取得してしまう。
そこで、SQLite3を使ってユーザ毎にログイン・ログアウト時刻を管理して、ログアウトしてからログインするまでの発言を取得できるように変更することにした。新規に参加したメンバーでも自動的にデータベースに登録される。せっかくユーザの時刻情報があるのだから、techtime:, techtimeall: というコマンドを作って、それぞれ自分の滞在時間、ユーザ全員分の滞在時間も取得できるようにしてみた。
IRCサーバ上で以下のコマンドを実行すると、IRCにボットが常駐する。
$ ./irc_bot.py > irc_bot.log &
ソースコードは以下の通り。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
# -*- coding: utf-8 -*- | |
import sys | |
import os | |
import socket | |
import string | |
import datetime | |
import time | |
import re | |
import sqlite3 | |
import logging | |
VERSION = "0.11" | |
REVISION = "b" | |
VER_DATE = "20150113" | |
SERVER = "irc.your_domain.com" | |
PORT = 6667 | |
NICKNAME = "techbot" | |
CHANNEL = "#tech" | |
logging.basicConfig(level=logging.INFO) | |
SQLITE_DB = "irc_users.dat" | |
con = sqlite3.connect(SQLITE_DB, check_same_thread=True) | |
cur = con.cursor() | |
cur.execute(""" | |
CREATE TABLE IF NOT EXISTS userdata | |
(id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, username TEXT, join_time INTEGER, quit_time INTEGER, duration INTEGER) | |
""") | |
INSERT_DB = "INSERT INTO userdata VALUES(NULL,?,?,?,?)" | |
IRC = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
def irc_conn(): | |
IRC.connect((SERVER, PORT)) | |
def send_data(command): | |
IRC.send(command + "\n") | |
def join(channel): | |
send_data("JOIN %s" % channel) | |
def login(nickname, username="techbot", password=None, realname="techbot", hostname="irc.your_domain.com", servername="irc.your_domain.com"): | |
send_data("USER %s %s %s %s" % (username, hostname, servername, realname)) | |
send_data("NICK " + nickname) | |
def main(args): | |
irc_conn() | |
login(NICKNAME) | |
join(CHANNEL) | |
log_date = datetime.date.today().isoformat() | |
logf = file("irc_%s.log" % log_date, "a+") | |
logm = [l for l in logf] | |
status = "" | |
is_finished = False | |
buffer = "" | |
while (1): | |
today = datetime.date.today().isoformat() | |
if log_date != today: | |
log_date = today | |
logf.close() | |
logf = file("irc_%s.log" % log_date, "a+") | |
logm = [] | |
buffer += IRC.recv(4096) | |
if not string.split(buffer): continue | |
lines = string.split(buffer, "\n") | |
buffer = lines[-1] | |
for line in lines[:-1]: | |
line += "\n" | |
sys.stdout.write(line) | |
sys.stdout.flush() | |
msg = string.split(line) | |
if msg[0] == "PING": | |
send_data("PONG %s" % msg[1]) | |
if msg[1] == "JOIN": | |
nickname = msg[0][1:string.find(msg[0], "!")] | |
cur.execute("SELECT quit_time FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
now = int(time.mktime(datetime.datetime.now().timetuple())) | |
if s: | |
cur.execute("UPDATE userdata SET join_time=%d WHERE username='%s'" % (now, nickname)) | |
else: | |
cur.execute(INSERT_DB, (nickname, now, None, 0)) | |
con.commit() | |
send_data(":%s LIST %s\n" % (NICKNAME, CHANNEL)) | |
status = "JOIN" | |
if msg[1] in ("QUIT", "PART"): | |
nickname = msg[0][1:string.find(msg[0], "!")] | |
cur.execute("SELECT join_time FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
now = int(time.mktime(datetime.datetime.now().timetuple())) | |
if s: | |
duration = 0 | |
if s[0]: duration = now - int(s[0]) | |
cur.execute("SELECT duration FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
if s and s[0]: duration += int(s[0]) | |
cur.execute("UPDATE userdata SET quit_time=%d, duration=%d WHERE username='%s'" % (now, duration, nickname)) | |
else: | |
cur.execute(INSERT_DB, (nickname, None, now, 0)) | |
con.commit() | |
send_data(":%s LIST %s\n" % (NICKNAME, CHANNEL)) | |
status = "QUIT" | |
if msg[1] == "322": | |
if msg[3] == CHANNEL and msg[4] == "2": | |
if status == "JOIN": | |
send_data(":%s PRIVMSG %s :%s\n" % (NICKNAME, CHANNEL, "一番乗りですね。")) | |
if status == "QUIT": | |
send_data(":%s PRIVMSG %s :%s\n" % (NICKNAME, CHANNEL, "お疲れさまです。最後になりました。")) | |
if msg[1] == "PRIVMSG" or msg[1] == "NOTICE": | |
if msg[2] not in (CHANNEL, NICKNAME): continue | |
if msg[1] == "PRIVMSG": | |
nickname = msg[0][1:string.find(msg[0], "!")] | |
message = " ".join(msg[3:]) | |
m = re.search(r"techlog\:(.*)", message) | |
if m: | |
cur.execute("SELECT quit_time FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
logmm = logm[:] | |
if s and s[0]: | |
prevlogs = [] | |
quit_time = datetime.datetime.fromtimestamp(int(s[0])) | |
quit_date = quit_time.date() | |
while quit_date < datetime.date.today(): | |
logff = file("irc_%s.log" % quit_date.isoformat(), "r") | |
prevlogs.extend([l for l in logff]) | |
quit_date += datetime.timedelta(1) | |
logff.close() | |
prevlogs.extend(logm[:]) | |
logmm = [] | |
for l in prevlogs: | |
xs = l.split("#") | |
tm = datetime.datetime.strptime(xs[0], "%Y-%m-%dT%H:%M:%S.%f") | |
if tm > quit_time: | |
logmm.append(l) | |
for l in logmm: | |
xs = l.split("#") | |
tm = datetime.datetime.strptime(xs[0], "%Y-%m-%dT%H:%M:%S.%f") | |
msg = "#".join(xs[1:]) | |
send_data(":%s PRIVMSG %s :%s %s\n" % (NICKNAME, nickname, tm.time().isoformat().split(".")[0], msg)) | |
m = re.search(r"techquit\:(.*)", message) | |
if m: | |
is_finished = True | |
break | |
m = re.search(r"techtime\:(.*)", message) | |
if m: | |
cur.execute("SELECT duration FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
duration = datetime.timedelta(seconds=0) | |
if s and s[0]: | |
duration = datetime.timedelta(seconds=int(s[0])) | |
cur.execute("SELECT join_time FROM userdata WHERE username='%s'" % nickname) | |
s = cur.fetchone() | |
if s and s[0]: | |
duration += datetime.timedelta(seconds=int(time.mktime(datetime.datetime.now().timetuple())) - int(s[0])) | |
seconds = duration.seconds | |
minutes, seconds = seconds / 60, seconds % 60 | |
hours, minutes = minutes / 60, minutes % 60 | |
send_data(":%s PRIVMSG %s :あなたは、%d日%d時間%d分%d秒、%s に滞在しました。\n" % (NICKNAME, nickname, duration.days, hours, minutes, seconds, CHANNEL)) | |
m = re.search(r"techtimeall\:(.*)", message) | |
if m: | |
cur.execute("SELECT duration, username FROM userdata") | |
for s in cur: | |
if s and s[1] and s[1] == NICKNAME: | |
continue | |
duration = datetime.timedelta(seconds=0) | |
if s and s[0]: | |
duration = datetime.timedelta(seconds=int(s[0])) | |
seconds = duration.seconds | |
minutes, seconds = seconds / 60, seconds % 60 | |
hours, minutes = minutes / 60, minutes % 60 | |
send_data(":%s PRIVMSG %s :%s は、%d日%d時間%d分%d秒、%s に滞在しました。\n" % (NICKNAME, nickname, str(s[1]), duration.days, hours, minutes, seconds, CHANNEL)) | |
m = re.search(r"techbotkawaii\:(.*)", message) | |
if m: | |
send_data(":%s PRIVMSG %s :%s\n" % (NICKNAME, nickname, "ありがと!")) | |
if msg[2] != CHANNEL: continue | |
nickname = msg[0][1:string.find(msg[0], "!")] | |
message = " ".join(msg[3:]) | |
message = "%s#(%s) %s\n" % (datetime.datetime.now().isoformat(), string.lstrip(nickname, ":"), string.lstrip(message, ":")) | |
logf.write(message) | |
logf.flush() | |
logm.append(message) | |
if is_finished: | |
break | |
logf.close() | |
if __name__ == "__main__": main(sys.argv) |
そこそこ社内IRCも使えるようになったのだが、最近、別プロジェクトで利用を始めたSlackがかなり便利だったので、そこに技術系チャンネルを作ったところ、ほとんどのメンバーがSlackに流れてしまった…。次はSlackでボット作成かなぁ。
コメント