diff --git a/README.md b/README.md index b70d10b..75b93d6 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,20 @@ # smskrupp -A project using [gammu](https://github.com/gammu/gammu) to handle sms lists. +smskrupp **implements SMS lists** on top of [gammu](https://github.com/gammu/gammu). Gammu does all the lower-level talking to the modem. + +It has a command-line interface and a Web interface for list management. + +## Usage + +Use the `smskrupp` command to manage groups: + + $ ./smskrupp add-group test t + $ ./smskrupp add-member 0731234567 member1 test + $ ./smskrupp add-member 0731234568 member2 test + $ ./smskrupp set-sender 0731234567 test + $ ./smskrupp list-members test + +Now if you have gammu-smsd set up correctly you should be able to send a message from 0731234567 to the phone you've setup with gammu and it will deliver to all members in the group. ## Setup @@ -14,32 +28,20 @@ If you want the webapp you also need: # apt-get install python-bcrypt python-flask -Setting up the db: +Setting up the sqlite database: - $ cat sql/*.sql | sqlite3 smskrupp.db + $ cat sql/*.sql | sqlite3 smskrupp.db && cat sql/*.sql | sqlite3 smskrupp-test.db Create the config file: $ cp config.py.default config.py -Edit the config file so it points to the correct places. +Edit the config file so it points to the correct locations. For gammu to work you also need a gammu-smsdrc, there is an example file in the doc/ directory. -## Usage - -use smskrupp command to manage groups: - - $ smskrupp add-group test t - $ smskrupp add-member 0731234567 member1 test - $ smskrupp add-member 0731234568 member2 test - $ smskrupp set-sender 0731234567 test - $ smskrupp list-members test - -Now if you have gammu-smsd setup correctly you should be able to send a message from 0731234567 to the phone you've setup with gammu and it will deliver to all members in the group. - ## Tests -If your config.py points to a test-db and a test-smsdrc, simply run +If your config.py points to a test-db and a test-smsdrc -- this is the default configuration -- simply run: $ nosetests diff --git a/config.py.default b/config.py.default index a6f8b5f..bd01ccf 100644 --- a/config.py.default +++ b/config.py.default @@ -1,8 +1,9 @@ class Config: db = 'smskrupp.db' smsdrc = '/etc/gammu-smsdrc' - test_db = 'tmp.db' + test_db = 'smskrupp-test.db' test_smsdrc = 'test-gammu-smsdrc' + send_prefix='#' admin_prefix='/' log = 'smskrupp.log' default_phone = 'phone1' diff --git a/core.py b/core.py index 66b1fc0..265ed8b 100644 --- a/core.py +++ b/core.py @@ -2,19 +2,19 @@ from config import config import sqlite3 -import gammu.smsd from time import strftime, localtime -import bcrypt + def normalize_number(number): if number.startswith('07'): - return '+46'+number[1:] + return '+46' + number[1:] elif number.startswith('0046'): - return '+'+number[2:] + return '+' + number[2:] elif number.startswith('+46'): return number return None + class Data: def __init__(self): self.conn = sqlite3.connect(config.db) @@ -22,7 +22,7 @@ def __init__(self): def setup_db(self): '''Creates the database tables.''' - with open('sql/smskrupp.sql','r') as f: + with open('sql/smskrupp.sql', 'r') as f: self.cursor.executescript(f.read()) self.conn.commit() @@ -34,37 +34,37 @@ def add_number(self, number, alias, group_id): c = self.cursor if alias == None: base = "noname" - c.execute("select alias from qq_groupMembers "+ + c.execute("select alias from qq_groupMembers " + "where alias like ? order by alias ", - (base+'%',)) + (base + '%',)) max_num = 0 for row in c: num = row[0][len(base):] if num.isdigit() and int(num) > max_num: max_num = int(num) - alias = base+str(max_num+1) + alias = base + str(max_num + 1) - c.execute("insert or ignore into qq_groupMembers "+ - "(number, groupId, alias) "+ + c.execute("insert or ignore into qq_groupMembers " + + "(number, groupId, alias) " + "values (?,?,?)", (number, group_id, alias)) - c.execute("update qq_groupMembers set alias=? "+ + c.execute("update qq_groupMembers set alias=? " + "where number=? and groupId=?", (alias, number, group_id)) self.conn.commit() - return self.get_member_id(number,group_id) + return self.get_member_id(number, group_id) def set_member_info(self, member_id, **kwargs): c = self.cursor if 'sender' in kwargs: c.execute("update qq_groupMembers set sender=? where id=?", - (kwargs['sender'],member_id)) + (kwargs['sender'], member_id)) if 'admin' in kwargs: c.execute("update qq_groupMembers set admin=? where id=?", - (kwargs['admin'],member_id)) + (kwargs['admin'], member_id)) if 'alias' in kwargs: c.execute("update qq_groupMembers set alias=? where id=?", - (kwargs['alias'],member_id)) + (kwargs['alias'], member_id)) self.conn.commit() def remove_number(self, member_id=None, number=None, group_id=None): @@ -80,9 +80,9 @@ def add_group(self, name, keyword, phone): ''' create a group and return id of the created group ''' c = self.cursor - c.execute("insert into qq_groups (name,keyword,phone) "+ + c.execute("insert into qq_groups (name,keyword,phone) " + "values (?,?,?)", - (name,keyword,phone)) + (name, keyword, phone)) self.conn.commit() c.execute('select id from qq_groups where name=?', (name,)) group_id = None @@ -104,15 +104,15 @@ def remove_group(self, gid): def get_group_senders(self, group_id): c = self.cursor - c.execute('select id, number, alias from qq_groupMembers '+ + c.execute('select id, number, alias from qq_groupMembers ' + 'where groupId=? and sender=1', (group_id,)) - return [{'id':row[0],'number':row[1],'alias':row[2]} for row in c] + return [{'id': row[0], 'number': row[1], 'alias': row[2]} for row in c] def get_group_admins(self, group_id): c = self.cursor - c.execute('select id,number,alias from qq_groupMembers '+ + c.execute('select id,number,alias from qq_groupMembers ' + 'where groupId=? and admin=1', (group_id,)) - return [{'id':row[0],'number':row[1],'alias':row[2]} for row in c] + return [{'id': row[0], 'number': row[1], 'alias': row[2]} for row in c] def get_group_members(self, group_id): ''' return array of dicts describing members (id, number, alias, sender, admin) @@ -120,8 +120,8 @@ def get_group_members(self, group_id): c = self.cursor c.execute('select id,number,alias,sender,admin from qq_groupMembers '+ 'where groupId=?', - (group_id,)) - return [{'id':row[0], 'number':row[1], 'alias':row[2], 'sender':(row[3]==1), + (group_id, )) + return [{'id': row[0], 'number': row[1], 'alias': row[2], 'sender': (row[3] ==1 ), 'admin':(row[4]==1)} for row in c] def get_groups(self, phone=None, number=None): @@ -132,11 +132,11 @@ def get_groups(self, phone=None, number=None): c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' +'join qq_groups g on g.id = m.groupId ' +'where m.number=? and g.phone=? order by g.name asc', - (number,phone)) + (number, phone)) elif number: c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' - +'join qq_groups g on g.id = m.groupId ' - +'where m.number=? order by g.name asc', + + 'join qq_groups g on g.id = m.groupId ' + + 'where m.number=? order by g.name asc', (number,)) elif phone: c.execute('select id,name, keyword, phone from qq_groups where phone=? order by name asc', @@ -151,13 +151,13 @@ def get_send_groups(self, sender, phone=None): c = self.cursor if phone: c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' - +'join qq_groups g on g.id = m.groupId ' - +'where m.number=? and g.phone=? and m.sender=1 order by g.name asc', - (sender,phone)) + + 'join qq_groups g on g.id = m.groupId ' + + 'where m.number=? and g.phone=? and m.sender=1 order by g.name asc', + (sender, phone)) else: c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' - +'join qq_groups g on g.id = m.groupId ' - +'where m.number=? and m.sender=1 order by g.name asc', + + 'join qq_groups g on g.id = m.groupId ' + + 'where m.number=? and m.sender=1 order by g.name asc', (sender,)) return [{'id':row[0], 'name':row[1], 'keyword':row[2], 'phone':row[3]} for row in c] @@ -167,17 +167,17 @@ def get_admin_groups(self, sender, phone=None): c = self.cursor if phone: c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' - +'join qq_groups g on g.id = m.groupId ' - +'where m.number=? and g.phone=? and m.admin=1 order by g.name asc', - (sender,phone)) + + 'join qq_groups g on g.id = m.groupId ' + + 'where m.number=? and g.phone=? and m.admin=1 order by g.name asc', + (sender, phone)) else: c.execute('select g.id, g.name, g.keyword, g.phone from qq_groupMembers m ' - +'join qq_groups g on g.id = m.groupId ' - +'where m.number=? and m.admin=1 order by g.name asc', + + 'join qq_groups g on g.id = m.groupId ' + + 'where m.number=? and m.admin=1 order by g.name asc', (sender,)) return [{'id':row[0], 'name':row[1], 'keyword':row[2], 'phone':row[3]} for row in c] - def get_group_id(self,name): + def get_group_id(self, name): c = self.cursor c.execute('select id,name from qq_groups where name=?', (name,)) x = c.fetchone() @@ -208,11 +208,11 @@ def get_member_ids(self, number): def get_member_info(self, member_id): c = self.cursor c.execute('select m.id,m.number,m.alias,m.groupId,g.name from qq_groupMembers m ' - +'left join qq_groups g on g.id = m.groupId where m.id=?', + + 'left join qq_groups g on g.id = m.groupId where m.id=?', (member_id,)) x = c.fetchone() if x: - return {'id': x[0], 'number': x[1], 'alias': x[2], 'groupId':x[3], 'groupName':x[4]} + return {'id': x[0], 'number': x[1], 'alias': x[2], 'groupId': x[3], 'groupName': x[4]} return None def _calculate_udh_part(self, udh): @@ -222,51 +222,51 @@ def _calculate_udh_part(self, udh): i = 1 while i <= length: # parse one IEI - iei_id = int(udh[i*2:i*2+2], 16) + iei_id = int(udh[i * 2: i * 2 + 2], 16) i += 1 - iei_len = int(udh[i*2:i*2+2], 16) + iei_len = int(udh[i * 2: i * 2 + 2], 16) if not iei_id == 0: # not concatenation iei i += iei_len continue i += 1 - ref = int(udh[i*2:i*2+2], 16) + ref = int(udh[i * 2: i * 2 + 2], 16) i += 1 - num_parts = int(udh[i*2:i*2+2], 16) + num_parts = int(udh[i * 2: i * 2 + 2], 16) i += 1 - part = int(udh[i*2:i*2+2], 16) - return part,num_parts,ref + part = int(udh[i * 2: i * 2 + 2], 16) + return part, num_parts, ref def get_unprocessed(self): c = self.cursor - c.execute("select ID,SenderNumber,RecipientID,TextDecoded,UDH "+ + c.execute("select ID,SenderNumber,RecipientID,TextDecoded,UDH " + "from inbox where Processed='false'") parts = {} ret = [] for row in c: - i,src,phone,text,udh = row + i, src, phone, text, udh = row x = self._calculate_udh_part(udh) if x: - part,num_parts,ref = x - key = src+'-'+str(ref) + part, num_parts, ref = x + key = src + '-' + str(ref) if not key in parts: - parts[key] = src,phone,[],[] - parts[key][2].append((part,text)) + parts[key] = src, phone, [], [] + parts[key][2].append((part, text)) parts[key][3].append(i) if len(parts[key][2]) == num_parts: # found all parts - tot_text = "".join(map(lambda x: x[1], sorted(parts[key][2], key = lambda x: x[0]))) - ret.append({'ids':parts[key][3], 'src':src, 'phone':phone, 'text':tot_text}) + tot_text = "".join(map(lambda x: x[1], sorted(parts[key][2], key=lambda x: x[0]))) + ret.append({'ids': parts[key][3], 'src': src, 'phone': phone, 'text': tot_text}) del parts[key] else: # single part - ret.append({'ids':[i], 'src':src, 'phone':phone, 'text':text}) + ret.append({'ids': [i], 'src': src, 'phone': phone, 'text': text}) return ret def set_processed(self, msgId, status='true'): c = self.cursor - c.execute("update inbox set Processed=? where ID=?", (status,msgId)) + c.execute("update inbox set Processed=? where ID=?", (status, msgId)) self.conn.commit() def fake_incoming(self, src, phoneId, msg): @@ -288,39 +288,40 @@ def cleanup(self): if self.conn: self.conn.close() self.conn = None - + def add_webuser(self, username, pw, privilege): + import bcrypt c = self.cursor - h = bcrypt.hashpw(pw,bcrypt.gensalt()) + h = bcrypt.hashpw(pw, bcrypt.gensalt()) c.execute('insert into qq_webUsers (username,hash,privilege) values (?,?,?)', - (username,h,privilege)) + (username, h, privilege)) self.conn.commit() def get_webusers(self): c = self.cursor c.execute('select u.id,u.username,u.privilege,g.id,g.name from qq_webUsers u ' - +'left join qq_webUserGroups wg on wg.userId = u.id ' - +'left join qq_groups g on g.id = wg.groupId ' - +'order by u.id') + + 'left join qq_webUserGroups wg on wg.userId = u.id ' + + 'left join qq_groups g on g.id = wg.groupId ' + + 'order by u.id') ret = [] seen = [] for row in c: if row[0] in seen: - ret[-1]['groups'].append({'group_id':row[3],'group_name':row[4]}) + ret[-1]['groups'].append({'group_id': row[3], 'group_name': row[4]}) else: seen.append(row[0]) - ret.append({'user_id':row[0],'username':row[1],'privilege':row[2],'groups':[]}) + ret.append({'user_id': row[0], 'username': row[1], 'privilege': row[2], 'groups': []}) if row[3]: - ret[-1]['groups'].append({'group_id':row[3],'group_name':row[4]}) + ret[-1]['groups'].append({'group_id': row[3], 'group_name': row[4]}) return ret def get_webuser_groups(self, webuser_id): c = self.cursor c.execute('select groupId,name,keyword from qq_webUserGroups wg ' - +'left join qq_groups g on g.id=wg.groupId where userId=?', + + 'left join qq_groups g on g.id=wg.groupId where userId=?', (webuser_id,)) - return [{'id':row[0],'name':row[1], 'keyword':row[2]} for row in c] + return [{'id': row[0], 'name': row[1], 'keyword': row[2]} for row in c] def set_webuser_group(self, webuser_id, group_id): c = self.cursor @@ -343,10 +344,11 @@ def remove_webuser_group(self, webuser_id, group_id): self.conn.commit() def set_webuser_pw(self, user_id, pw): - h = bcrypt.hashpw(pw,bcrypt.gensalt()) + import bcrypt + h = bcrypt.hashpw(pw, bcrypt.gensalt()) c = self.cursor c.execute('update qq_webUsers set hash=? where id=?', - (h,user_id)) + (h, user_id)) self.conn.commit() def check_webuser_login(self, username, password): @@ -355,10 +357,12 @@ def check_webuser_login(self, username, password): (username,)) row = c.fetchone() if row: + import bcrypt hashed = row[1] if bcrypt.hashpw(password, hashed) == hashed: - return row[3],row[2] - return 0,0 + return row[3], row[2] + return 0, 0 + class Doer: def __init__(self, sender): @@ -371,14 +375,14 @@ def cleanup(self): def _log(self, text): with open(config.log, "a") as log: t = strftime("%Y-%m-%d %H:%M:%S", localtime()) - log.write("[%s] [doer] %s\n"%(t,text.encode('utf-8'))); + log.write("[%s] [doer] %s\n" % (t, text.encode('utf-8'))) def _parse_action(self, src, phone, orig_msg): - groups = self.data.get_groups(phone=phone, sender=src) + groups = self.data.get_groups(phone=phone, number=src) send_groups = self.data.get_send_groups(src, phone=phone) lmsg = orig_msg.lower().strip() if lmsg == 'stop' or lmsg == 'stopp': - return {'action':'stop', 'groups':groups} + return {'action': 'stop', 'groups': groups} if lmsg.startswith(config.admin_prefix): admin_cmd = lmsg[len(config.admin_prefix):] first_word = admin_cmd.split(" ")[0] @@ -394,18 +398,17 @@ def _parse_action(self, src, phone, orig_msg): if not group: # no keyword if len(groups) == 1 and admin_cmd in ["stop", "stopp"]: - return {'action':'stop', 'groups':groups} + return {'action': 'stop', 'groups': groups} if len(send_groups) == 1: group = send_groups[0] if not group: # can't figure out group, give up - return {'action':'invalid'} + return {'action': 'invalid'} if admin_cmd in ["stop", "stopp"]: - return {'action':'stop', 'groups':groups} - + return {'action': 'stop', 'groups': groups} number = None action = None @@ -421,9 +424,9 @@ def _parse_action(self, src, phone, orig_msg): action = 'add' number = normalize_number(rest) if action and number: - return {'action':action, 'number':number, 'group':group} + return {'action': action, 'number': number, 'group': group} else: - return {'action':'invalid'} + return {'action': 'invalid'} if lmsg.startswith(config.send_prefix): send_msg = None @@ -431,18 +434,18 @@ def _parse_action(self, src, phone, orig_msg): lfirst_word = send_cmd.split(" ")[0].lower() for g in groups: if lfirst_word == g['keyword'].lower(): - send_msg = "%s%s %s"%(config.send_prefix,g['keyword'], + send_msg = "%s%s %s" % (config.send_prefix, g['keyword'], send_cmd[len(lfirst_word):].strip()) group = g break if send_msg and group: - return {'action':'sendout', 'group':group, 'msg':send_msg} + return {'action': 'sendout', 'group': group, 'msg': send_msg} - return {'action':'invalid'} + return {'action': 'invalid'} def _handle_message(self, ids, src, phone, orig_msg): - self._log("got message '%s' from %s to %s"%(orig_msg,src,phone)) - action = self._parse_action(src,phone,orig_msg) + self._log("got message '%s' from %s to %s" % (orig_msg, src, phone)) + action = self._parse_action(src, phone, orig_msg) status = 'invalid' if action['action'] == 'stop': @@ -453,40 +456,40 @@ def _handle_message(self, ids, src, phone, orig_msg): group = action['group'] msg = action['msg'] if not src in [m['number'] for m in self.data.get_group_senders(group['id'])]: - self._log("Warning: Unauthorized sendout command '%s' from %s to %s"% - (orig_msg,src,phone)) + self._log("Warning: Unauthorized sendout command '%s' from %s to %s" % + (orig_msg, src, phone)) status = 'unauthorized' else: - self._log("doing sendout to group %s"%group['name']) + self._log("doing sendout to group %s" % group['name']) members = self.data.get_group_members(group['id']) for member in members: - self.sender.send(member['number'],msg) + self.sender.send(member['number'], msg) status = 'send' - elif action['action'] in ['add','add_sender', 'add_admin']: + elif action['action'] in ['add', 'add_sender', 'add_admin']: group = action['group'] if not src in [m['number'] for m in self.data.get_group_admins(group['id'])]: - self._log("Warning: Unauthorized admin command '%s' from %s to %s"%(orig_msg,src,phone)) + self._log("Warning: Unauthorized admin command '%s' from %s to %s" % (orig_msg, src, phone)) status = 'unauthorized' else: status = 'admin' - self._log("doing command '%s' to group %s"%(action['action'],group['name'])) + self._log("doing command '%s' to group %s" % (action['action'], group['name'])) mid = self.data.add_number(action['number'], None, group['id']) - is_sender,is_admin = False,False + is_sender, is_admin = False, False if action['action'] == 'add_sender': self.data.set_member_info(mid, sender=True) is_sender = True elif action['action'] == 'add_admin': self.data.set_member_info(mid, sender=True, admin=True) - is_sender,is_admin = True,True + is_sender, is_admin = True, True - user_groups = self.data.get_groups(sender=action['number'], phone=phone) + user_groups = self.data.get_groups(number=action['number'], phone=phone) user_send_groups = self.data.get_send_groups(action['number'], phone=phone) welcomes = Helper().get_welcomes(group['name'], group['keyword'], is_sender, is_admin, user_groups, user_send_groups) for msg in welcomes: self.sender.send(action['number'], msg) elif action['action'] == 'invalid': # send help message - user_groups = self.data.get_groups(sender=src, phone=phone) + user_groups = self.data.get_groups(number=src, phone=phone) user_send_groups = self.data.get_send_groups(src, phone=phone) user_admin_groups = self.data.get_admin_groups(src, phone=phone) helps = Helper().get_help(user_groups, user_send_groups, user_admin_groups) @@ -494,34 +497,36 @@ def _handle_message(self, ids, src, phone, orig_msg): self.sender.send(src, msg) for i in ids: - self.data.set_processed(i,status) + self.data.set_processed(i, status) def run(self): self._log("starting doer") messages = self.data.get_unprocessed() for m in messages: - self._handle_message(m['ids'],m['src'],m['phone'],m['text']) + self._handle_message(m['ids'], m['src'], m['phone'], m['text']) self.cleanup() + class Sender: def __init__(self): - self.smsd = gammu.smsd.SMSD(config.smsdrc) + from gammu import smsd + self.smsd = smsd.SMSD(config.smsdrc) def _log(self, text): with open(config.log, "a") as log: t = strftime("%Y-%m-%d %H:%M:%S", localtime()) - log.write("[%s] [doer] %s\n"%(t,text.encode('utf-8'))); + log.write("[%s] [doer] %s\n" % (t, text.encode('utf-8'))) def send(self, dest, msg): # this length calculation will fail will not work for some special # gsm7 chars like [ - if len(msg) <= 160: + if len(msg) <= 160: message = {'Text': msg, 'SMSC': {'Location': 1}, 'Number': dest} - self._log("sending single part message "+str(message)) + self._log("sending single part message " + str(message)) self.smsd.InjectSMS([message]) else: # multipart - self._log("sending multipart message"); + self._log("sending multipart message") smsinfo = { 'Class': 1, 'Unicode': False, @@ -530,28 +535,30 @@ def send(self, dest, msg): 'Buffer': msg }]} # Encode messages - encoded = gammu.EncodeSMS(smsinfo) + from gammu import EncodeSMS + encoded = EncodeSMS(smsinfo) # Send messages for message in encoded: # Fill in numbers message['SMSC'] = {'Location': 1} message['Number'] = dest # Actually send the message - self._log("sending part of message: "+str(message)); + self._log("sending part of message: " + str(message)) self.smsd.InjectSMS([message]) + class Helper: def get_welcomes(self, group_name, group_kw, is_sender, is_admin, groups, send_groups): - msg = u"Välkommen till smslistan %s.\n"%group_name + msg = u"Välkommen till smslistan %s.\n" % group_name if len(groups) == 1: - msg += u"För att lämna listan skriv ett sms med texten \"stop\"." + msg += u"För att lämna listan skriv ett sms med texten \"stop\"." else: - msg += u"För att lämna listan skriv ett sms med texten \"%s%s stop\"."%( + msg += u"För att lämna listan skriv ett sms med texten \"%s%s stop\"." % ( config.admin_prefix, group_kw) if is_sender: - msg += u"\nFör att skicka ett sms, börja smset med %s%s."%(config.send_prefix, group_kw) + msg += u"\nFör att skicka ett sms, börja smset med %s%s." % (config.send_prefix, group_kw) if is_admin: - msg += u"\nFör att lägga till någon till listan, skicka \"%s%s add [nummer]\""%(config.admin_prefix,group_kw) + msg += u"\nFör att lägga till någon till listan, skicka \"%s%s add [nummer]\"" % (config.admin_prefix, group_kw) return [msg] def get_help(self, groups, send_groups, admin_groups): @@ -559,16 +566,16 @@ def get_help(self, groups, send_groups, admin_groups): send_names = [x['name'] for x in send_groups] admin_names = [x['name'] for x in admin_groups] for g in groups: - msg = u"Det här är den automatiska smslistan %s.\n"%g['name'] + msg = u"Det här är den automatiska smslistan %s.\n" % g['name'] if len(groups) == 1: - msg += u"För att lämna listan skriv ett sms med texten \"stop\"." + msg += u"För att lämna listan skriv ett sms med texten \"stop\"." else: - msg += u"För att lämna listan skriv ett sms med texten \"%s%s stop\"."%( + msg += u"För att lämna listan skriv ett sms med texten \"%s%s stop\"." % ( config.admin_prefix, g['keyword']) if g['name'] in send_names: - msg += u"\nFör att skicka ett sms, börja smset med %s%s."%(config.send_prefix, g['keyword']) + msg += u"\nFör att skicka ett sms, börja smset med %s%s." % (config.send_prefix, g['keyword']) if g['name'] in admin_names: - msg += u"\nFör att lägga till någon till listan, skicka \"%s%s add [nummer]\""%(config.admin_prefix,g['keyword']) + msg += u"\nFör att lägga till någon till listan, skicka \"%s%s add [nummer]\"" % (config.admin_prefix, g['keyword']) msgs.append(msg) return msgs diff --git a/core_test.py b/core_test.py index 7e428c8..b125cad 100644 --- a/core_test.py +++ b/core_test.py @@ -1,6 +1,7 @@ import core from config import config + class TestData: def setUp(self): config.db = config.test_db @@ -30,11 +31,11 @@ def test_get_groups(self): gs = self.data.get_groups(phone=phone) assert 2 == len(gs) - gs = self.data.get_groups(phone=phone,sender=number1) + gs = self.data.get_groups(phone=phone, number=number1) assert 1 == len(gs) assert 'group1' == gs[0]['name'] - gs = self.data.get_groups(sender=number2) + gs = self.data.get_groups(number=number2) assert 1 == len(gs) assert 'group2' == gs[0]['name'] @@ -113,11 +114,12 @@ def test_get_unprocessed(self): assert "phone1" == u[0]['phone'] assert "hello" == u[0]['text'] + class TestDoer: def setUp(self): config.db = config.test_db config.smsdrc = config.test_smsdrc - self.sender = FakeSender() #core.Sender() + self.sender = FakeSender() # core.Sender() self.doer = core.Doer(self.sender) self.data = core.Data() self.data.setup_db() @@ -160,7 +162,7 @@ def test_run_admin_command_add_unauthorized(self): assert not "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # help message + assert len(self.sender.sendouts) == 1 # help message assert self.sender.sendouts[0][0] == number def test_run_admin_command_add(self): @@ -174,7 +176,7 @@ def test_run_admin_command_add(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_admin_command_add_sender(self): @@ -188,7 +190,7 @@ def test_run_admin_command_add_sender(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_admin_command_add_admin(self): @@ -202,7 +204,7 @@ def test_run_admin_command_add_admin(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_admin_command_add_keyword(self): @@ -216,7 +218,7 @@ def test_run_admin_command_add_keyword(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_admin_command_add_sender_keyword(self): @@ -230,7 +232,7 @@ def test_run_admin_command_add_sender_keyword(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert not "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_admin_command_add_admin_keyword(self): @@ -244,7 +246,7 @@ def test_run_admin_command_add_admin_keyword(self): assert "+4673123" in [x['number'] for x in self.data.get_group_members(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_senders(gid)] assert "+4673123" in [x['number'] for x in self.data.get_group_admins(gid)] - assert len(self.sender.sendouts) == 1 # welcome message + assert len(self.sender.sendouts) == 1 # welcome message assert self.sender.sendouts[0][0] == "+4673123" def test_run_sendout_unauthorized(self): @@ -292,7 +294,7 @@ def test_run_sendout(self): doer = core.Doer(s) self.data.fake_incoming(number, phone, "test") doer.run() - assert 1 == len(s.sendouts) # test message + assert 1 == len(s.sendouts) # test message assert s.sendouts[0][0] == number def test_run_sendout_prefix(self): @@ -309,9 +311,10 @@ def test_run_sendout_prefix(self): assert 1 == len(s.sendouts) assert (number, "#keyword test") == s.sendouts[0] + class FakeSender: def __init__(self): self.sendouts = [] def send(self, dest, msg): - self.sendouts.append((dest,msg)) + self.sendouts.append((dest, msg)) diff --git a/smskrupp b/smskrupp index 59d4124..ac31cbb 100755 --- a/smskrupp +++ b/smskrupp @@ -4,16 +4,17 @@ import sys import core from config import config + def usage(): - print("%s add-group "%sys.argv[0]) - print("%s list-groups"%sys.argv[0]) - print("%s list-members "%sys.argv[0]) - print("%s add-member "%sys.argv[0]) - print("%s rm-member "%sys.argv[0]) - print("%s set-sender "%sys.argv[0]) - print("%s set-admin "%sys.argv[0]) - print("%s fake-incoming "%sys.argv[0]) - print("%s add-webadmin "%sys.argv[0]) + print("%s add-group " % sys.argv[0]) + print("%s list-groups" % sys.argv[0]) + print("%s list-members " % sys.argv[0]) + print("%s add-member " % sys.argv[0]) + print("%s rm-member " % sys.argv[0]) + print("%s set-sender " % sys.argv[0]) + print("%s set-admin " % sys.argv[0]) + print("%s fake-incoming " % sys.argv[0]) + print("%s add-webadmin " % sys.argv[0]) sys.exit(1) if len(sys.argv) < 2: @@ -36,17 +37,20 @@ elif sys.argv[1] == 'list-members': print "no such group!" sys.exit(1) for member in data.get_group_members(gid): - if member['sender']: s = 's' + if member['sender']: + s = 's' else: s = ' ' - if member['admin']: s = 'a'+s - else: s = ' '+s - s += ' %s [%s]'%(member['alias'],member['number']) + if member['admin']: + s = 'a' + s + else: + s = ' ' + s + s += ' %s [%s]' % (member['alias'], member['number']) print(s) elif sys.argv[1] == 'add-member': if len(sys.argv) != 5: usage() - number,alias,group = sys.argv[2:] + number, alias, group = sys.argv[2:] number = core.normalize_number(number) if not number: print "number error!" @@ -61,7 +65,7 @@ elif sys.argv[1] == 'add-member': elif sys.argv[1] == 'rm-member': if len(sys.argv) != 4: usage() - number,group = sys.argv[2:] + number, group = sys.argv[2:] number = core.normalize_number(number) if not number: print "number error!" @@ -80,13 +84,13 @@ elif sys.argv[1] == 'set-sender': if len(sys.argv) != 4: usage() - number,group = sys.argv[2:] + number, group = sys.argv[2:] number = core.normalize_number(number) group_id = data.get_group_id(group) if not group_id: print "group error!" sys.exit(1) - mid = data.get_member_id(number, group_id); + mid = data.get_member_id(number, group_id) if not mid: print "no such number!" sys.exit(1) @@ -95,13 +99,13 @@ elif sys.argv[1] == 'set-admin': if len(sys.argv) != 4: usage() - number,group = sys.argv[2:] + number, group = sys.argv[2:] number = core.normalize_number(number) group_id = data.get_group_id(group) if not group_id: print "group error!" sys.exit(1) - mid = data.get_member_id(number, group_id); + mid = data.get_member_id(number, group_id) if not mid: print "no such number!" sys.exit(1) @@ -109,12 +113,12 @@ elif sys.argv[1] == 'set-admin': elif sys.argv[1] == 'fake-incoming': if len(sys.argv) != 5: usage() - src,phone,msg = sys.argv[2:] + src, phone, msg = sys.argv[2:] src = core.normalize_number(src) if not src: print "number error!" sys.exit(1) - data.fake_incoming(src,phone,msg) + data.fake_incoming(src, phone, msg) elif sys.argv[1] == 'add-webadmin': if len(sys.argv) != 4: usage() @@ -122,4 +126,3 @@ elif sys.argv[1] == 'add-webadmin': data.add_webuser(login, password, 2) else: usage() - diff --git a/web.py b/web.py index d87ba52..566e368 100644 --- a/web.py +++ b/web.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- import sqlite3 -from flask import Flask,render_template,request,session,flash,redirect, \ - url_for,abort, g +from flask import Flask, render_template, request, session, flash, redirect, \ + url_for, abort, g from config import config import core @@ -15,15 +15,18 @@ app.debug = config.debug app.secret_key = config.flask_key + def connect_db(): """Returns a new connection to the database.""" return sqlite3.connect(config.db) + @app.before_request def before_request(): """Make sure we are connected to the database each request.""" g.db = connect_db() + @app.teardown_request def teardown_request(exception): """Closes the database again at the end of the request.""" @@ -61,7 +64,7 @@ def groups(name=None): gid = data.add_group(request.form['name'], request.form['keyword'], config.default_phone) data.set_webuser_group(session.get('userid'), gid) - return redirect(url_for('groups')+request.form['name']) + return redirect(url_for('groups') + request.form['name']) members = None if not session.get('admin'): @@ -80,7 +83,8 @@ def groups(name=None): return render_template('group.html', name=name, members=members, groups=groups, error=error) -@app.route('/removemember/') + +@app.route('/removemember/') def remove_member(mid=None): data = core.Data() info = data.get_member_info(mid) @@ -94,7 +98,8 @@ def remove_member(mid=None): data.remove_number(mid) return redirect(url_for('groups', name=info['groupName'])) -@app.route('/removegroup/') + +@app.route('/removegroup/') def remove_group(name): data = core.Data() gid = data.get_group_id(name) @@ -109,7 +114,8 @@ def remove_group(name): data.remove_group(gid) return redirect(url_for('groups')) -@app.route("/settings", methods=['POST','GET']) + +@app.route("/settings", methods=['POST', 'GET']) def settings(): if not session.get('admin'): abort(401) @@ -125,19 +131,21 @@ def settings(): data.set_webuser_pw(request.form['userid'], request.form['pw']) groups = data.get_groups() - return render_template('settings.html', webusers=data.get_webusers(),groups=groups) + return render_template('settings.html', webusers=data.get_webusers(), groups=groups) -@app.route('/removewebusergroup//') -def remove_webuser_group(uid,gid): + +@app.route('/removewebusergroup//') +def remove_webuser_group(uid, gid): if not session.get('admin'): abort(401) data = core.Data() - data.remove_webuser_group(uid,gid) + data.remove_webuser_group(uid, gid) return redirect(url_for('settings')) -@app.route('/removewebuser/') + +@app.route('/removewebuser/') def remove_webuser(uid): if not session.get('admin'): abort(401) @@ -146,13 +154,14 @@ def remove_webuser(uid): data.remove_webuser(uid) return redirect(url_for('settings')) + @app.route("/") @app.route('/login', methods=['POST', 'GET']) def login(): error = None if request.method == 'POST': data = core.Data() - userid,privilege = data.check_webuser_login(request.form['username'], + userid, privilege = data.check_webuser_login(request.form['username'], request.form['password']) if privilege: session['username'] = request.form['username'] @@ -165,6 +174,7 @@ def login(): error = 'Invalid username/password' return render_template('login.html', error=error) + @app.route('/logout') def logout(): session.pop('username', None) @@ -176,4 +186,3 @@ def logout(): if __name__ == '__main__': app.run() -