Commit cec1151e authored by mathieui's avatar mathieui

Fix #1839 (User mood/activity)

- Added as always new theming variables:
    CHAR_ROSTER_MOOD, CHAR_ROSTER_ACTIVITY (a SNOWMAN!)
    COLOR_ROSTER_MOOD, COLOR_ROSTER_ACTIVITY
- Added two new notification types in Theme.INFO_COLORS (mood/activity)
- Added new configuration options:
    display_mood/activity/tune_notifications (those can be set for a
    specific JID)
    enable_user_tune/nick/activity/mood
- Added /activity and /mood commands, with completions
- Moved the old /activity to /last_activity
- Details are show in the ContactInfoWin if there is room, or with "i"
  on a contact in the roster.
parent d2d04162
......@@ -347,11 +347,29 @@ display_tune_notifications = false
# in the roster).
# If this is set to false, then the display_tune_notifications
# option will be ignored.
receive_user_tune = true
enable_user_tune = true
# Display user mood notifications as information messages or not
display_mood_notifications = false
# Receive the mood notifications or not (in order to display informations
# in the roster).
# If this is set to false, then the display_mood_notifications
# option will be ignored.
enable_user_mood = true
# Display user activity notifications as information messages or not
display_activity_notifications = false
# Receive the activity notifications or not (in order to display informations
# in the roster).
# If this is set to false, then the display_activity_notifications
# option will be ignored.
enable_user_activity = true
# If set to true, use the nickname broadcasted by the user if none has been
# set manually.
use_pep_nick = true
enable_user_nick = true
# if true, chat states will be sent to the people you are talking to.
# Chat states are, for example, messages informing that you are composing
......
......@@ -118,11 +118,40 @@ section of this documentation.
If set to true, notifications about the music your contacts listen to
will be displayed in the info buffer as 'Tune' messages.
*display_mood_notifications*:: false
If set to true, notifications about the mood of your contacts
will be displayed in the info buffer as 'Mood' messages.
*display_activity_notifications*:: false
If set to true, notifications about the current activity of your contacts
will be displayed in the info buffer as 'Activity' messages.
*display_user_color_in_join_part*:: false
If set to true, the color of the nick will be used in MUCs information
messages, instead of the default color from the theme.
*enable_user_activity*:: true
Set this to false if you don’t want to receive the mood of your contacts
anymore.
*enable_user_mood*:: true
Set this to false if you don’t want to receive the mood of your contacts
anymore.
*enable_user_nick*:: true
Set to false if you don’t want your contacts to hint you their identity.
*enable_user_tune*:: true
If this is set to false, you will no longer be subscribed to tune events,
and the display_tune_notifications option will be ignored.
*enable_vertical_tab_list*:: false
If true, a vertical list of tabs, with their name, is displayed on the left of
......@@ -281,11 +310,6 @@ section of this documentation.
If the message takes more than one line, the popup will stay visible
two more second per additional lines.
*receive_user_tune*:: true
If this is set to false, you will no longer be subscribed to tune events,
and the display_tune_notifications option will be ignored.
*remote_fifo_path*:: ./poezio.fifo
The path of the FIFO used to send the commands (see the exec_remote option).
......@@ -543,11 +567,26 @@ bar = false
Disable the beeps triggered by this conversation. Works in MucTab,
PrivateTab and ConversationTab.
*display_activity_notifications*:: false
If set to true, notifications about the current activity of your contacts
will be displayed in the info buffer as 'Activity' messages.
*display_mood_notifications*:: false
If set to true, notifications about the mood of your contacts
will be displayed in the info buffer as 'Mood' messages.
*display_user_color_in_join_part*:: false
If set to true, the color of the nick will be used in MUCs information
messages, instead of the default color from the theme.
*display_tune_notifications*:: false
If set to true, notifications about the music your contacts listen to
will be displayed in the info buffer as 'Tune' messages.
*hide_exit_join*:: -1
Exact same thing than hide_status_change, except that it concerns
......
......@@ -72,10 +72,18 @@ class Connection(sleekxmpp.ClientXMPP):
self.register_plugin('xep_0115')
self.register_plugin('xep_0191')
if config.get('receive_user_tune', 'true') != 'false':
if config.get('enable_user_tune', 'true') != 'false':
self.register_plugin('xep_0118')
if config.get('use_pep_nick', 'true') != 'false':
if config.get('enable_user_nick', 'true') != 'false':
self.register_plugin('xep_0172')
if config.get('enable_user_mood', 'true') != 'false':
self.register_plugin('xep_0107')
if config.get('enable_user_activity', 'true') != 'false':
self.register_plugin('xep_0108')
if config.get('send_poezio_info', 'true') == 'true':
info = {'name':'poezio',
'version': options.version}
......
......@@ -68,6 +68,8 @@ class Contact(object):
self._name = ''
self.error = None
self.tune = {}
self.mood = ''
self.activity = ''
@property
def groups(self):
......
......@@ -20,6 +20,7 @@ from threading import Event
from datetime import datetime
from xml.etree import cElementTree as ET
import pep
import common
import theming
import logging
......@@ -230,7 +231,6 @@ class Core(object):
self.key_func.update(key_func)
# Add handlers
self.xmpp.add_event_handler("user_tune_publish", self.on_tune_event)
self.xmpp.add_event_handler('connected', self.on_connected)
self.xmpp.add_event_handler('disconnected', self.on_disconnected)
self.xmpp.add_event_handler('no_auth', self.on_failed_auth)
......@@ -260,8 +260,14 @@ class Core(object):
self.xmpp.add_event_handler("attention", self.on_attention)
self.xmpp.add_event_handler("ssl_cert", self.validate_ssl)
self.all_stanzas = Callback('custom matcher', connection.MatchAll(None), self.incoming_stanza)
if config.get('use_pep_nick', 'true') != 'false':
if config.get('enable_user_tune', 'true') != 'false':
self.xmpp.add_event_handler("user_tune_publish", self.on_tune_event)
if config.get('enable_user_nick', 'true') != 'false':
self.xmpp.add_event_handler("user_nick_publish", self.on_nick_received)
if config.get('enable_user_mood', 'true') != 'false':
self.xmpp.add_event_handler("user_mood_publish", self.on_mood)
if config.get('enable_user_activity', 'true') != 'false':
self.xmpp.add_event_handler("user_activity_publish", self.on_activity)
self.xmpp.register_handler(self.all_stanzas)
self.initial_joins = []
......@@ -2132,7 +2138,7 @@ class Core(object):
serv_list.add(serv)
return the_input.auto_completion(list(serv_list), ' ')
def command_activity(self, arg):
def command_last_activity(self, arg):
"""
/activity <jid>
"""
......@@ -2158,12 +2164,72 @@ class Core(object):
self.information(msg, 'Info')
jid = safeJID(arg)
if jid == '':
return self.command_help('activity')
return self.command_help('last_activity')
self.xmpp.plugin['xep_0012'].get_last_activity(jid, block=False, callback=callback)
def completion_activity(self, the_input):
def completion_last_activity(self, the_input):
return the_input.auto_completion([jid for jid in roster.jids()], '', quotify=False)
def command_mood(self, arg):
"""
/mood [<mood> [text]]
"""
args = common.shell_split(arg)
if not args:
return self.xmpp.plugin['xep_0107'].stop(block=False)
mood = args[0]
if mood not in pep.MOODS:
return self.information('%s is not a correct value for a mood.' % mood, 'Error')
if len(args) > 1:
text = args[1]
else:
text = None
self.xmpp.plugin['xep_0107'].publish_mood(mood, text, block=False)
def completion_mood(self, the_input):
"""Completion for /mood"""
return the_input.auto_completion(list(pep.MOODS.keys()), '', quotify=False)
def command_activity(self, arg):
"""
/activity [<general> [specific] [text]]
"""
args = common.shell_split(arg)
length = len(args)
if not length:
return self.xmpp.plugin['xep_0108'].stop(block=False)
general = args[0]
if general not in pep.ACTIVITIES:
return self.information('%s is not a correct value for an activity' % general, 'Error')
specific = None
text = None
if length == 2:
if args[1] in pep.ACTIVITIES[general]:
specific = args[1]
else:
text = args[1]
elif length == 3:
specific = args[1]
text = args[2]
if specific and specific not in pep.ACTIVITIES[general]:
return self.information('%s is not a correct value for an activity' % specific, 'Error')
self.xmpp.plugin['xep_0108'].publish_activity(general, specific, text, block=False)
def completion_activity(self, the_input):
"""Completion for /activity"""
txt = the_input.get_text()
args = common.shell_split(txt)
n = len(args)
if txt.endswith(' '):
n += 1
if n == 2:
return the_input.auto_completion(list(pep.ACTIVITIES.keys()), '', quotify=False)
elif n == 3:
if args[1] in pep.ACTIVITIES:
l = list(pep.ACTIVITIES[args[1]])
l.remove('category')
return the_input.auto_completion(l, '', quotify=False)
def command_invite(self, arg):
"""/invite <to> <room> [reason]"""
args = common.shell_split(arg)
......@@ -2233,6 +2299,10 @@ class Core(object):
msg = arg
else:
msg = None
if config.get('enable_user_mood', 'true') != 'false':
self.xmpp.plugin['xep_0107'].stop(block=False)
if config.get('enable_user_activity', 'true') != 'false':
self.xmpp.plugin['xep_0108'].stop(block=False)
self.plugin_manager.disable_plugins()
self.disconnect(msg)
self.running = False
......@@ -2489,11 +2559,25 @@ class Core(object):
completion=self.completion_runkey)
self.register_command('self', self.command_self,
shortdesc=_('Remind you of who you are.'))
self.register_command('activity', self.command_activity,
self.register_command('last_activity', self.command_last_activity,
usage='<jid>',
desc=_('Informs you of the last activity of a JID.'),
shortdesc=_('Get the activity of someone.'),
completion=self.completion_activity)
completion=self.completion_last_activity)
if config.get('enable_user_mood', 'true') != 'false':
self.register_command('activity', self.command_activity,
usage='[<general> [specific] [text]]',
desc=_('Send your current activity to your contacts (use the completion).'
' Nothing means "stop broadcasting an activity".'),
shortdesc=_('Send your activity.'),
completion=self.completion_activity)
if config.get('eanble_user_activity', 'true') != 'false':
self.register_command('mood', self.command_mood,
usage='[<mood> [text]]',
desc=_('Send your current mood to your contacts (use the completion).'
' Nothing means "stop broadcasting a mood".'),
shortdesc=_('Send your mood.'),
completion=self.completion_mood)
####################### XMPP Event Handlers ##################################
......@@ -2555,7 +2639,7 @@ class Core(object):
else:
remote_nick = ''
# check for a received nick
if not remote_nick and config.get('use_pep_nick', 'true') != 'false':
if not remote_nick and config.get('enable_user_nick', 'true') != 'false':
if message.xml.find('{http://jabber.org/protocol/nick}nick') is not None:
remote_nick = message['nick']['nick']
# bind the nick to the conversation
......@@ -2633,6 +2717,68 @@ class Core(object):
item = message['pubsub_event']['items']['item']
if item.xml.find('{http://jabber.org/protocol/nick}nick') is not None:
contact.name = item['nick']['nick']
else:
contact.name= ''
def on_mood(self, message):
"""
Called when a pep notification for an user mood
is received.
"""
contact = roster[message['from'].bare]
if not contact:
return
item = message['pubsub_event']['items']['item']
if item.xml.find('{http://jabber.org/protocol/mood}mood') is not None:
mood = item['mood']['value']
if mood:
mood = pep.MOODS.get(mood, mood)
text = item['mood']['text']
if text:
mood = '%s (%s)' % (mood, text)
contact.mood = mood
else:
contact.mood = ''
else:
contact.mood = ''
if config.get_by_tabname('display_mood_notifications', 'false', contact.bare_jid) == 'true':
if contact.mood:
self.information('Mood from '+ contact.bare_jid + ': ' + contact.mood, 'Mood')
else:
self.information(contact.bare_jid + ' stopped having his/her mood.', 'Mood')
def on_activity(self, message):
"""
Called when a pep notification for an user activity
is received.
"""
contact = roster[message['from'].bare]
if not contact:
return
item = message['pubsub_event']['items']['item']
if item.xml.find('{http://jabber.org/protocol/activity}activity') is not None:
try:
activity = item['activity']['value']
except ValueError:
return
if activity[0]:
general = pep.ACTIVITIES.get(activity[0])
s = general['category']
if activity[1]:
s = s + '/' + general.get(activity[1], 'other')
text = item['activity']['text']
if text:
s = '%s (%s)' % (s, text)
contact.activity = s
else:
contact.activity = ''
else:
contact.activity = ''
if config.get_by_tabname('display_activity_notifications', 'false', contact.bare_jid) == 'true':
if contact.activity:
self.information('Activity from '+ contact.bare_jid + ': ' + contact.activity, 'Activity')
else:
self.information(contact.bare_jid + ' stopped doing his/her activity.', 'Activity')
def on_tune_event(self, message):
"""
......@@ -2656,10 +2802,13 @@ class Core(object):
}
else:
contact.tune = {}
if config.get('display_tune_notifications', 'false') == 'true' and contact.tune:
self.information(
'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune),
'Tune')
if config.get_by_tabname('display_tune_notifications', 'false', contact.bare_jid) == 'true':
if contact.tune:
self.information(
'Tune from '+ message['from'].bare + ': ' + common.format_tune_string(contact.tune),
'Tune')
else:
self.information(contact.bare_jid + ' stopped listening to music.', 'Tune')
def on_groupchat_message(self, message):
"""
......@@ -3068,7 +3217,7 @@ class Core(object):
status=self.status.message,
show=self.status.show)
if config.get('use_pep_nick', 'true') != 'false':
if config.get('enable_user_nick', 'true') != 'false':
self.xmpp.plugin['xep_0172'].publish_nick(nick=self.own_nick)
### Other handlers ###
......
# -*- coding:utf-8 -*-
## src/common/pep.py
##
## Copyright (C) 2007 Piotr Gaczkowski <doomhammerng AT gmail.com>
## Copyright (C) 2007-2012 Yann Leboulanger <asterix AT lagaule.org>
## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
## Jean-Marie Traissard <jim AT lapin.org>
## Jonathan Schleifer <js-common.gajim AT webkeks.org>
## Stephan Erb <steve-e AT h3c.de>
##
## This file is part of Gajim.
##
## Gajim 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 only.
##
## Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
##
from gettext import gettext as _
MOODS = {
'afraid': _('Afraid'),
'amazed': _('Amazed'),
'amorous': _('Amorous'),
'angry': _('Angry'),
'annoyed': _('Annoyed'),
'anxious': _('Anxious'),
'aroused': _('Aroused'),
'ashamed': _('Ashamed'),
'bored': _('Bored'),
'brave': _('Brave'),
'calm': _('Calm'),
'cautious': _('Cautious'),
'cold': _('Cold'),
'confident': _('Confident'),
'confused': _('Confused'),
'contemplative': _('Contemplative'),
'contented': _('Contented'),
'cranky': _('Cranky'),
'crazy': _('Crazy'),
'creative': _('Creative'),
'curious': _('Curious'),
'dejected': _('Dejected'),
'depressed': _('Depressed'),
'disappointed': _('Disappointed'),
'disgusted': _('Disgusted'),
'dismayed': _('Dismayed'),
'distracted': _('Distracted'),
'embarrassed': _('Embarrassed'),
'envious': _('Envious'),
'excited': _('Excited'),
'flirtatious': _('Flirtatious'),
'frustrated': _('Frustrated'),
'grateful': _('Grateful'),
'grieving': _('Grieving'),
'grumpy': _('Grumpy'),
'guilty': _('Guilty'),
'happy': _('Happy'),
'hopeful': _('Hopeful'),
'hot': _('Hot'),
'humbled': _('Humbled'),
'humiliated': _('Humiliated'),
'hungry': _('Hungry'),
'hurt': _('Hurt'),
'impressed': _('Impressed'),
'in_awe': _('In Awe'),
'in_love': _('In Love'),
'indignant': _('Indignant'),
'interested': _('Interested'),
'intoxicated': _('Intoxicated'),
'invincible': _('Invincible'),
'jealous': _('Jealous'),
'lonely': _('Lonely'),
'lost': _('Lost'),
'lucky': _('Lucky'),
'mean': _('Mean'),
'moody': _('Moody'),
'nervous': _('Nervous'),
'neutral': _('Neutral'),
'offended': _('Offended'),
'outraged': _('Outraged'),
'playful': _('Playful'),
'proud': _('Proud'),
'relaxed': _('Relaxed'),
'relieved': _('Relieved'),
'remorseful': _('Remorseful'),
'restless': _('Restless'),
'sad': _('Sad'),
'sarcastic': _('Sarcastic'),
'satisfied': _('Satisfied'),
'serious': _('Serious'),
'shocked': _('Shocked'),
'shy': _('Shy'),
'sick': _('Sick'),
'sleepy': _('Sleepy'),
'spontaneous': _('Spontaneous'),
'stressed': _('Stressed'),
'strong': _('Strong'),
'surprised': _('Surprised'),
'thankful': _('Thankful'),
'thirsty': _('Thirsty'),
'tired': _('Tired'),
'undefined': _('Undefined'),
'weak': _('Weak'),
'worried': _('Worried')}
ACTIVITIES = {
'doing_chores': {'category': _('Doing Chores'),
'buying_groceries': _('Buying Groceries'),
'cleaning': _('Cleaning'),
'cooking': _('Cooking'),
'doing_maintenance': _('Doing Maintenance'),
'doing_the_dishes': _('Doing the Dishes'),
'doing_the_laundry': _('Doing the Laundry'),
'gardening': _('Gardening'),
'running_an_errand': _('Running an Errand'),
'walking_the_dog': _('Walking the Dog')},
'drinking': {'category': _('Drinking'),
'having_a_beer': _('Having a Beer'),
'having_coffee': _('Having Coffee'),
'having_tea': _('Having Tea')},
'eating': {'category': _('Eating'),
'having_a_snack': _('Having a Snack'),
'having_breakfast': _('Having Breakfast'),
'having_dinner': _('Having Dinner'),
'having_lunch': _('Having Lunch')},
'exercising': {'category': _('Exercising'),
'cycling': _('Cycling'),
'dancing': _('Dancing'),
'hiking': _('Hiking'),
'jogging': _('Jogging'),
'playing_sports': _('Playing Sports'),
'running': _('Running'),
'skiing': _('Skiing'),
'swimming': _('Swimming'),
'working_out': _('Working out')},
'grooming': {'category': _('Grooming'),
'at_the_spa': _('At the Spa'),
'brushing_teeth': _('Brushing Teeth'),
'getting_a_haircut': _('Getting a Haircut'),
'shaving': _('Shaving'),
'taking_a_bath': _('Taking a Bath'),
'taking_a_shower': _('Taking a Shower')},
'having_appointment': {'category': _('Having an Appointment')},
'inactive': {'category': _('Inactive'),
'day_off': _('Day Off'),
'hanging_out': _('Hanging out'),
'hiding': _('Hiding'),
'on_vacation': _('On Vacation'),
'praying': _('Praying'),
'scheduled_holiday': _('Scheduled Holiday'),
'sleeping': _('Sleeping'),
'thinking': _('Thinking')},
'relaxing': {'category': _('Relaxing'),
'fishing': _('Fishing'),
'gaming': _('Gaming'),
'going_out': _('Going out'),
'partying': _('Partying'),
'reading': _('Reading'),
'rehearsing': _('Rehearsing'),
'shopping': _('Shopping'),
'smoking': _('Smoking'),
'socializing': _('Socializing'),
'sunbathing': _('Sunbathing'),
'watching_tv': _('Watching TV'),
'watching_a_movie': _('Watching a Movie')},
'talking': {'category': _('Talking'),
'in_real_life': _('In Real Life'),
'on_the_phone': _('On the Phone'),
'on_video_phone': _('On Video Phone')},
'traveling': {'category': _('Traveling'),
'commuting': _('Commuting'),
'cycling': _('Cycling'),
'driving': _('Driving'),
'in_a_car': _('In a Car'),
'on_a_bus': _('On a Bus'),
'on_a_plane': _('On a Plane'),
'on_a_train': _('On a Train'),
'on_a_trip': _('On a Trip'),
'walking': _('Walking')},
'working': {'category': _('Working'),
'coding': _('Coding'),
'in_a_meeting': _('In a Meeting'),
'studying': _('Studying'),
'writing': _('Writing')}}
LOCATION_DATA = {
'accuracy': _('accuracy'),
'alt': _('alt'),
'area': _('area'),
'bearing': _('bearing'),
'building': _('building'),
'country': _('country'),
'countrycode': _('countrycode'),
'datum': _('datum'),
'description': _('description'),
'error': _('error'),
'floor': _('floor'),
'lat': _('lat'),
'locality': _('locality'),
'lon': _('lon'),
'postalcode': _('postalcode'),
'region': _('region'),
'room': _('room'),
'speed': _('speed'),
'street': _('street'),
'text': _('text'),
'timestamp': _('timestamp'),
'uri': _('uri')}
......@@ -2076,7 +2076,7 @@ class RosterInfoTab(Tab):
self.key_func["M-Y"] = self.move_cursor_to_prev_group
self.key_func["M-[1;5B"] = self.move_cursor_to_next_group
self.key_func["M-[1;5A"] = self.move_cursor_to_prev_group
self.key_func["l"] = self.command_activity
self.key_func["l"] = self.command_last_activity
self.key_func["o"] = self.toggle_offline_show
self.key_func["v"] = self.get_contact_version
self.key_func["i"] = self.show_contact_info
......@@ -2139,11 +2139,11 @@ class RosterInfoTab(Tab):
completion=self.completion_file)
self.register_command('clear_infos', self.command_clear_infos,
shortdesc=_('Clear the info buffer.'))
self.register_command('activity', self.command_activity,
self.register_command('last_activity', self.command_last_activity,
usage=_('<jid>'),
desc=_('Informs you of the last activity of a JID.'),
shortdesc=_('Get the activity of someone.'),
completion=self.core.completion_activity)