a few renamings, and some other stuff

This commit is contained in:
louiz@4325f9fc-e183-4c21-96ce-0ab188b42d13 2010-11-15 11:59:09 +00:00
parent de7c007a22
commit f4d4a205f1
16 changed files with 100 additions and 205 deletions

View file

@ -3,15 +3,20 @@ For more detailed changelog, see the roadmap:
http://codingteam.net/project/poezio/roadmap
* Poezio 0.7 - dev
"Complete the MUC support"
- All poezio colors can be changed with a theme file
- /say command
"Roster"
- Introduce the roster
- One to one conversations
- Roster search
- Resizable mini-buffer displaying various informations
- All colors can be changed with a theme file
- /say and // commands
- Warn user about publicly logged rooms
- Possibility to limit the number of history messages received from MUC
- auto-rejoin when kicked from a MUC
- The number of lines available to scroll down is displayed
- Possibility to use a modified nickname automatically when a nick is reserved
- A line indicates the new messages in the window
- A line separates the alread-read messages
from the new messages in a conversation
- Information messages are more colored
- bugfixes

25
README
View file

@ -16,18 +16,21 @@ doesn't have to create a Jabber account, exactly like people are using
IRC. Poezio's commands are designed to be (if possible) like IRC
clients (weechat, irssi, etc).
Since version 0.7, poezio can handle real Jabber accounts along with
roster and one to one conversation, making it a full-featured console
roster and one to one conversations, making it a full-featured console
Jabber client, but still MultiUserChats-centered.
In the futur, poezio should implement at a 100% level all XEP related to
MUCs, especially XEP 0045.
All other IM-related XEP (wherever possible) should be implemented through
plugins or directly in poezio's core.
=======================
Install
=======================
You need python 3.0 or higher, and the SleekXMPP python library.
You can find my patched version at http://github.com/louiz/SleekXMPP until
my changes (required to properly run poezio) are merged upstream.
SleekXMPP can be found at http://github.com/fritzy/SleekXMPP.
you can launch poezio with
sh launch.sh
./launch.sh
or you can install it with (as root or with sudo)
make install
@ -36,7 +39,7 @@ you can now simply launch `poezio'
You can edit the config file (~/.config/poezio/poezio.cfg by default)
or data/default_config.cfg (if you want to edit the config before the
first launch)
first launch). The default config file is fully commented.
Please, see the online documentation for more information on installing,
configuring or using poezio:
@ -66,10 +69,10 @@ Report a bug: http://codingteam.net/project/poezio/bugs/add
Poezio is Free Software.
(learn more: http://www.gnu.org/philosophy/free-sw.html)
Poezio is released under the Gnu GPLv3 license
Please read the COPYING file for details
Poezio is released under the Gnu GPLv3 license.
Please read the COPYING file for details.
The artwork logo is made by Gaëtan Ribémont and released under
The artwork logo was made by Gaëtan Ribémont and released under
the Creative Commons BY license (http://creativecommons.org/licenses/by/2.0/)
=======================
@ -85,9 +88,3 @@ the Creative Commons BY license (http://creativecommons.org/licenses/by/2.0/)
FlashCode (weechat dev) - Useful advices on how to use ncurses efficiently
= Project =
Gajim - send_vcard method and common.py
=======================
Donate
=======================
If you're willing to thank me, or ask for an on-demand feature, please
see the page http://louiz.org/donate.html and contact me.

View file

@ -17,7 +17,7 @@ port = 5222
resource =
# the nick you will use when joining a room with no associated nick
# If this is empty, the $USER variable will be used
# If this is empty, the $USER environn<ement variable will be used
default_nick =
# Jabber identifiant. Specify it only if you want to connect using an existing
@ -35,15 +35,6 @@ password =
# default_nick will be used if "/nickname" is not specified
rooms = poezio@conference.codingteam.net
# PROXY
# set to true if you want to use an http proxy server
# if false, no proxy will be used and the proxy_* settings will have no effect
use_proxy = false
proxy_server =
proxy_port =
proxy_user =
proxy_password =
# the completion type you will use to complete nicknames
# if "normal", complete the entire name to the first available completion
# and then cycle through the possible completion with the next TABs

View file

@ -5,7 +5,7 @@
.SH "NAME"
Poezio \- a ncurses jabber client
.SH "SYNOPSIS"
.B poezio [\-f \fICONFIG_FILE\fR] [\-h]
.B poezio [\-f \fICONFIG_FILE\fR] [\-d \fIDEBUG_FILE\fR] [\-h]
.SH "DESCRIPTION"
.B Poezio
is a console jabber (XMPP) client written in Python and using ncurses to draw its interface. It aims at being similar to the most famous IRC clients. For more information on XMPP see http://xmpp.org and on Poezio see http://poezio.eu
@ -15,10 +15,13 @@ is a console jabber (XMPP) client written in Python and using ncurses to draw it
\fB\-f\fR, \fB\-\-file \fICONFIG_FILE\fR
Run poezio using \fICONFIG_FILE\fR as the config file instead of ~/.config/poezio/poezio.cfg
.TP
\fB\-d\fR, \fB\-\-debug \fIDEBUG_FILE\fR
Log debug from both poezio and SleekXMPP in \fIDEBUG_FILE\fR. Debug contains incoming and outgoing stanzas in addition to various message helping poezio's debuging.
.TP
\fB\-h\fR
Display and help message
.SH "BUGS"
Sure, of course.
Sure.
.SH "FEEDBACK"
You are encouraged to report bugs or feature requests on http://codingteam.net/project/poezio.
You can also find us on the Jabber chatroom poezio@conference.codingteam.net

View file

@ -1,4 +1,3 @@
#!/usr/bin/env sh
python3 src/poezio.py "$@"

View file

@ -14,6 +14,14 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Define all the buffers.
A buffer is a little part of the screen, for example the input buffer,
the text bufferr, the roster buffer, etc.
A Tab (see tab.py) is composed of multiple Buffers
A buffer can also be called Window, even if it's not prefered.
"""
from gettext import (bindtextdomain, textdomain, bind_textdomain_codeset,
gettext as _)
from os.path import isfile

View file

@ -15,12 +15,15 @@
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Defines the Resource and Contact classes
Defines the Resource and Contact classes, which are used in
the roster
"""
from sleekxmpp.xmlstream.stanzabase import JID
import logging
log = logging.getLogger(__name__)
from sleekxmpp.xmlstream.stanzabase import JID
class Resource(object):
"""
Defines a roster item.
@ -80,8 +83,7 @@ class Contact(object):
def get_highest_priority_resource(self):
"""
There must be, at any time, at least ONE resource.
And they always should be ordered by priority.
Return the resource with the highest priority
"""
ret = None
for resource in self._resources:
@ -94,8 +96,10 @@ class Contact(object):
Called, for example, when a new resource get offline
(the first, or any subsequent one)
"""
# TODO sort by priority
def f(o):
return o.get_priority()
self._resources.append(resource)
self._resources = sorted(self._resources, key=f, reverse=True)
def remove_resource(self, resource):
"""

View file

@ -38,9 +38,9 @@ log = logging.getLogger(__name__)
import multiuserchat as muc
from connection import connection
from handler import Handler
from config import config
from tab import MucTab, InfoTab, PrivateTab, RosterInfoTab, ConversationTab
from logger import logger
from user import User
from room import Room
from roster import Roster, RosterGroup, roster
@ -822,6 +822,8 @@ class Core(object):
body = message['body']
if body:
date = date if delayed == True else None
if not delayed:
logger.groupchat(room_from, nick_from, body)
self.add_message_to_text_buffer(room, body, date, nick_from)
self.refresh_window()
self.doupdate()

View file

@ -1,83 +0,0 @@
# Copyright 2009, 2010 Erwan Briand
# Copyright 2010, Florent Le Coz <louiz@louiz.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 version 3 of the License.
# 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 <http://www.gnu.org/licenses/>.
from singleton import Singleton
#Todo, it's not a singleton. Oh, also, remove-me
class Handler(Singleton):
"""
This class is the global handler for the software's signals.
"""
__is_first_instance = True
def __init__(self):
if Handler.__is_first_instance:
Handler.__is_first_instance = False
self.__signals__ = {
'on-connected': list(),
# At the end of a successful connection process.
# emitted when presence confirmation is received
# Args: jid
'join-room': list(),
# Join a room.
# Args: room, nick
'room-presence': list(),
# A presence is received
# Args: the stanza object
'room-message': list(),
# A message is received
# Args: the stanza object
'private-message': list(),
# A message is received
# Args: the stanza object
'room-delayed-message': list(),
# A message is received
# Args: the stanza object
'send-version': list(),
# We send our version
# Args: the stanza we reply to
'send-time': list(),
# We send our time
# Args: the stanza we reply to
'error-message': list(),
# We send our time
# Args: the stanza we reply to
'error': list()
# We send our time
# Args: the stanza we reply to
}
def connect(self, signal, func):
"""Connect a function to a signal."""
if func not in self.__signals__[signal]:
self.__signals__[signal].append(func)
def emit(self, signal, **kwargs):
"""Emit a signal."""
if signal in self.__signals__:
for func in self.__signals__[signal]:
func(**kwargs)

View file

@ -29,32 +29,10 @@ class Logger(object):
"""
def __init__(self):# , logfile, loglevel):
self.logfile = config.get('logfile', 'logs')
self.loglevel = config.get('loglevel', 3)
# self.logfile = logfile
# self.loglevel = loglevel
def info(self, msg):
if self.logfile and self.loglevel >= 3:
fd = open(self.logfile, 'a')
fd.write(datetime.now().strftime("%H:%M:%S") + ' Info [' + msg + ']\n')
fd.close()
def warning(self, msg):
if self.logfile and self.loglevel >= 2:
fd = open(self.logfile, 'a')
fd.write(datetime.now().strftime("%H:%M:%S") + ' Warning [' + msg + ']\n')
fd.close()
def error(self, msg):
if self.logfile and self.loglevel >= 1:
fd = open(self.logfile, 'a')
fd.write(datetime.now().strftime("%H:%M:%S") + ' Error [' + msg + ']\n')
fd.close()
sys.exit(-1)
def message(self, room, nick, msg):
def groupchat(self, room, nick, msg):
"""
log the message in the appropriate room
log the message in the appropriate room's file
"""
if config.get('use_log', 'false') == 'false':
return

View file

@ -14,6 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Define the Message class
"""
from datetime import datetime
class Message(object):

View file

@ -46,7 +46,7 @@ def change_show(xmpp, jid, own_nick, show, status):
Change our 'Show'
"""
pres = xmpp.makePresence(pto='%s/%s' % (jid, own_nick))
if show: # if show is None, don't put a <show /> tag. It means "online"
if show: # if show is None, don't put a <show /> tag. It means "available"
pres['type'] = show
if status:
pres['status'] = status

View file

@ -1,23 +0,0 @@
# Copyright 2009, 2010 Erwan Briand
# Copyright 2010, Florent Le Coz <louiz@louiz.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 version 3 of the License.
#
# 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 <http://www.gnu.org/licenses/>.
class Singleton(object):
""" Canonic Singleton implementation.
"""
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super(Singleton, cls).__new__(cls, *args, **kwargs)
return cls._instance

View file

@ -15,11 +15,11 @@
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
a Tab object is a way to organize various Window (see window.py)
a Tab object is a way to organize various Buffers (see buffers.py)
around the screen at once.
A tab is then composed of multiple Window.
Each Tab object has different refresh() and resize() methods, defining of its
Window are displayed, etc
A tab is then composed of multiple Buffer.
Each Tab object has different refresh() and resize() methods, defining how its
Buffer are displayed, resized, etc
"""
MIN_WIDTH = 50
@ -28,7 +28,7 @@ MIN_HEIGHT = 16
import logging
log = logging.getLogger(__name__)
import window
import buffers
import theme
import curses
import difflib
@ -145,9 +145,9 @@ class InfoTab(Tab):
"""
def __init__(self, stdscr, core, name):
Tab.__init__(self, stdscr, core)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.text_win = window.TextWin(self.height-2, self.width, 0, 0, stdscr, self.visible)
self.input = window.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.text_win = buffers.TextWin(self.height-2, self.width, 0, 0, stdscr, self.visible)
self.input = buffers.Input(1, self.width, self.height-1, 0, stdscr, self.visible)
self.name = name
self.color_state = theme.COLOR_TAB_NORMAL
@ -209,14 +209,14 @@ class MucTab(Tab):
"""
Tab.__init__(self, stdscr, core)
self._room = room
self.topic_win = window.Topic(1, self.width, 0, 0, stdscr, self.visible)
self.text_win = window.TextWin(self.height-4-self.core.information_win_size, (self.width//10)*9, 1, 0, stdscr, self.visible)
self.v_separator = window.VerticalSeparator(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
self.user_win = window.UserList(self.height-3, (self.width//10), 1, 9*(self.width//10)+1, stdscr, self.visible)
self.info_header = window.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
self.topic_win = buffers.Topic(1, self.width, 0, 0, stdscr, self.visible)
self.text_win = buffers.TextWin(self.height-4-self.core.information_win_size, (self.width//10)*9, 1, 0, stdscr, self.visible)
self.v_separator = buffers.VerticalSeparator(self.height-3, 1, 1, 9*(self.width//10), stdscr, self.visible)
self.user_win = buffers.UserList(self.height-3, (self.width//10), 1, 9*(self.width//10)+1, stdscr, self.visible)
self.info_header = buffers.MucInfoWin(1, (self.width//10)*9, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = buffers.TextWin(self.core.information_win_size, (self.width//10)*9, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
"""
@ -333,11 +333,11 @@ class PrivateTab(Tab):
def __init__(self, stdscr, core, room):
Tab.__init__(self, stdscr, core)
self._room = room
self.text_win = window.TextWin(self.height-3-self.core.information_win_size, self.width, 0, 0, stdscr, self.visible)
self.info_header = window.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
self.text_win = buffers.TextWin(self.height-3-self.core.information_win_size, self.width, 0, 0, stdscr, self.visible)
self.info_header = buffers.PrivateInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = buffers.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
Tab.resize(self, stdscr)
@ -420,12 +420,12 @@ class RosterInfoTab(Tab):
self.name = "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)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible)
self.roster_win = window.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible)
self.contact_info_win = window.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible)
self.default_help_message = window.HelpText(1, self.width, self.height-1, 0, stdscr, self.visible, "Enter commands with “/”. “o”: toggle offline show")
self.v_separator = buffers.VerticalSeparator(self.height-2, 1, 0, roster_width, stdscr, self.visible)
self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.info_win = buffers.TextWin(self.height-2, info_width, 0, roster_width+1, stdscr, self.visible)
self.roster_win = buffers.RosterWin(self.height-2-3, roster_width, 0, 0, stdscr, self.visible)
self.contact_info_win = buffers.ContactInfoWin(3, roster_width, self.height-2-3, 0, stdscr, self.visible)
self.default_help_message = buffers.HelpText(1, self.width, self.height-1, 0, stdscr, self.visible, "Enter commands with “/”. “o”: toggle offline show")
self.input = self.default_help_message
self.set_color_state(theme.COLOR_TAB_NORMAL)
@ -491,7 +491,7 @@ class RosterInfoTab(Tab):
'/' is pressed, we enter "input mode"
"""
curses.curs_set(1)
self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "", self.reset_help_message, self.execute_slash_command)
self.input = buffers.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "", self.reset_help_message, self.execute_slash_command)
self.input.do_command("/") # we add the slash
def reset_help_message(self, _=None):
@ -551,7 +551,7 @@ class RosterInfoTab(Tab):
in it.
"""
curses.curs_set(1)
self.input = window.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
self.input = buffers.CommandInput(1, self.width, self.height-1, 0, self.default_help_message, self.visible, "[Search]", self.on_search_terminate, self.on_search_terminate, self.set_roster_filter)
return True
def set_roster_filter(self, txt):
@ -578,12 +578,12 @@ class ConversationTab(Tab):
self._text_buffer = text_buffer
self.color_state = theme.COLOR_TAB_NORMAL
self._name = jid # a conversation tab is linked to one specific full jid OR bare jid
self.text_win = window.TextWin(self.height-4-self.core.information_win_size, self.width, 1, 0, stdscr, self.visible)
self.upper_bar = window.ConversationStatusMessageWin(1, self.width, 0, 0, stdscr, self.visible)
self.info_header = window.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = window.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = window.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = window.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
self.text_win = buffers.TextWin(self.height-4-self.core.information_win_size, self.width, 1, 0, stdscr, self.visible)
self.upper_bar = buffers.ConversationStatusMessageWin(1, self.width, 0, 0, stdscr, self.visible)
self.info_header = buffers.ConversationInfoWin(1, self.width, self.height-3-self.core.information_win_size, 0, stdscr, self.visible)
self.info_win = buffers.TextWin(self.core.information_win_size, self.width, self.height-2-self.core.information_win_size, 0, stdscr, self.visible)
self.tab_win = buffers.GlobalInfoBar(1, self.width, self.height-2, 0, stdscr, self.visible)
self.input = buffers.MessageInput(1, self.width, self.height-1, 0, stdscr, self.visible)
def resize(self, stdscr):
Tab.resize(self, stdscr)

View file

@ -14,6 +14,10 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Define the TextBuffer class
"""
from message import Message
from datetime import datetime
import theme

View file

@ -14,6 +14,11 @@
# You should have received a copy of the GNU General Public License
# along with Poezio. If not, see <http://www.gnu.org/licenses/>.
"""
Define the user class.
An user is a MUC participant, not a roster contact (see contact.py)
"""
from random import randrange, choice
from config import config
from datetime import timedelta, datetime
@ -26,6 +31,7 @@ ROLE_DICT = {
'participant':2,
'moderator':3
}
class User(object):
"""
keep trace of an user in a Room