# -*- coding: utf-8 -*-
# Copyright (C) 2011 godric <godric@0x3f.fr>
# License: WTFPL
#
# Changelog
# v1.1
# + re-implemented state change
# + changed pattern and color in nicklist
SCRIPT_NAME = 'weenetsoul'
SCRIPT_AUTHOR = 'godric <godric@0x3f.fr>'
SCRIPT_VERSION = '1.1'
SCRIPT_LICENSE = 'WTFPL'
SCRIPT_DESC = 'Netsoul protocol for WeeChat'
import weechat
import time, socket, hashlib, urllib, datetime, re
########################################
# Netsoul network functions
########################################
class weeNSUser :
def __init__(self, login, data = '', location = '', group = '', state = '', state_time = 0, machtype = '',
ip = '', connection_time = 0, lastseen_time = 0, user_trust = 0, client_trust = 0, fd = '') :
self.login = login
self.fd = fd
self.data = data
self.location = location
self.group = group
self.state = state
self.state_time = int(state_time)
self.machtype = machtype
self.ip = ip
self.connection_time = int(connection_time)
self.lastseen_time = int(lastseen_time)
self.user_trust = int(user_trust)
self.client_trust = int(client_trust)
self.nick = None
def prnt(self, buffer = '', prefix = '') :
hcolor = weechat.color('separator')
ncolor = weechat.color('chat_nick')
prefix = prefix+hcolor
weechat.prnt(buffer, "%s%s=============== %s[%s] (%s) %s===============\n" % (prefix, hcolor, ncolor, self.login, self.fd, hcolor))
weechat.prnt(buffer, '%s Host . . . . . . . . : %s\n' % (prefix, self.ip))
weechat.prnt(buffer, "%s Machine type . . . . : %s\n" % (prefix, self.machtype))
weechat.prnt(buffer, "%s Group. . . . . . . . : %s\n" % (prefix, self.group))
weechat.prnt(buffer, "%s State. . . . . . . . : %s\n" % (prefix, self.state))
weechat.prnt(buffer, "%s Data . . . . . . . . : %s\n" % (prefix, self.data))
weechat.prnt(buffer, "%s Location . . . . . . : %s\n" % (prefix, self.location))
weechat.prnt(buffer, "%s Connected at . . . . : %s\n" % (prefix, datetime.datetime.fromtimestamp(self.connection_time).strftime('%d/%m/%Y %H:%M:%S')))
weechat.prnt(buffer, "%s Last Activity. . . . : %s\n" % (prefix, datetime.datetime.fromtimestamp(self.lastseen_time).strftime('%d/%m/%Y %H:%M:%S')))
weechat.prnt(buffer, "%s Last status change . : %s\n" % (prefix, datetime.datetime.fromtimestamp(self.state_time).strftime('%d/%m/%Y %H:%M:%S')))
weechat.prnt(buffer, "%s Trust (User/client). : (%d/%d)\n" % (prefix, self.user_trust, self.client_trust))
weechat.prnt(buffer, "%s%s==============================================" % (prefix, hcolor))
class weeNSChat :
def __init__(self, server, login = None, fd = None) :
self.server = server
self.login = login
self.fd = fd
self.buffer = weechat.buffer_new('Netsoul.temp', 'weeNS_buffer_input_cb', '', 'weeNS_buffer_close_cb', '')
weechat.buffer_set(self.buffer, "display", "auto")
self.updateBuffer(login)
self.server.chats.append(self)
def updateBuffer(self, login) :
self.login = login
fd = self.fd or '*'
login = self.login or 'unknown'
weechat.buffer_set(self.buffer, "title", 'Netsoul chat with %s on %s' % (login, fd))
weechat.buffer_set(self.buffer, 'name', 'Netsoul.%s:%s' % (login, fd))
weechat.buffer_set(self.buffer, 'short_name', 'Netsoul.%s:%s' % (login, fd))
def recv(self, login, message) :
if login != self.login :
self.updateBuffer(login)
weechat.prnt(self.buffer, '%s%s\t%s' % (weechat.color("chat_nick"), self.login, message))
def send(self, message) :
weechat.prnt(self.buffer, '%s%s\t%s' % (weechat.color("chat_nick_self"), self.server.getOption('login'), message))
recipient = self.login if self.fd is None else ':'+self.fd
self.server._ns_user_cmd_msg_user(recipient, message)
def delete(self) :
if self.buffer is not None :
return weechat.buffer_close(self.buffer)
self.server.chats.remove(self)
class weeNSServer :
def __init__(self) :
global weeNS_server_opt, weeNS_config_file, weeNS_config_server_section
self.buffer = None
self.hook_fd = None
self.socket = None
self.netbuffer = ''
self.chats = []
self.contacts = {}
self.options = {
'host' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'host', 'string', 'Server Host (default: ns-server.epita.fr)', '', 0, 0,
'ns-server.epita.fr', 'ns-server.epita.fr', 0, '', '', '', '', '', ''),
'port' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'port', 'string', 'Server Port (default: 4242)', '', 0, 0,
'4242', '4242', 0, '', '', '', '', '', ''),
'login' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'login', 'string', 'User login (ie: login_x)', '', 0, 0,
'login_x', 'login_x', 0, '', '', '', '', '', ''),
'password' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'password', 'string', 'User password (ie: your SOCKS password)', '', 0, 0,
'xxxxxx', 'xxxxxx', 0, '', '', '', '', '', ''),
'location' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'location', 'string', 'User location (ie: at home)', '', 0, 0,
'-', '-', 0, '', '', '', '', '', ''),
'data' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'data', 'string', 'User data (ie: j\'aime les chips)', '', 0, 0,
'-', '-', 0, '', '', '', '', '', ''),
'contacts' : weechat.config_new_option(weeNS_config_file, weeNS_config_server_section,
'contacts', 'string', 'Comma separated login list (ie: sb,rn)', '', 0, 0,
'', '', 0, '', '', '', '', '', '')}
def getOption(self, opt_name) :
return weechat.config_string(self.options[opt_name])
def getChatByRecipient(self, login = None, fd = None, create = False) :
for chat in self.chats :
if (fd is not None and chat.fd == fd) or (chat.login == login and chat.fd == fd) :
return chat
if create is True :
return weeNSChat(self, login, fd)
return None
def getChatByBuffer(self, buffer) :
for chat in self.chats :
if chat.buffer == buffer :
return chat
return server
def createBuffer(self) :
if self.buffer == None :
self.buffer = weechat.buffer_new('Netsoul', 'weeNS_buffer_input_cb', '', 'weeNS_buffer_close_cb', '')
weechat.buffer_set(self.buffer, "nicklist", "1")
weechat.buffer_set(self.buffer, "nicklist_display_groups", "1")
weechat.buffer_set(self.buffer, "display", "auto")
def connect(self) :
self.createBuffer()
self.disconnect()
weechat.prnt(self.buffer, 'Connecting to %s:%s' % (self.getOption('host'), self.getOption('port')))
for res in socket.getaddrinfo(self.getOption('host'), self.getOption('port'), socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE) :
af, socktype, proto, canonname, sa = res
try :
self.socket = socket.socket(af, socktype, proto)
self.socket.connect(sa)
except socket.error, msg:
self.socket = None
continue
break
if self.socket is None :
weechat.prnt(self.buffer, 'Could not connect')
self.hook_fd = weechat.hook_fd(self.socket.fileno(), 1, 0, 0, 'weeNS_hook_fd_cb', '')
def recv(self) :
# while 1337 :
data = self.socket.recv(512)
if len(data) == 0 :
return self.disconnect()
self.netbuffer += data
while "\n" in self.netbuffer :
line, ignored, buffer = self.netbuffer.partition("\n")
index = len(line) + 1
self.netbuffer = self.netbuffer[index:]
weechat.prnt(self.buffer, '%s[%s]' % (
weechat.prefix('join'),
line))
self._ns_parse(line)
def send(self, data) :
weechat.prnt(self.buffer, '%s[%s]' % (weechat.prefix('quit'), data))
self.socket.send('%s\n' % data)
def isConnected(self) :
return (self.socket is not None)
def disconnect(self) :
if self.hook_fd is not None :
weechat.unhook(self.hook_fd)
if self.socket is not None :
self.socket.close()
if self.buffer is not None :
self.contacts = {}
weechat.nicklist_remove_all(self.buffer)
weechat.prnt(self.buffer, '... Disconnected ...')
self.hook_fd = None
self.socket = None
self.contacts = {}
def delete(self) :
for chat in reversed(self.chats) :
chat.delete()
self.disconnect()
if self.buffer is not None :
weechat.buffer_close(self.buffer)
self.buffer = None
for option in self.options.keys():
weechat.config_option_free(option)
def setupNicklist(self) :
contact_list = self.getOption('contacts').replace(' ', '').split(',')
for login in contact_list :
weechat.nicklist_add_group(self.buffer, '', login, 'lightcyan', 1)
self.contacts[login] = {}
self._ns_user_cmd_who('{%s}' % ','.join(contact_list))
self._ns_user_cmd_watch_log_user(','.join(contact_list))
def updateNicklist(self, user, remove = False) :
group = weechat.nicklist_search_group(self.buffer, '', user.login)
if group is not None and user.login in self.contacts :
if user.fd in self.contacts[user.login] :
weechat.nicklist_remove_nick(self.buffer, self.contacts[user.login][user.fd].nick)
del self.contacts[user.login][user.fd]
if remove is False :
user.nick = weechat.nicklist_add_nick(self.buffer, group, ' :%s%s@%s%s' % (user.fd, weechat.color('separator'), weechat.color('default'), user.location), '', '', '', 1)
self.contacts[user.login][user.fd] = user
def _ns_auth_ag(self) :
self.send("auth_ag ext_user none none")
def _ns_ext_user_log(self, secret, ip, port) :
location = urllib.quote(self.getOption('location'))
data = urllib.quote(self.getOption('data'))
crypt = hashlib.md5('%s-%s/%s%s' % (secret, ip, port, self.getOption('password'))).hexdigest()
self.send("ext_user_log %s %s %s %s" % (self.getOption('login'), crypt, location, data))
def _ns_user_cmd_msg_user(self, login, msg) :
msg = unicode(msg, 'utf-8').encode('iso-8859-1')
self.send("user_cmd msg_user %s msg %s" % (login, urllib.quote(msg)))
def _ns_user_cmd_who(self, login) :
self.send("user_cmd who %s" % login)
def _ns_user_cmd_watch_log_user(self, friends) :
self.send("user_cmd watch_log_user {%s}" % friends)
def _ns_state(self, state) :
self.send("state %s:%s" % (state, int(time.time())))
def _ns_parse(self, data) :
arglist = data.split(' ')
if arglist[0] == 'salut' :
self._ns_parse_salut(arglist)
elif arglist[0] == 'ping' :
self._ns_parse_ping(arglist)
elif arglist[0] == 'user_cmd' :
if arglist[3] == 'msg' :
self._ns_parse_user_cmd_msg(arglist)
elif arglist[3] == 'who':
self._ns_parse_user_cmd_who(arglist)
elif arglist[3] == 'login' :
self._ns_parse_user_cmd_login(arglist)
elif arglist[3] == 'logout' :
self._ns_parse_user_cmd_logout(arglist)
elif arglist[3] == 'state' :
self._ns_parse_user_cmd_state(arglist)
def _ns_parse_from(self, str) :
r = re.compile('([0-9]+):user:([0-9]+)/([0-9]+):([_a-z]+)@([0-9.]+):([^ :]+):([^ :]+):([^ :]+)');
match = re.match(r, str)
groups = match.groups()
user = weeNSUser(fd = groups[0], client_trust = groups[1], user_trust = groups[2], login = groups[3],
ip = groups[4], machtype = groups[5], location = groups[6], group = groups[7])
return user
def _ns_parse_salut(self, arglist) :
self._ns_auth_ag()
self._ns_ext_user_log(arglist[2], arglist[3], arglist[4])
self.setupNicklist()
def _ns_parse_ping(self, arglist) :
self.send("ping %s" % arglist[1])
def _ns_parse_user_cmd_msg(self, arglist) :
user = self._ns_parse_from(arglist[1])
msg = urllib.unquote(arglist[4])
msg = unicode(msg, 'iso-8859-1').encode('utf-8')
pchat = self.getChatByRecipient(fd = user.fd)
if pchat is not None :
pchat.recv(user.login, msg)
gchat = self.getChatByRecipient(user.login, create = (pchat is None))
if gchat is not None :
gchat.recv(user.login, msg)
def _ns_parse_user_cmd_who(self, arglist) :
r = re.compile('who ([0-9]+) ([_a-z]+) ([0-9.]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([^ ]+) ([^ ]+) ([^ ]+) ([^ :]+):{0,1}([0-9]+){0,1} ([^ ]+)')
match = re.match(r, ' '.join(arglist[3:]))
if match is not None :
groups = match.groups()
user = weeNSUser(fd = groups[0], login = groups[1], ip = groups[2], connection_time = groups[3], lastseen_time = groups[4], user_trust = groups[5], client_trust = groups[6], machtype = groups[7], location = urllib.unquote(groups[8]), group = groups[9], state = urllib.unquote(groups[10]), state_time = groups[11] or 0, data = urllib.unquote(groups[12]))
self.updateNicklist(user)
user.prnt(self.buffer, weechat.prefix('network'))
def _ns_parse_user_cmd_state(self, arglist) :
user = self._ns_parse_from(arglist[1])
if user.login in self.contacts and user.fd in self.contacts[user.login] :
user = self.contacts[user.login][user.fd]
state = arglist[4].split(':')
user.state = urllib.unquote(state[0])
user.state_time = int(time.time())
self.updateNicklist(user)
chat = self.getChatByRecipient(fd = user.fd)
if chat is not None :
weechat.prnt(chat.buffer, '%s%s%s changed state : %s' % (weechat.prefix('network'), weechat.color('nick_color'), user.login, user.state))
def _ns_parse_user_cmd_login(self, arglist) :
user = self._ns_parse_from(arglist[1])
self._ns_user_cmd_who(':%s' % user.fd)
chat = self.getChatByRecipient(fd = user.fd)
if chat is not None :
weechat.prnt(chat.buffer, '%s%s%s has connected' % (weechat.prefix('join'), weechat.color('nick_color'), user.login))
def _ns_parse_user_cmd_logout(self, arglist) :
user = self._ns_parse_from(arglist[1])
self.updateNicklist(user, remove = True)
chat = self.getChatByRecipient(fd = user.fd)
if chat is not None :
weechat.prnt(chat.buffer, '%s%s%s has disconnected' % (weechat.prefix('quit'), weechat.color('nick_color'), user.login))
########################################
# Weechat callbacks
########################################
def weeNS_buffer_input_cb(data, buffer, input_data) :
global server
if server.isConnected() :
server.getChatByBuffer(buffer).send(input_data)
return weechat.WEECHAT_RC_OK
def weeNS_buffer_close_cb(data, buffer) :
global server
context = server.getChatByBuffer(buffer)
context.buffer = None
context.delete()
return weechat.WEECHAT_RC_OK
def weeNS_server_section_read_cb(data, config_file, section, option_name, value) :
global server
return weechat.config_option_set(server.options[option_name], value, 0)
def weeNS_hook_fd_cb(data, fd) :
global server
server.recv()
return weechat.WEECHAT_RC_OK
def weeNS_script_unload_cb() :
global weeNS_config_file
weechat.config_write(weeNS_config_file)
return weechat.WEECHAT_RC_OK
def weeNS_hook_cmd_ns(data, buffer, args) :
global server
buffer = server.buffer if server.buffer is not None else ''
arglist = args.split(' ')
if arglist[0] == 'connect' and not server.isConnected():
server.connect()
elif arglist[0] == 'disconnect' and server.isConnected() :
server.disconnect()
elif arglist[0] == 'send' and server.isConnected() and len(arglist) > 2 :
match = re.match(re.compile('\A([a-z_]+)|:([0-9]+)\Z'), arglist[1])
if match is not None :
groups = match.groups()
msg = ' '.join(arglist[2:])
server.getChatByRecipient(groups[0], groups[1], create = True).send(msg)
else :
weechat.prnt(buffer, 'Message recipient must be of type login_x[:fd]')
elif arglist[0] == 'who' and server.isConnected() and len(arglist) > 1 :
server._ns_user_cmd_who(arglist[1])
elif arglist[0] == 'state' and server.isConnected() and len(arglist) > 1 :
server._ns_state(arglist[1])
else :
weechat.prnt(buffer, '%sNo such command, wrong argument count, or you need to (dis)connect' % weechat.prefix('error'))
return weechat.WEECHAT_RC_OK
######################################
# Main
######################################
if __name__ == "__main__" :
if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', 'weeNS_script_unload_cb'):
weechat.hook_command('ns', 'weeNetsoul main command', 'connect | disconnect | send <login> <msg> | state <status> | who <login>',
" connect : Connect\n"
" disconnect : Diconnect\n"
" send : Send <msg> to <login> (any client) or to <:fd> (unique client)\n"
" state : Change status to <status> (en ligne/actif/whatever)\n"
" who : Show infos about <login>\n",
'connect|disconnect|send|state|who', 'weeNS_hook_cmd_ns', '')
weeNS_config_file = weechat.config_new(SCRIPT_NAME, '', '')
weeNS_config_server_section = weechat.config_new_section(weeNS_config_file, 'server', 0, 0, 'weeNS_server_section_read_cb', '', '', '', '', '', '', '', '', '')
server = weeNSServer()
weechat.config_read(weeNS_config_file)
weechat.config_write(weeNS_config_file)