From 7e4ac71c32fc041addde6ac2b3abe3330ac3557c Mon Sep 17 00:00:00 2001 From: Joseph Botosh Date: Thu, 27 Aug 2015 16:38:28 +0300 Subject: add !lastseen and !mail commands --- config.py.template | 2 + main.py | 28 ++++++- onlineusers.py | 218 +++++++++++++++++++++++++++++++++++++++++++++++++++++ utils.py | 16 +++- 4 files changed, 261 insertions(+), 3 deletions(-) create mode 100644 onlineusers.py diff --git a/config.py.template b/config.py.template index e55b2f6..b7437bb 100644 --- a/config.py.template +++ b/config.py.template @@ -6,3 +6,5 @@ relist_time = 604800 # Time in seconds before an item needs to be relisted. character = 0 #slot character is in, 0 for first, 1 for second, 2 for third admin = "" #nosell = [] # Items which can't be sold - just add the itemid to the list. +sqlite3_dbfile = "data/mm.db" +online_txt_url = 'http://server.themanaworld.org/online.txt' diff --git a/main.py b/main.py index 9625fd9..a8e5c45 100755 --- a/main.py +++ b/main.py @@ -44,6 +44,7 @@ from player import * import tradey import utils import eliza +from onlineusers import SqliteDbManager chatbot = eliza.eliza() shop_broadcaster = utils.Broadcast() @@ -55,6 +56,7 @@ user_tree = tradey.UserTree() sale_tree = tradey.ItemTree() ItemLog = utils.ItemLog() logger = logging.getLogger('ManaLogger') +db_manager = SqliteDbManager(config.sqlite3_dbfile) def process_whisper(nick, msg, mapserv): msg = filter(lambda x: x in utils.allowed_chars, msg) @@ -76,7 +78,7 @@ def process_whisper(nick, msg, mapserv): if int(user.get("used_stalls")) == 0 and int(user.get("money")) == 0: mapserv.sendall(whisper(nick, "You can no longer use the bot. If you feel this is in error, please contact" + config.admin)) return - allowed_commands = ['!money', '!help', '!getback', '!info' ] + allowed_commands = ['!money', '!help', '!getback', '!info', '!lastseen', "!mail" ] if not broken_string[0] in allowed_commands: mapserv.sendall(whisper(nick, "Your access level has been set to blocked! If you feel this is in error, please contact" + config.admin)) mapserv.sendall(whisper(nick, "Though, you still can do the following: "+str(allowed_commands))) @@ -195,6 +197,10 @@ def process_whisper(nick, msg, mapserv): mapserv.sendall(whisper(nick, "!info - Displays basic information about your account.")) elif broken_string[1] == '!getback': mapserv.sendall(whisper(nick, "!getback - Allows you to retrieve an item that has expired or you no longer wish to sell.")) + elif broken_string[1] == '!lastseen': + mapserv.sendall(whisper(nick, "!lastseen - Show when was online the last time.")) + elif broken_string[1] == '!mail': + mapserv.sendall(whisper(nick, "!mail - Send a message to .")) elif user != -10: if int(user.get('accesslevel')) >= 10 and broken_string[1] == '!listusers': mapserv.sendall(whisper(nick, "!listusers - Lists all users which have a special accesslevel, e.g. they are blocked, seller or admin")) @@ -639,6 +645,21 @@ def process_whisper(nick, msg, mapserv): else: mapserv.sendall(whisper(nick, "Where are you?!? I can't trade with somebody who isn't here!")) trader_state.reset() + elif broken_string[0] == "!lastseen": + who = msg[10:].strip() + if len(who) == 0: + mapserv.sendall(whisper(nick, "Usage: !lastseen ")) + else: + ls_info = db_manager.get_lastseen_info(who) + mapserv.sendall(whisper(nick, ls_info)) + elif broken_string[0] == "!mail": + to_, msg_ = utils.parse_mail_cmdargs(msg[6:].strip()) + if to_ == "" or msg_ == "": + mapserv.sendall(whisper(nick, "Usage: !mail OR !mail \"nick with spaces\" ")) + else: + db_manager.send_mail(nick, to_, msg_) + mapserv.sendall(whisper(nick, "Message to \"%s\" sent" % (to_))) + else: response = chatbot.respond(msg) logger.info("Bot Response: "+response) @@ -770,8 +791,10 @@ def main(): pb = PacketBuffer() shop_broadcaster.mapserv = mapserv - # Map server packet loop + db_manager.mapserv = mapserv + db_manager.start() + # Map server packet loop print "Entering map packet loop\n"; while True: data = mapserv.recv(2048) @@ -1109,6 +1132,7 @@ def main(): # On Disconnect/Exit logger.info("Server disconnect.") + db_manager.stop() shop_broadcaster.stop() mapserv.close() diff --git a/onlineusers.py b/onlineusers.py new file mode 100644 index 0000000..c59834f --- /dev/null +++ b/onlineusers.py @@ -0,0 +1,218 @@ + +""" +Copyright 2015, Joseph Botosh + +This file is part of tradey, a trading bot in The Mana World +see www.themanaworld.org + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License as published by the Free +Software Foundation; either version 2 of the License, or (at your option) +any later version. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . + +Additionally to the GPL, you are *strongly* encouraged to share any modifications +you do on these sources. +""" + +import sys +import logging +import urllib2 +import string +import sqlite3 +import datetime +import threading +import time +from net.packet_out import whisper +import config + +class OnlineUsers: + + def __init__(self, online_url='http://server.themanaworld.org/online.txt', update_interval=60): + self._active = False + self._timer = 0 + self._thread = threading.Thread(target=self._threadfunc, args=()) + self._url = online_url + self._update_interval = update_interval + self.__lock = threading.Lock() + self.__online_users = [] + + @property + def online_users(self): + self.__lock.acquire(True) + users = self.__online_users[:] + self.__lock.release() + return users + + def dl_online_list(self): + """ + Download online.txt, parse it, and return a list of online user nicks. + If error occurs, return empty list + """ + try: + data = urllib2.urlopen(self._url).read() + except urllib2.URLError, e: + # self.logger.error("urllib error: %s", e.message) + print ("urllib error: %s" % e.message) + return [] + start = string.find(data, '------------------------------\n') + 31 + end = string.rfind(data, '\n\n') + s = data[start:end] + return map(lambda n: n[:-5].strip() if n.endswith('(GM) ') else n.strip(), + s.split('\n')) + + def _threadfunc(self): + while self._active: + if (time.time() - self._timer) > self._update_interval: + users = self.dl_online_list() + self.__lock.acquire(True) + self.__online_users=users + self.__lock.release() + self._timer = time.time() + else: + time.sleep(1.0) + + def start(self): + self._active = True + self._thread.start() + + def stop(self): + if self._active: + self._active = False + self._thread.join() + + +class SqliteDbManager: + + def __init__(self, dbfile): + self._active = False + self._timer = 0 + self._lastseen_thread = threading.Thread(target=self.__lastseen_threadfunc, args=()) + self._mailbox_thread = threading.Thread(target=self.__mailbox_threadfunc, args=()) + self._dbfile = dbfile + self.mapserv = None + self._online_manager = OnlineUsers(config.online_txt_url) + + self.db, self.cur = self._open_sqlite_db(dbfile) + self.cur.execute('create table if not exists LastSeen(\ + NICK text[25] not null unique,\ + DATE_ date not null)') + self.cur.execute('create table if not exists MailBox(\ + ID integer primary key,\ + FROM_ text[25] not null,\ + TO_ text[25] not null,\ + MESSAGE text[255] not null)') + self.cur.execute('create unique index if not exists \ + FROM_TO_IDX on MailBox(FROM_,TO_)') + self.db.commit() + + def __del__(self): + try: + self.db.close() + except Exception: + pass + + def _open_sqlite_db(self, dbfile): + """ + Open sqlite db, and return tuple (connection, cursor) + """ + try: + db = sqlite3.connect(dbfile) + cur = db.cursor() + except sqlite3.Error, e: + # self.logger.error("sqlite3 error: %s", e.message) + print ("sqlite3 error: %s" % e.message) + sys.exit(1) + return db, cur + + def __update_lastseen_info(self, users, db, cur): + now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M") + values = map(lambda u: (u, now), users) + cur.executemany('replace into LastSeen(NICK,DATE_) values(?,?)', + values) + db.commit() + + def get_lastseen_info(self, nick): + self.cur.execute('select DATE_ from LastSeen where NICK=?',(nick,)) + self.db.commit() # NOTE: do I need it? + row = self.cur.fetchone() + if row: + return ('%s was seen %s' % (nick, row[0])).encode('utf-8') + else: + return '%s was never seen' % nick + + def __lastseen_threadfunc(self): + print '__lastseen_threadfunc started' + db, cur = self._open_sqlite_db(self._dbfile) + while self._active: + if (time.time() - self._timer) > 60: + users = self._online_manager.online_users + self.__update_lastseen_info(users, db, cur) + self._timer = time.time() + else: + time.sleep(1.0) + db.close() + + def send_mail(self, from_, to_, message): + self.cur.execute('replace into MailBox(FROM_,TO_,MESSAGE) values(?,?,?)', + (from_,to_,message)) + self.db.commit() + + def get_unread_mails(self, nick, db=None, cur=None): + if db is None: + db = self.db + if cur is None: + cur = self.cur + cur.execute('select FROM_,MESSAGE from MailBox where TO_=?', + (nick,)) + db.commit() + mails = cur.fetchall() + cur.execute('delete from MailBox where TO_=?', + (nick,)) + db.commit() + return mails + + def __mailbox_threadfunc(self): + print '__mailbox_threadfunc started' + db, cur = self._open_sqlite_db(self._dbfile) + while self._active: + if (time.time() - self._timer) > 60: + users = self._online_manager.online_users + for u in users: + mail = self.get_unread_mails(u, db, cur) + nm = len(mail) + if nm > 0: + self.mapserv.sendall(whisper(u, "You have %d new mails:" % (nm,))) + time.sleep(0.7) + for m in mail: + msg = ("From %s : %s" % (m[0], m[1])).encode('utf-8') + self.mapserv.sendall(whisper(u, msg)) + time.sleep(0.7) + self._timer = time.time() + else: + time.sleep(1.0) + db.close() + + def start(self): + self._online_manager.start() + self._active = True + self._lastseen_thread.start() + self._mailbox_thread.start() + + def stop(self): + if self._active: + self._active = False + self._lastseen_thread.join() + self._mailbox_thread.join() + self._online_manager.stop() + + +if __name__=='__main__': + print "You should not run this file. Use main.py" diff --git a/utils.py b/utils.py index 52d51b6..aad402f 100644 --- a/utils.py +++ b/utils.py @@ -14,7 +14,7 @@ import mutex import threading from net.packet_out import * -allowed_chars = "abcdefghijklmnoprstquvwxyzABCDEFGHIJKLMNOPRSTQUVWXYZ1234567890-_+=!@$%^&*();'<>,.?/~`| " +allowed_chars = "abcdefghijklmnoprstquvwxyzABCDEFGHIJKLMNOPRSTQUVWXYZ1234567890-_+=!@$%^&*();'\"<>,.?/~`| " # Process a recieved ip address. def parse_ip(a): @@ -76,6 +76,20 @@ class ItemDB: return item_id return -10 #Not found + +def parse_mail_cmdargs(s): + if len(s) < 3: + return "", "" + if s[0] == '"': + end = s.find('"', 1) + if end > 0: + return s[1:end], s[end+2:] + else: + return "", "" + else: + end = s.find(' ') + return s[:end], s[end+1:] + class ItemLog: """ Writes all sales to a log file, for later processing.""" def __init__(self): -- cgit v1.2.3-60-g2f50