Basic implementation of the roster and one to one conversations

parent c486b78b
......@@ -58,13 +58,7 @@ def debug(string):
a CLI software
"""
fdes = open("/tmp/debug", 'a')
try:
fdes.write(string)
except:
try:
fdes.write(string.encode('utf-8'))
except:
fdes.write(string.encode('utf-8'))
fdes.write(string)
fdes.close()
def get_base64_from_file(path):
......
......@@ -22,8 +22,32 @@ class Contact(object):
"""
def __init__(self, jid):
self._jid = JID(jid) # a SleekXMPP jid object
self._display_name = None
self.groups = [] # a list of groups the contact is in
self._display_name = ''
self._subscription = 'none'
self._ask = None
self._status = ''
self._presence = 'unavailable'
self._priority = 0
self._groups = [] # a list of groups the contact is in
def getJid(self):
def set_ask(self, ask):
self._ask = ask
def set_subscription(self, sub):
self._subscription = sub
def get_jid(self):
return self._jid
def __repr__(self):
return '%s' % self._jid
def set_priority(self, priority):
assert isinstance(priority, int)
self._priority = priority
def set_presence(self, pres):
self._presence = pres
def get_presence(self):
return self._presence
......@@ -35,15 +35,17 @@ import theme
import multiuserchat as muc
from handler import Handler
from config import config
from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab
from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
from user import User
from room import Room
from roster import Roster
from contact import Contact
from message import Message
from text_buffer import TextBuffer
from keyboard import read_char
from common import is_jid_the_same, jid_get_domain, jid_get_resource, is_jid
from common import debug
# http://xmpp.org/extensions/xep-0045.html#errorstatus
ERROR_AND_STATUS_CODES = {
'401': _('A password is required'),
......@@ -75,8 +77,9 @@ class Gui(object):
self.init_curses(self.stdscr)
self.xmpp = xmpp
default_tab = InfoTab(self.stdscr, "Info") if self.xmpp.anon\
else RosterInfoTab(self.stdscr, self.xmpp.roster)
else RosterInfoTab(self.stdscr)
self.tabs = [default_tab]
self.roster = Roster()
# a unique buffer used to store global informations
# that are displayed in almost all tabs, in an
# information window.
......@@ -136,8 +139,10 @@ class Gui(object):
self.xmpp.add_event_handler("groupchat_presence", self.on_groupchat_presence)
self.xmpp.add_event_handler("groupchat_message", self.on_groupchat_message)
self.xmpp.add_event_handler("message", self.on_message)
self.xmpp.add_event_handler("presence", self.on_presence)
self.xmpp.add_event_handler("got_online" , self.on_got_online)
self.xmpp.add_event_handler("got_offline" , self.on_got_offline)
self.xmpp.add_event_handler("roster_update", self.on_roster_update)
# self.xmpp.add_event_handler("presence", self.on_presence)
def grow_information_win(self):
"""
......@@ -159,6 +164,25 @@ class Gui(object):
tab.on_info_win_size_changed(self.information_win_size, self.stdscr)
self.refresh_window()
def on_got_offline(self, presence):
jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
return
contact.set_presence('unavailable')
self.information('%s is not offline' % (contact.get_jid()), "Roster")
def on_got_online(self, presence):
jid = presence['from']
contact = self.roster.get_contact_by_jid(jid.bare)
if not contact:
return
status = presence['type']
priority = presence.getPriority()
contact.set_presence(status)
contact.set_priority(priority)
self.information("%s is now online (%s)" % (contact.get_jid(), status), "Roster")
def on_connected(self, event):
"""
Called when we are connected and authenticated
......@@ -409,6 +433,15 @@ class Gui(object):
"""
from common import debug
debug('MESSAGE: %s\n' % (message))
jid = message['from'].bare
room = self.get_conversation_by_jid(jid)
if not room:
room = self.open_conversation_window(jid, False)
if not room:
return
body = message['body']
self.add_message_to_text_buffer(room, body, None, jid)
self.refresh_window()
return
def on_presence(self, presence):
......@@ -423,10 +456,24 @@ class Gui(object):
A subscription changed, or we received a roster item
after a roster request, etc
"""
from common import debug
debug("ROSTER UPDATE: %s\n" % (iq))
for subscriber in iq['roster']['items']:
debug("subscriber: %s\n" % (iq['roster']['items'][subscriber]['subscription']))
# debug('Roster Update: \n%s\n\n' % iq)
for item in iq.findall('{jabber:iq:roster}query/{jabber:iq:roster}item'):
jid = item.attrib['jid']
contact = self.roster.get_contact_by_jid(jid)
if not contact:
contact = Contact(jid)
self.roster.add_contact(contact, jid)
if 'ask' in item.attrib:
contact.set_ask(item.attrib['ask'])
else:
contact.set_ask(None)
if item.attrib['subscription']:
contact.set_subscription(item.attrib['subscription'])
groups = item.findall('{jabber:iq:roster}group')
self.roster.edit_groups_of_contact(contact, [group.text for group in groups])
if isinstance(self.current_tab(), RosterInfoTab):
# TODO refresh roster_win only
self.refresh_window()
def resize_window(self):
"""
......@@ -456,6 +503,16 @@ class Gui(object):
"""
return self.tabs[0]
def get_conversation_by_jid(self, jid):
"""
Return the room of the ConversationTab with the given jid
"""
for tab in self.tabs:
if isinstance(tab, ConversationTab):
if tab.get_name() == jid:
return tab.get_room()
return None
def get_room_by_name(self, name):
"""
returns the room that has this name
......@@ -489,7 +546,8 @@ class Gui(object):
Refresh everything
"""
self.current_tab().set_color_state(theme.COLOR_TAB_CURRENT)
self.current_tab().refresh(self.tabs, self.information_buffer)
self.current_tab().refresh(self.tabs, self.information_buffer, self.roster)
doupdate()
def open_new_room(self, room, nick, focus=True):
"""
......@@ -584,6 +642,26 @@ class Gui(object):
self.add_message_to_text_buffer(room, _('You can join the room with an other nick, by typing "/join /other_nick"'))
self.refresh_window()
def open_conversation_window(self, room_name, focus=True):
"""
open a new conversation tab and focus it if needed
"""
r = Room(room_name, self.xmpp.fulljid)
new_tab = ConversationTab(self.stdscr, r, self.information_win_size)
# insert it in the rooms
if self.current_tab().nb == 0:
self.tabs.append(new_tab)
else:
for ta in self.tabs:
if ta.nb == 0:
self.tabs.insert(self.tabs.index(ta), new_tab)
break
if focus: # focus the room if needed
self.command_win('%s' % (new_tab.nb))
# self.window.new_room(r)
self.refresh_window()
return r
def open_private_window(self, room_name, user_nick, focus=True):
complete_jid = room_name+'/'+user_nick
for tab in self.tabs: # if the room exists, focus it and return
......@@ -1178,8 +1256,31 @@ class Gui(object):
if not key:
return
res = self.current_tab().on_input(key)
if key in ('^J', '\n'):
if not res:
return
if key in ('^J', '\n') and isinstance(res, str):
self.execute(res)
else:
# we did "enter" with an empty input in the roster
self.on_roster_enter_key(res)
def on_roster_enter_key(self, roster_row):
"""
when enter is pressed on the roster window
"""
if isinstance(roster_row, Contact):
r = Room(roster_row.get_jid().full, self.xmpp.fulljid)
new_tab = ConversationTab(self.stdscr, r, self.information_win_size)
debug('%s\n'% new_tab)
# insert it in the tabs
if self.current_tab().nb == 0:
self.tabs.append(new_tab)
else:
for ta in self.tabs:
if ta.nb == 0:
self.tabs.insert(self.tabs.index(ta), new_tab)
break
self.refresh_window()
def execute(self,line):
"""
......@@ -1203,6 +1304,10 @@ class Gui(object):
def command_say(self, line):
if isinstance(self.current_tab(), PrivateTab):
muc.send_private_message(self.xmpp, self.current_tab().get_name(), line)
elif isinstance(self.current_tab(), ConversationTab): # todo, special case
muc.send_private_message(self.xmpp, self.current_tab().get_name(), line)
if isinstance(self.current_tab(), PrivateTab) or\
isinstance(self.current_tab(), ConversationTab):
self.add_message_to_text_buffer(self.current_tab().get_room(), line, None, self.current_tab().get_room().own_nick)
elif isinstance(self.current_tab(), MucTab):
muc.send_groupchat_message(self.xmpp, self.current_tab().get_name(), line)
......
......@@ -52,7 +52,7 @@ def read_char(s):
if first == 27:
(first, c) = get_next_byte(s)
if not isinstance(first, int):
return Nones
return None
return "M-"+chr(first)
if 194 <= first:
(code, c) = get_next_byte(s) # 2 bytes char
......
......@@ -16,30 +16,146 @@
from contact import Contact
from common import debug
class Roster(object):
def __init__(self):
self._contacts = {} # key = jid; value = Contact()
self._roster_groups = []
def add_contact(self, contact, jid):
"""
Add a contact to the contact list
"""
assert jid not in self._contacts
self._contacts[jid] = contact
def get_contact_len(self):
return len(self._contacts.keys())
def get_contact_by_jid(self, jid):
if jid in self._contacts:
return self._contacts[jid]
return None
def edit_groups_of_contact(self, contact, groups):
"""
Edit the groups the contact is in
Add or remove RosterGroup if needed
"""
# add the contact to each group he is in
for group in groups:
if group in contact._groups:
continue
else:
# create the group if it doesn't exist yet
contact._groups.append(group)
self.add_contact_to_group(group, contact)
# remove the contact from each group he is not in
for group in contact._groups:
if group not in groups:
# the contact is not in the group anymore
self.remove_contact_from_group(group, contact)
def remove_contact_from_group(self, group_name, contact):
"""
Remove the contact from the group.
Remove also the group if this makes it empty
"""
for group in self._roster_groups:
if group.name == group_name:
group.remove_contact(contact)
if group.is_empty():
self._roster_groups.remove(group)
return
def add_contact_to_group(self, group_name, contact):
"""
Add the contact to the group.
Create the group if it doesn't already exist
"""
for group in self._roster_groups:
if group.name == group_name:
group.add_contact(contact)
return
new_group = RosterGroup(group_name)
self._roster_groups.append(new_group)
new_group.add_contact(contact)
# def ordered_by_group(self, dic_roster, order):
# # ordered by contact
# for jid in dic_roster:
# if not dic_roster[jid]['in_roster']:
# continue
# self.contact_number += 1
# groups=dic_roster[jid]['groups']
# if not groups:
# groups = ['(none)']
# new_contact = Contact(jid, name=dic_roster[jid]['name'],
# groups=groups,
# subscription=dic_roster[jid]['subscription'],
# presence=dic_roster[jid]['presence'])
# for group in groups:
# self.add_contact_to_group(group, new_contact)
# # debug('Jid:%s, (%s)\n' % (jid, dic_roster[jid]))
# debug('\n')
def get_groups(self):
return self._roster_groups
def __len__(self):
"""
Return the number of line that would be printed
"""
l = 0
for group in self._roster_groups:
l += 1
if not group.folded:
for contact in group.get_contacts():
l += 1
return l
def __repr__(self):
ret = '== Roster:\nContacts:\n'
for contact in self._contacts:
ret += '%s\n' % (contact,)
ret += 'Groups\n'
for group in self._roster_groups:
ret += '%s\n' % (group,)
return ret + '\n'
class RosterGroup(object):
"""
Defines the roster
A RosterGroup is a group containing contacts
It can be Friends/Family etc, but also can be
Online/Offline or whatever
"""
def __init__(self):
self._contacts = {}
def __init__(self, name, folded=False):
# debug('New group: %s \n' % name)
self._contacts = []
self.name = name
self.folded = folded # if the group content is to be shown
def is_empty(self):
return len(self._contacts) == 0
def addContactToList(self, contact):
def remove_contact(self, contact):
"""
Remove a Contact object to the list
"""
assert isinstance(contact, Contact)
assert contact not in self._contacts
self._contacts[contact.getJid().bare] = contact
assert contact in self._contacts
self._contacts.remove(contact)
def getContacts(self):
def add_contact(self, contact):
"""
returns all the contacts in a list
TODO: sorted
TODO: only some contacts (online only for example)
append a Contact object to the list
"""
return [contact for contact in self._contacts.keys()]
assert isinstance(contact, Contact)
assert contact not in self._contacts
self._contacts.append(contact)
def __len__(self):
return len(self._contacts)
def get_contacts(self):
return self._contacts
def getContact(self, bare_jid):
if bare_jid not in self._contacts:
return None
return self._contacts[bare_jid]
def __repr__(self):
return '<Roster_group: %s; %s>' % (self.name, self._contacts)
......@@ -44,7 +44,7 @@ class Tab(object):
else:
self.visible = True
def refresh(self, tabs, informations):
def refresh(self, tabs, informations, roster):
"""
Called on each screen refresh (when something has changed)
"""
......@@ -136,7 +136,7 @@ class InfoTab(Tab):
self.text_win.resize(self.height-2, self.width, 0, 0, stdscr, self.visible)
self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible)
def refresh(self, tabs, informations):
def refresh(self, tabs, informations, _):
self.text_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
self.input.refresh()
......@@ -206,7 +206,7 @@ class MucTab(Tab):
self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible)
def refresh(self, tabs, informations):
def refresh(self, tabs, informations, _):
self.topic_win.refresh(self._room.topic)
self.text_win.refresh(self._room)
self.v_separator.refresh()
......@@ -311,7 +311,7 @@ class PrivateTab(Tab):
self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible)
def refresh(self, tabs, informations):
def refresh(self, tabs, informations, _):
self.text_win.refresh(self._room)
self.info_header.refresh(self._room)
self.info_win.refresh(informations)
......@@ -358,12 +358,11 @@ class PrivateTab(Tab):
class RosterInfoTab(Tab):
"""
A tab, splitted in two, containg the roster and infos
A tab, splitted in two, containing the roster and infos
"""
def __init__(self, stdscr, roster):
def __init__(self, stdscr):
Tab.__init__(self, stdscr)
self.name = "Roster"
self.roster = roster
roster_width = self.width//2
info_width = self.width-roster_width-1
self.v_separator = window.VerticalSeparator(self.height-2, 1, 0, roster_width, stdscr, self.visible)
......@@ -383,8 +382,8 @@ class RosterInfoTab(Tab):
self.roster_win.resize(self.height-2, roster_width, 0, 0, stdscr, self.visible)
self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible)
def refresh(self, tabs, informations):
self.roster_win.refresh(self.roster)
def refresh(self, tabs, informations, roster):
self.roster_win.refresh(roster)
self.v_separator.refresh()
self.info_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
......@@ -400,6 +399,8 @@ class RosterInfoTab(Tab):
self._color_state = color
def on_input(self, key):
if key in ('\n', '^J', '^M') and self.input.is_empty():
return self.on_enter()
return self.input.do_command(key)
def on_lose_focus(self):
......@@ -412,10 +413,81 @@ class RosterInfoTab(Tab):
return False
def on_scroll_down(self):
debug('TODO DOWN')
self.roster_win.move_cursor_down()
def on_scroll_up(self):
debug('TODO UP')
self.roster_win.move_cursor_up()
def on_info_win_size_changed(self):
return
def on_info_win_size_changed(self, _, __):
pass
def on_enter(self):
debug('%s\n' % (self.roster_win.get_selected_row()))
return self.roster_win.get_selected_row()
class ConversationTab(Tab):
"""
The tab containg a normal conversation (someone from our roster)
"""
def __init__(self, stdscr, room, info_win_size):
Tab.__init__(self, stdscr)
self.info_win_size = info_win_size
self._room = room
self.text_win = window.TextWin(self.height-2-self.info_win_size, self.width, 0, 0, stdscr, self.visible)
self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.info_win_size, self.width, self.height-2-self.info_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
Tab.resize(self, stdscr)
self.text_win.resize(self.height-2-self.info_win_size, self.width, 0, 0, stdscr, self.visible)
self.info_header.resize(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible)
self.info_win.resize(self.info_win_size, self.width, self.height-2-self.info_win_size, 0, stdscr, self.visible)
self.tab_win.resize(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input.resize(1, self.width, self.height-1, 0, stdscr, self.visible)
def refresh(self, tabs, informations, _):
self.text_win.refresh(self._room)
self.info_header.refresh(self._room)
self.info_win.refresh(informations)
self.tab_win.refresh(tabs, tabs[0])
self.input.refresh()
def get_color_state(self):
if self._room.color_state == theme.COLOR_TAB_NORMAL or\
self._room.color_state == theme.COLOR_TAB_CURRENT:
return self._room.color_state
return theme.COLOR_TAB_PRIVATE
def set_color_state(self, color):
self._room.color_state = color
def get_name(self):
return self._room.name
def on_input(self, key):
return self.input.do_command(key)
def on_lose_focus(self):
self._room.set_color_state(theme.COLOR_TAB_NORMAL)
self._room.remove_line_separator()
self._room.add_line_separator()
def on_gain_focus(self):
self._room.set_color_state(theme.COLOR_TAB_CURRENT)
def on_scroll_up(self):
self._room.scroll_up(self.text_win.height-1)
def on_scroll_down(self):
self._room.scroll_down(self.text_win.height-1)
def on_info_win_size_changed(self, size, stdscr):
self.info_win_size = size
self.text_win.resize(self.height-2, self.width, 0, 0, stdscr, self.visible)
self.info_header.resize(1, self.width, self.height-3-self.info_win_size, 0, stdscr, self.visible)
self.info_win.resize(self.info_win_size, (self.width//10)*9, self.height-2-self.info_win_size, 0, stdscr, self.visible)
def get_room(self):
return self._room
......@@ -74,7 +74,8 @@ COLOR_STATUS_NONE = 0
COLOR_STATUS_DND = 21
COLOR_STATUS_AWAY = 35
COLOR_STATUS_CHAT = 28
COLOR_STATUS_UNAVAILABLE = 57
COLOR_STATUS_ONLINE = 41
# Bars
COLOR_INFORMATION_BAR = 42
COLOR_TOPIC_BAR = 42
......
......@@ -32,7 +32,6 @@ class User(object):
"""
def __init__(self, nick, affiliation, show, status, role):
from common import debug
debug('NEW USER: nick:%s, affiliation:%s, show:%s, status:%s, role:%s\n' % (nick, affiliation, show, status, role))
self.last_talked = datetime(1, 1, 1) # The oldest possible time
self.update(affiliation, show, status, role)
self.change_nick(nick)
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment