# Console Jabber Client # Copyright (C) 2004-2006 Jacek Konieczny # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 as published # by the Free Software Foundation # # 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, write to the Free Software Foundation, Inc., # 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. import string import curses import re import pyxmpp import pyxmpp.roster from cjc.plugin import PluginBase from cjc.ui import ListBuffer,ListBufferError from cjc import common from cjc import ui from cjc import cjc_globals theme_attrs=( ("roster.available_online", curses.COLOR_YELLOW,curses.COLOR_BLACK,curses.A_BOLD, curses.A_BOLD), ("roster.available_dnd", curses.COLOR_RED,curses.COLOR_BLACK,curses.A_NORMAL, curses.A_BOLD), ("roster.available_away", curses.COLOR_YELLOW,curses.COLOR_BLACK,curses.A_NORMAL, curses.A_BOLD), ("roster.available_xa", curses.COLOR_BLUE,curses.COLOR_BLACK,curses.A_NORMAL, curses.A_BOLD), ("roster.available_chat", curses.COLOR_GREEN,curses.COLOR_BLACK,curses.A_BOLD, curses.A_BOLD), ("roster.unavailable", curses.COLOR_WHITE,curses.COLOR_BLACK,curses.A_NORMAL|curses.A_DIM, curses.A_DIM), ) theme_formats=( ("roster.group", "%(group)s:"), ("roster.group_none", "unfiled:"), ("roster.group_me", "me:"), ("roster.group_unknown", "not in roster:"), ("roster.unavailable", "%[roster.unavailable] %(aflag)s%(sflag)s%(name)-20s [%(J:jid:show)s] %(J:jid:status)s"), ("roster.available", "%[roster.available_%(J:jid:show)s] %(aflag)s%(sflag)s%(name)-20s [%(J:jid:show)s] %(J:jid:status)s"), ("roster.list", "[%(T:now)s] Roster:\n%{roster_groups}\n"), ("roster.list_group", "[%(T:now)s] %(group)s:\n%{roster_group_items}"), ("roster.list_unavailable", "[%(T:now)s] %{@roster.unavailable}\n"), ("roster.list_available", "[%(T:now)s] %{@roster.unavailable}\n"), ) # virtual groups VG_ME=1 VG_UNKNOWN=2 class Plugin(PluginBase): def __init__(self,app,name): PluginBase.__init__(self,app,name) self.available_settings={ "show": ("Which items show - list of 'available','unavailable','chat'," "'online','away','xa' or 'all'",list,self.set_show), "buffer_preference": ("Preference of roster buffers when switching to the next active buffer. If 0 then the buffer is not even shown in active buffer list.",int), } self.settings={"show":["all"],"buffer_preference":2} app.add_info_handler("rostername",self.info_rostername) app.add_info_handler("rostergroups",self.info_rostergroups) app.add_event_handler("roster updated",self.ev_roster_updated) app.add_event_handler("presence changed",self.ev_presence_changed) app.add_event_handler("layout changed",self.ev_layout_changed) cjc_globals.theme_manager.set_default_attrs(theme_attrs) cjc_globals.theme_manager.set_default_formats(theme_formats) self.buffer=ListBuffer("Roster") self.buffer.preference=self.settings["buffer_preference"] self.extra_items=[] ui.activate_cmdtable("roster",self) def info_rostername(self,k,v): if not v: return None return "Roster name",v def info_rostergroups(self,k,v): if not v: return None return "Roster groups",string.join(v,",") def set_show(self,oldval,newval): self.write_all() def ev_roster_updated(self,event,arg): if not arg: for item in self.cjc.roster.get_items(): for group,it in self.extra_items: if not isinstance(it,pyxmpp.JID) or group!=VG_UNKNOWN: continue jid=item.jid if group==VG_UNKNOWN and it==jid or it.bare()==jid: self.extra_items.remove((VG_UNKNOWN,it)) if self.buffer.has_key((VG_UNKNOWN,it)): self.buffer.remove_item((VG_UNKNOWN,it)) self.write_all() return self.update_item(arg) def ev_presence_changed(self,event,arg): if arg: self.update_item(arg) def ev_layout_changed(self,event,arg): if self.cjc.roster_window: self.cjc.roster_window.set_buffer(self.buffer) def update_item(self,item): self.debug("Roster.update_item(%r): %r" % (item, item)) if isinstance(item,pyxmpp.JID): if self.cjc.roster: try: item=self.cjc.roster.get_item_by_jid(item) except KeyError: try: item=self.cjc.roster.get_item_by_jid(item.bare()) except KeyError: pass if isinstance(item,pyxmpp.roster.RosterItem): jid=item.jid groups=item.groups for g,j in self.buffer.get_keys(): if j==jid and g not in groups: self.buffer.remove_item((g,j)) elif g==VG_UNKNOWN and j and (j==jid or j.bare()==jid): self.buffer.remove_item((g,j)) if groups: for group in groups: self.write_item(group,item) else: self.write_item(None,item) elif isinstance(item,pyxmpp.JID): if item.bare()==self.cjc.jid.bare(): group=VG_ME if not self.buffer.has_key((group,None)): self.buffer.insert_themed((group,None),"roster.group_me",{}) else: group=VG_UNKNOWN if not self.buffer.has_key((group,None)): self.buffer.insert_themed((group,None),"roster.group_unknown",{}) self.write_item(group,item) self.extra_items.append((group,item)) self.buffer.update() def get_item_format_params(self,group,item,show_list): if isinstance(item,pyxmpp.JID): jid=item name=None ask=None if group==VG_ME: subs="both" else: subs="none" else: subs=item.subscription jid=item.jid name=item.name ask=item.ask if jid.resource: self.cjc.set_user_info(jid,"rostername",name) else: self.cjc.set_bare_user_info(jid,"rostername",name) if not name: name=jid.as_unicode() p={"name":name,"jid":jid} pr=self.cjc.get_user_info(jid,"presence") if not pr or pr.get_type() and pr.get_type()!="available": available=0 show=None else: available=1 show=pr.get_show() if not show: show="online" if subs=="remove": show_it=0 elif "all" in show_list: show_it=1 elif "available" in show_list and available: show_it=1 elif "unavailable" in show_list and not available: show_it=1 elif show in show_list and available: show_it=1 else: show_it=0 if not show_it: return None p["ask"]=ask if not ask: p["aflag"]=" " elif ask=="unsubscribe": p["aflag"]="-" else: p["aflag"]="?" p["subscription"]=subs if subs=="both": p["sflag"]=" " elif subs=="from": p["sflag"]="<" elif subs=="to": p["sflag"]=">" else: p["sflag"]="-" p["available"]=available return p def write_item(self,group,item): self.debug("Roster.write_item(%r): %r" % (item, item)) if not self.buffer.has_key((group,None)): if group: p={"group":group} self.buffer.insert_themed((group,None),"roster.group",p) else: self.buffer.insert_themed((group,None),"roster.group_none",{}) params=self.get_item_format_params(group,item,self.settings["show"]) if params is None: self.debug("Roster.write_item: removing item") if isinstance(item,pyxmpp.JID): jid=item else: jid=item.jid try: self.buffer.remove_item((group,jid)) except ListBufferError: pass return self.debug("Roster.write_item: updating item") if params["available"]: self.buffer.insert_themed((group,params["jid"]),"roster.available",params) else: self.buffer.insert_themed((group,params["jid"]),"roster.unavailable",params) def write_all(self): self.buffer.clear() groups_added=[] for group,item in self.extra_items: if group==VG_ME and group not in groups_added: self.buffer.insert_themed((group,None),"roster.group_me",{}) elif group==VG_UNKNOWN and group not in groups_added: self.buffer.insert_themed((group,None),"roster.group_unknown",{}) self.write_item(group,item) if not self.cjc.roster: self.buffer.update() return groups=self.cjc.roster.get_groups() groups.sort() for group in groups: for item in self.cjc.roster.get_items_by_group(group): self.write_item(group,item) self.buffer.update() def session_started(self,stream): self.cjc.request_roster() def cmd_add(self,args): groups=[] while 1: arg=args.shift() if arg=="-group": groups.append(args.shift()) else: break try: user=pyxmpp.JID(arg) except pyxmpp.JIDError: self.error("Bad JID!") return if user.bare()==self.cjc.stream.me.bare(): self.error("Self presence subscription is automatic." " Cannot add own JID to the roster.") return try: item=self.cjc.roster.get_item_by_jid(user) if item: self.error(u"User '%s' already in roster." % (unicode(user),)) return except KeyError: pass name=args.all() if not name: name=None item=self.cjc.roster.add_item(user,name=name) item.groups=groups iq=item.make_roster_push() self.cjc.stream.send(iq) def cmd_remove(self,args): user=args.shift() args.finish() user=self.cjc.get_user(user) if user is None: return if user.bare()==self.cjc.stream.me.bare(): self.error("Self presence subscription is automatic." " Cannot remove own JID from the roster.") return try: item=self.cjc.roster.remove_item(user) except KeyError: self.error(u"There is no %s in roster" % (user.as_unicode())) return iq=item.make_roster_push() self.cjc.stream.send(iq) def cmd_rename(self,args): user=args.shift() users=self.cjc.get_users(user) if not users: return for user in users: if user.bare()==self.cjc.stream.me.bare(): self.error("Self presence subscription is automatic." " Cannot rename own JID in the roster.") continue name=args.all() try: item=self.cjc.roster.get_item_by_jid(user) except KeyError: self.error(u"You don't have %s in your roster" % (user.as_unicode(),)) continue item.name=name iq=item.make_roster_push() self.cjc.stream.send(iq) def cmd_group(self,args): user=args.shift() if user is None: self.error(u"/group without arguments!") return users=self.cjc.get_users(user) if not users: return groups=[] groups_add=[] groups_remove=[] while 1: group=args.shift() if not group: break if group.startswith("+"): groups_add.append(group[1:]) elif group.startswith("-"): groups_remove.append(group[1:]) else: groups.append(group) if groups and (groups_add or groups_remove): self.error("You cannot use groups with +/- sign and" " without it in one /group command") return for user in users: if user.bare()==self.cjc.stream.me.bare(): self.error("Self presence subscription is automatic." " Cannot group own JID in the roster.") continue item=self.cjc.roster.get_item_by_jid(user) if not item: self.error(u"You don't have %s in your roster" % (user.as_unicode(),)) continue if not groups and not groups_add and not groups_remove: self.info(u"User %s belongs to the following groups: %s" % (user.as_unicode(), string.join(item.groups(),","))) continue if groups: item.groups=groups else: for group in groups_add: if group not in item.groups: item.groups.append(group) for group in groups_remove: item.groups.remove(group) iq=item.make_roster_push() self.cjc.stream.send(iq) def filter_roster(self, args, eat_all_args = False, split_groups = False, extra_options = []): groups = [] jids = [] filter = [] names = [] subs = [] while eat_all_args or (args.get() and args.get().startswith("-")): if args.get() in extra_options: break arg = args.shift() if arg is None: break if not arg.startswith("-"): try: names.append(re.compile(arg)) except re.error: self.error(u"Invalid regular expression: %r" % (arg,)) return continue if arg=="-group": arg=args.shift() try: groups.append(re.compile(arg)) except re.error: self.error(u"Invalid regular expression: %r" % (arg,)) return elif arg=="-jid": arg=args.shift() try: jids.append(re.compile(arg)) except re.error: self.error(u"Invalid regular expression: %r" % (arg,)) return elif arg=="-state" or arg=="-show": arg=args.shift() if "," in arg: filter+=arg.split(",") else: filter.append(arg) elif arg == "-subscription" or arg == "-sub": arg = args.shift() if "," in arg: subs += arg.split(",") else: subs.append(arg) elif arg=="-name": arg=args.shift() try: names.append(re.compile(arg)) except re.error: self.error(u"Invalid regular expression: %r" % (arg,)) return else: self.error("Bad roster filter option: %r" % (arg,)) return if "all" in filter: filter = [] def check_item(item): if names: if item.name is None: return False ok=False for g in names: if g.search(item.name): ok=True break if not ok: return False if jids: if item.jid is None: return False ok=False for g in jids: if g.search(unicode(item.jid)): ok=True break if not ok: return False if groups: if not item.groups: return False ok = False for g in groups: for ig in item.groups: if g.search(ig): ok=True break if not ok: return False if filter: ok = False for f in filter: pr = self.cjc.get_user_info(item.jid, "presence") if pr: pr_type = pr.get_type() if not pr or pr_type == "unavailable": if f == "unavailable": ok = True break continue if pr_type == "error": if f == "error": ok = True break continue if pr_type and pr_type != "available": continue if f == "online": ok = True break if f == pr.get_show(): ok = True break if not ok: return False if subs: if not item.subscription in subs: return False return True if split_groups: rgroups = self.cjc.roster.get_groups() rgroups.sort() result = [] for group in rgroups: if groups: if not group: continue ok=False for g in groups: if g.search(group): ok=True break if not ok: continue items = [(item.name,item.jid.as_unicode(),item) for item in self.cjc.roster.get_items_by_group(group) if check_item(item)] items.sort() result.append([group,[ item[2] for item in items ]]) return result else: items = self.cjc.roster.get_items() return [ item for item in items if check_item(item) ] def cmd_list(self,args): if not self.cjc.roster: self.error("No roster available.") return groups = self.filter_roster(args, eat_all_args = True, split_groups = True) args.finish() formatted_list = [] for group, items in groups: items = [(item.name,item.jid.as_unicode(), item) for item in items] items.sort() formatted_group = [] for name, jid, item in items: params = self.get_item_format_params(group, item, "all") if not params: continue if params["available"]: formatted_group += cjc_globals.theme_manager.format_string( "roster.list_available",params) else: formatted_group += cjc_globals.theme_manager.format_string( "roster.list_unavailable",params) if not formatted_group: continue if group is None: group=u"unfiled" params = {"roster_group_items": formatted_group, "group": group} formatted_list += cjc_globals.theme_manager.format_string("roster.list_group", params) if formatted_list: params = {"roster_groups": formatted_list} self.cjc.status_buf.append_themed("roster.list", params) else: self.error("No roster items matches your filter") self.cjc.status_buf.update() roster_filter_cmd_args = ("-group opaque", "-jid opaque", "-state opaque", "-name opaque", "-subscription opaque") ui.CommandTable("roster",50,( ui.Command("add",Plugin.cmd_add, "/add [-group group]... jid [name]", "Add a user to the roster (this doesn't automatically subscribe to his presence).", ("-group opaque","user","text")), ui.Command("remove",Plugin.cmd_remove, "/remove user", "Remove user from the roster.", ("user",)), ui.Command("rename",Plugin.cmd_rename, "/rename user name", "Change visible name of a user in the roster.", ("user","text")), ui.Command("group",Plugin.cmd_group, "/group user [+|-]group...", "Change groups a user from roster belongs to.", ("user","text")), ui.Command("list",Plugin.cmd_list, ("/list [-group regexp]..." " [-jid regexp]..." " [-state available|unavailable|online|offline|away|xa|chat|error]... " " [[-name] regexp]..." ), "List roster items", roster_filter_cmd_args + ("opaque", )), )).install() # vi: sts=4 et sw=4