これからカーチャンbotのメッセージ選択アルゴリズムを移植します。
まずJavaScript動作のときの概要をちょっと書いておきます。
カーチャンbotは、全部で70種類くらいの定型文を用意しています。
お昼や夕食等の定刻用メッセージ、文中にユーザー名、商品名、などが入るものも含まれます。それら定型文自体をDOM要素として持たせていました。また、置換アイテムも同様です。
大雑把に書くと、
・定型文、置換アイテム、followerリストをデータとして持っている
・そしてそれらを使用したかどうかフラグを管理している(ストレージ等に書き出さないRAM上のもの)
・毎時ランダムに未使用のものを選ぶ
・定刻は定刻用定型文から引っ張ってくる
・通常時も、時間縛りあり定型文、時間縛りなし定型文がある
といった感じです。
毎時00分に、定型文をランダムに選び、時刻縛りがある場合はそれにあわせて、置換する必要があればアイテムもランダムに選び、そして投稿する文章が完成します。post完了後には、次の00分00秒000までの時間を計算して、setTimeoutで次の00分にまた同じことをするよう、仕込んでおきます。それを繰り返しています。
(0時過ぎのおやすみpost、followerリスト更新、などはまだ移植してないので、触れないでおきます)
GAE移植にあたって、これらデータの使用/未使用管理をどうするか、です。JavaScriptではRAM上でフラグ管理してました(のでブラウザ再起動で初期化されます)が、今度GAE移植して、cronで毎時動作させるとなると、いくら定型文使用/未使用のフラグをRAM上で管理しても、1回動作が終われば消えますから、ストレージに書き出さないとなりません。当然ですが。(むしろ今までが特異な動作原理のbotだっただけで)
ということで、僕はDBは全くわからないので、csv形式の生データファイルを読み書きする方向で行きます。(※最後に書いてありますが、ちなみにこの方法ではうまくいきません)
たとえば、
0,10-17,%A おかあさんちょっと買い物行って来るからね\n
という感じで。先頭が使用/未使用(1/0)、次が時刻指定/時間範囲縛り設定(なし=N,19時用=19,10時~17時用=9-18といった感じ)、最後が本文です。%AはJ( 'ー`)しに後で置換します。
これがずらーっと並んだデータファイルを作っておきます。
で、実際にPythonで読み込むときはこんな感じに。
import os
import random
# ファイル読み書きクラス
class fileAccess:
def __init__(self, src):
self.src = src
self.lines = []
def readData(self):
try:
f = open(self.src, 'r')
except IndexError:
print 'Index error'
except IOError:
print '"%s" cannot be opened.' % self.src
else: # no error
tmpLines = f.read().split('\n') # \n を区切りとしてファイル内容を読み込む
f.close() # ファイルクローズ
length = len(tmpLines) # 読み込んだ行数
for cnt in range(0, length):
str = tmpLines[cnt]
if str != "": # 空行は除外
self.lines.append(str)
def modifyData(self):
if len(self.lines) == 0:
print 'input lines has no data.'
return
tmpfile = "tmp_" + self.src
try:
f = open(tmpfile, 'w')
except IndexError:
print 'Index error'
except IOError:
print '"%s" cannot be opened.' % tmpfile
else: # no error
length = len(self.lines) # 読み込んだ行数
for cnt in range(0, length):
if self.lines[cnt] != "": # 空行は除外
f.write(self.lines[cnt] + "\n") # 引数の文字列をファイルに書き込む
f.close() # ファイルを閉じる
# ファイル操作: 変更前backupコピーを作成し 変更後tmpを正式にrename
bakfile = "bak_" + self.src
if os.path.exists(bakfile):
os.remove(bakfile)
os.rename(self.src, bakfile)
os.rename(tmpfile, self.src)
# 読み込んだ行をカンマで分けるクラス
class messageStruct:
def __init__(self, str):
tmp = str.split(',')
self.used = tmp[0]
self.timespan = tmp[1]
self.msg = tmp[2]
def setUsedStatus(self):
self.used = "1" # ものぐさで、int()で数値化してないで文字で判定してる
def clearUsedStatus(self):
self.used = "0"
def getCombinedStr(self):
return self.used + ',' + self.timespan + ',' + self.msg
# main
# データ読み込み
messageSource = fileAccess("msg.csv.dat")
messageSource.readData()
found = False
while found == False:
# ランダム
rndMax = len(essageSource.lines) - 1
i = random.randint(0, rndMax)
# カンマ区切りを各パラメータに分解
str = messageStruct(messageSource.lines[i])
if str.used == "0": # 未使用なのでメッセージ確定
str.setUsedStatus() # 使用済みに更新
messageSource.lines[i] = str.etCombinedStr()
found = True
# 略しますが、
# 必要があればuser名など置換してpost処理
# データ更新
messageSource.modifyData()
本当はもっと細かく、現在時刻取得して定刻用メッセージにするかどうか判定したり、置換アイテムが必要か、どの種のアイテムか、などいろいろやりますが、基本はファイル読んでランダムに選んで未使用なら採用、ってやってます。
が、ここで困ったことが。
実はGAEではファイルを静的なデータソースとして置くことはできるっぽいのですが、それを動的に更新したりはできません(知らずにLocalでターミナルからPython実行して↑の仕組み作ってた)。ので、この方法は、ランダムポストだけするには使えますが、本来の目的のどの定型文をポストしたかの管理を行うには、これでは不十分です。GAEのデータストアの方法を知らねばならないようです。僕がさっぱり知らない領域、DBに首を突っ込まねばなりません。
続く