4 ## Copyright (C) 2007 Piotr Gaczkowski <doomhammerng AT gmail.com>
5 ## Copyright (C) 2007-2010 Yann Leboulanger <asterix AT lagaule.org>
6 ## Copyright (C) 2008 Brendan Taylor <whateley AT gmail.com>
7 ## Jean-Marie Traissard <jim AT lapin.org>
8 ## Jonathan Schleifer <js-common.gajim AT webkeks.org>
9 ## Stephan Erb <steve-e AT h3c.de>
11 ## This file is part of Gajim.
13 ## Gajim is free software; you can redistribute it and/or modify
14 ## it under the terms of the GNU General Public License as published
15 ## by the Free Software Foundation; version 3 only.
17 ## Gajim is distributed in the hope that it will be useful,
18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ## GNU General Public License for more details.
22 ## You should have received a copy of the GNU General Public License
23 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
27 'afraid': _('Afraid'),
28 'amazed': _('Amazed'),
29 'amorous': _('Amorous'),
31 'annoyed': _('Annoyed'),
32 'anxious': _('Anxious'),
33 'aroused': _('Aroused'),
34 'ashamed': _('Ashamed'),
38 'cautious': _('Cautious'),
40 'confident': _('Confident'),
41 'confused': _('Confused'),
42 'contemplative': _('Contemplative'),
43 'contented': _('Contented'),
44 'cranky': _('Cranky'),
46 'creative': _('Creative'),
47 'curious': _('Curious'),
48 'dejected': _('Dejected'),
49 'depressed': _('Depressed'),
50 'disappointed': _('Disappointed'),
51 'disgusted': _('Disgusted'),
52 'dismayed': _('Dismayed'),
53 'distracted': _('Distracted'),
54 'embarrassed': _('Embarrassed'),
55 'envious': _('Envious'),
56 'excited': _('Excited'),
57 'flirtatious': _('Flirtatious'),
58 'frustrated': _('Frustrated'),
59 'grateful': _('Grateful'),
60 'grieving': _('Grieving'),
61 'grumpy': _('Grumpy'),
62 'guilty': _('Guilty'),
64 'hopeful': _('Hopeful'),
66 'humbled': _('Humbled'),
67 'humiliated': _('Humiliated'),
68 'hungry': _('Hungry'),
70 'impressed': _('Impressed'),
71 'in_awe': _('In Awe'),
72 'in_love': _('In Love'),
73 'indignant': _('Indignant'),
74 'interested': _('Interested'),
75 'intoxicated': _('Intoxicated'),
76 'invincible': _('Invincible'),
77 'jealous': _('Jealous'),
78 'lonely': _('Lonely'),
83 'nervous': _('Nervous'),
84 'neutral': _('Neutral'),
85 'offended': _('Offended'),
86 'outraged': _('Outraged'),
87 'playful': _('Playful'),
89 'relaxed': _('Relaxed'),
90 'relieved': _('Relieved'),
91 'remorseful': _('Remorseful'),
92 'restless': _('Restless'),
94 'sarcastic': _('Sarcastic'),
95 'satisfied': _('Satisfied'),
96 'serious': _('Serious'),
97 'shocked': _('Shocked'),
100 'sleepy': _('Sleepy'),
101 'spontaneous': _('Spontaneous'),
102 'stressed': _('Stressed'),
103 'strong': _('Strong'),
104 'surprised': _('Surprised'),
105 'thankful': _('Thankful'),
106 'thirsty': _('Thirsty'),
108 'undefined': _('Undefined'),
110 'worried': _('Worried')}
113 'doing_chores': {'category': _('Doing Chores'),
114 'buying_groceries': _('Buying Groceries'),
115 'cleaning': _('Cleaning'),
116 'cooking': _('Cooking'),
117 'doing_maintenance': _('Doing Maintenance'),
118 'doing_the_dishes': _('Doing the Dishes'),
119 'doing_the_laundry': _('Doing the Laundry'),
120 'gardening': _('Gardening'),
121 'running_an_errand': _('Running an Errand'),
122 'walking_the_dog': _('Walking the Dog')},
123 'drinking': {'category': _('Drinking'),
124 'having_a_beer': _('Having a Beer'),
125 'having_coffee': _('Having Coffee'),
126 'having_tea': _('Having Tea')},
127 'eating': {'category': _('Eating'),
128 'having_a_snack': _('Having a Snack'),
129 'having_breakfast': _('Having Breakfast'),
130 'having_dinner': _('Having Dinner'),
131 'having_lunch': _('Having Lunch')},
132 'exercising': {'category': _('Exercising'),
133 'cycling': _('Cycling'),
134 'dancing': _('Dancing'),
135 'hiking': _('Hiking'),
136 'jogging': _('Jogging'),
137 'playing_sports': _('Playing Sports'),
138 'running': _('Running'),
139 'skiing': _('Skiing'),
140 'swimming': _('Swimming'),
141 'working_out': _('Working out')},
142 'grooming': {'category': _('Grooming'),
143 'at_the_spa': _('At the Spa'),
144 'brushing_teeth': _('Brushing Teeth'),
145 'getting_a_haircut': _('Getting a Haircut'),
146 'shaving': _('Shaving'),
147 'taking_a_bath': _('Taking a Bath'),
148 'taking_a_shower': _('Taking a Shower')},
149 'having_appointment': {'category': _('Having an Appointment')},
150 'inactive': {'category': _('Inactive'),
151 'day_off': _('Day Off'),
152 'hanging_out': _('Hanging out'),
153 'hiding': _('Hiding'),
154 'on_vacation': _('On Vacation'),
155 'praying': _('Praying'),
156 'scheduled_holiday': _('Scheduled Holiday'),
157 'sleeping': _('Sleeping'),
158 'thinking': _('Thinking')},
159 'relaxing': {'category': _('Relaxing'),
160 'fishing': _('Fishing'),
161 'gaming': _('Gaming'),
162 'going_out': _('Going out'),
163 'partying': _('Partying'),
164 'reading': _('Reading'),
165 'rehearsing': _('Rehearsing'),
166 'shopping': _('Shopping'),
167 'smoking': _('Smoking'),
168 'socializing': _('Socializing'),
169 'sunbathing': _('Sunbathing'),
170 'watching_tv': _('Watching TV'),
171 'watching_a_movie': _('Watching a Movie')},
172 'talking': {'category': _('Talking'),
173 'in_real_life': _('In Real Life'),
174 'on_the_phone': _('On the Phone'),
175 'on_video_phone': _('On Video Phone')},
176 'traveling': {'category': _('Traveling'),
177 'commuting': _('Commuting'),
178 'cycling': _('Cycling'),
179 'driving': _('Driving'),
180 'in_a_car': _('In a Car'),
181 'on_a_bus': _('On a Bus'),
182 'on_a_plane': _('On a Plane'),
183 'on_a_train': _('On a Train'),
184 'on_a_trip': _('On a Trip'),
185 'walking': _('Walking')},
186 'working': {'category': _('Working'),
187 'coding': _('Coding'),
188 'in_a_meeting': _('In a Meeting'),
189 'studying': _('Studying'),
190 'writing': _('Writing')}}
192 TUNE_DATA
= ['artist', 'title', 'source', 'track', 'length']
194 LOCATION_DATA
= ['accuracy', 'alt', 'area', 'bearing', 'building', 'country',
195 'countrycode', 'datum', 'description', 'error', 'floor', 'lat',
196 'locality', 'lon', 'postalcode', 'region', 'room', 'speed', 'street',
197 'text', 'timestamp', 'uri']
203 log
= logging
.getLogger('gajim.c.pep')
205 from common
import helpers
206 from common
import xmpp
207 from common
import gajim
209 import gtkgui_helpers
212 class AbstractPEP(object):
218 def get_tag_as_PEP(cls
, jid
, account
, event_tag
):
219 items
= event_tag
.getTag('items', {'node': cls
.namespace
})
221 log
.debug("Received PEP 'user %s' from %s" % (cls
.type, jid
))
222 return cls(jid
, account
, items
)
226 def __init__(self
, jid
, account
, items
):
227 self
._pep
_specific
_data
, self
._retracted
= self
._extract
_info
(items
)
229 self
._update
_contacts
(jid
, account
)
230 if jid
== gajim
.get_jid_from_account(account
):
231 self
._update
_account
(account
)
233 def _extract_info(self
, items
):
234 '''To be implemented by subclasses'''
235 raise NotImplementedError
237 def _update_contacts(self
, jid
, account
):
238 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
240 if self
.type in contact
.pep
:
241 del contact
.pep
[self
.type]
243 contact
.pep
[self
.type] = self
245 def _update_account(self
, account
):
246 acc
= gajim
.connections
[account
]
248 if self
.type in acc
.pep
:
249 del acc
.pep
[self
.type]
251 acc
.pep
[self
.type] = self
253 def asPixbufIcon(self
):
254 '''SHOULD be implemented by subclasses'''
257 def asMarkupText(self
):
258 '''SHOULD be implemented by subclasses'''
262 class UserMoodPEP(AbstractPEP
):
263 '''XEP-0107: User Mood'''
266 namespace
= xmpp
.NS_MOOD
268 def _extract_info(self
, items
):
271 for item
in items
.getTags('item'):
272 mood_tag
= item
.getTag('mood')
274 for child
in mood_tag
.getChildren():
275 name
= child
.getName().strip()
277 mood_dict
['text'] = child
.getData()
279 mood_dict
['mood'] = name
281 retracted
= items
.getTag('retract') or not 'mood' in mood_dict
282 return (mood_dict
, retracted
)
284 def asPixbufIcon(self
):
285 assert not self
._retracted
286 received_mood
= self
._pep
_specific
_data
['mood']
287 mood
= received_mood
if received_mood
in MOODS
else 'unknown'
288 pixbuf
= gtkgui_helpers
.load_mood_icon(mood
).get_pixbuf()
291 def asMarkupText(self
):
292 assert not self
._retracted
293 untranslated_mood
= self
._pep
_specific
_data
['mood']
294 mood
= self
._translate
_mood
(untranslated_mood
)
295 markuptext
= '<b>%s</b>' % gobject
.markup_escape_text(mood
)
296 if 'text' in self
._pep
_specific
_data
:
297 text
= self
._pep
_specific
_data
['text']
298 markuptext
+= ' (%s)' % gobject
.markup_escape_text(text
)
301 def _translate_mood(self
, mood
):
308 class UserTunePEP(AbstractPEP
):
309 '''XEP-0118: User Tune'''
312 namespace
= xmpp
.NS_TUNE
314 def _extract_info(self
, items
):
317 for item
in items
.getTags('item'):
318 tune_tag
= item
.getTag('tune')
320 for child
in tune_tag
.getChildren():
321 name
= child
.getName().strip()
322 data
= child
.getData().strip()
323 if child
.getName() in TUNE_DATA
:
324 tune_dict
[name
] = data
326 retracted
= items
.getTag('retract') or not ('artist' in tune_dict
or
327 'title' in tune_dict
)
328 return (tune_dict
, retracted
)
330 def asPixbufIcon(self
):
332 path
= os
.path
.join(gajim
.DATA_DIR
, 'emoticons', 'static', 'music.png')
333 return gtk
.gdk
.pixbuf_new_from_file(path
)
335 def asMarkupText(self
):
336 assert not self
._retracted
337 tune
= self
._pep
_specific
_data
339 artist
= tune
.get('artist', _('Unknown Artist'))
340 artist
= gobject
.markup_escape_text(artist
)
342 title
= tune
.get('title', _('Unknown Title'))
343 title
= gobject
.markup_escape_text(title
)
345 source
= tune
.get('source', _('Unknown Source'))
346 source
= gobject
.markup_escape_text(source
)
348 tune_string
= _('<b>"%(title)s"</b> by <i>%(artist)s</i>\n'
349 'from <i>%(source)s</i>') % {'title': title
,
350 'artist': artist
, 'source': source
}
354 class UserActivityPEP(AbstractPEP
):
355 '''XEP-0108: User Activity'''
358 namespace
= xmpp
.NS_ACTIVITY
360 def _extract_info(self
, items
):
363 for item
in items
.getTags('item'):
364 activity_tag
= item
.getTag('activity')
366 for child
in activity_tag
.getChildren():
367 name
= child
.getName().strip()
368 data
= child
.getData().strip()
370 activity_dict
['text'] = data
372 activity_dict
['activity'] = name
373 for subactivity
in child
.getChildren():
374 subactivity_name
= subactivity
.getName().strip()
375 activity_dict
['subactivity'] = subactivity_name
377 retracted
= items
.getTag('retract') or not 'activity' in activity_dict
378 return (activity_dict
, retracted
)
380 def asPixbufIcon(self
):
381 assert not self
._retracted
382 pep
= self
._pep
_specific
_data
383 activity
= pep
['activity']
385 has_known_activity
= activity
in ACTIVITIES
386 has_known_subactivity
= (has_known_activity
and ('subactivity' in pep
)
387 and (pep
['subactivity'] in ACTIVITIES
[activity
]))
389 if has_known_activity
:
390 if has_known_subactivity
:
391 subactivity
= pep
['subactivity']
392 return gtkgui_helpers
.load_activity_icon(activity
, subactivity
).get_pixbuf()
394 return gtkgui_helpers
.load_activity_icon(activity
).get_pixbuf()
396 return gtkgui_helpers
.load_activity_icon('unknown').get_pixbuf()
398 def asMarkupText(self
):
399 assert not self
._retracted
400 pep
= self
._pep
_specific
_data
401 activity
= pep
['activity']
402 subactivity
= pep
['subactivity'] if 'subactivity' in pep
else None
403 text
= pep
['text'] if 'text' in pep
else None
405 if activity
in ACTIVITIES
:
406 # Translate standard activities
407 if subactivity
in ACTIVITIES
[activity
]:
408 subactivity
= ACTIVITIES
[activity
][subactivity
]
409 activity
= ACTIVITIES
[activity
]['category']
411 markuptext
= '<b>' + gobject
.markup_escape_text(activity
)
413 markuptext
+= ': ' + gobject
.markup_escape_text(subactivity
)
416 markuptext
+= ' (%s)' % gobject
.markup_escape_text(text
)
420 class UserNicknamePEP(AbstractPEP
):
421 '''XEP-0172: User Nickname'''
424 namespace
= xmpp
.NS_NICK
426 def _extract_info(self
, items
):
428 for item
in items
.getTags('item'):
429 child
= item
.getTag('nick')
431 nick
= child
.getData()
434 retracted
= items
.getTag('retract') or not nick
435 return (nick
, retracted
)
437 def _update_contacts(self
, jid
, account
):
438 nick
= '' if self
._retracted
else self
._pep
_specific
_data
439 for contact
in gajim
.contacts
.get_contacts(account
, jid
):
440 contact
.contact_name
= nick
442 def _update_account(self
, account
):
444 gajim
.nicks
[account
] = gajim
.config
.get_per('accounts', account
, 'name')
446 gajim
.nicks
[account
] = self
._pep
_specific
_data
449 class UserLocationPEP(AbstractPEP
):
450 '''XEP-0080: User Location'''
453 namespace
= xmpp
.NS_LOCATION
455 def _extract_info(self
, items
):
458 for item
in items
.getTags('item'):
459 location_tag
= item
.getTag('geoloc')
461 for child
in location_tag
.getChildren():
462 name
= child
.getName().strip()
463 data
= child
.getData().strip()
464 if child
.getName() in LOCATION_DATA
:
465 location_dict
[name
] = data
467 retracted
= items
.getTag('retract') or not location_dict
468 return (location_dict
, retracted
)
470 def _update_account(self
, account
):
471 AbstractPEP
._update
_account
(self
, account
)
472 con
= gajim
.connections
[account
].location_info
= \
473 self
._pep
_specific
_data
475 def asPixbufIcon(self
):
476 path
= gtkgui_helpers
.get_icon_path('gajim-earth')
477 return gtk
.gdk
.pixbuf_new_from_file(path
)
479 def asMarkupText(self
):
480 assert not self
._retracted
481 location
= self
._pep
_specific
_data
484 for entry
in location
.keys():
485 text
= location
[entry
]
486 text
= gobject
.markup_escape_text(text
)
487 location_string
+= '\n<b>%(tag)s</b>: %(text)s' % \
488 {'tag': entry
.capitalize(), 'text': text
}
490 return location_string
.strip()
493 SUPPORTED_PERSONAL_USER_EVENTS
= [UserMoodPEP
, UserTunePEP
, UserActivityPEP
,
494 UserNicknamePEP
, UserLocationPEP
]
496 from common
.connection_handlers_events
import PEPReceivedEvent
498 class ConnectionPEP(object):
500 def __init__(self
, account
, dispatcher
, pubsub_connection
):
501 self
._account
= account
502 self
._dispatcher
= dispatcher
503 self
._pubsub
_connection
= pubsub_connection
504 self
.reset_awaiting_pep()
506 def pep_change_account_name(self
, new_name
):
507 self
._account
= new_name
509 def reset_awaiting_pep(self
):
510 self
.to_be_sent_activity
= None
511 self
.to_be_sent_mood
= None
512 self
.to_be_sent_tune
= None
513 self
.to_be_sent_nick
= None
514 self
.to_be_sent_location
= None
516 def send_awaiting_pep(self
):
518 Send pep info that were waiting for connection
520 if self
.to_be_sent_activity
:
521 self
.send_activity(*self
.to_be_sent_activity
)
522 if self
.to_be_sent_mood
:
523 self
.send_mood(*self
.to_be_sent_mood
)
524 if self
.to_be_sent_tune
:
525 self
.send_tune(*self
.to_be_sent_tune
)
526 if self
.to_be_sent_nick
:
527 self
.send_nick(self
.to_be_sent_nick
)
528 if self
.to_be_sent_location
:
529 self
.send_location(self
.to_be_sent_location
)
530 self
.reset_awaiting_pep()
532 def _pubsubEventCB(self
, xmpp_dispatcher
, msg
):
533 ''' Called when we receive <message /> with pubsub event. '''
534 gajim
.nec
.push_incoming_event(PEPReceivedEvent(None, conn
=self
,
537 def send_activity(self
, activity
, subactivity
=None, message
=None):
538 if self
.connected
== 1:
539 # We are connecting, keep activity in mem and send it when we'll be
541 self
.to_be_sent_activity
= (activity
, subactivity
, message
)
543 if not self
.pep_supported
:
545 item
= xmpp
.Node('activity', {'xmlns': xmpp
.NS_ACTIVITY
})
547 i
= item
.addChild(activity
)
549 i
.addChild(subactivity
)
551 i
= item
.addChild('text')
553 self
._pubsub
_connection
.send_pb_publish('', xmpp
.NS_ACTIVITY
, item
, '0')
555 def retract_activity(self
):
556 if not self
.pep_supported
:
558 self
.send_activity(None)
559 # not all client support new XEP, so we still retract
560 self
._pubsub
_connection
.send_pb_retract('', xmpp
.NS_ACTIVITY
, '0')
562 def send_mood(self
, mood
, message
=None):
563 if self
.connected
== 1:
564 # We are connecting, keep mood in mem and send it when we'll be
566 self
.to_be_sent_mood
= (mood
, message
)
568 if not self
.pep_supported
:
570 item
= xmpp
.Node('mood', {'xmlns': xmpp
.NS_MOOD
})
574 i
= item
.addChild('text')
576 self
._pubsub
_connection
.send_pb_publish('', xmpp
.NS_MOOD
, item
, '0')
578 def retract_mood(self
):
579 if not self
.pep_supported
:
582 # not all client support new XEP, so we still retract
583 self
._pubsub
_connection
.send_pb_retract('', xmpp
.NS_MOOD
, '0')
585 def send_tune(self
, artist
='', title
='', source
='', track
=0, length
=0,
587 if self
.connected
== 1:
588 # We are connecting, keep tune in mem and send it when we'll be
590 self
.to_be_sent_tune
= (artist
, title
, source
, track
, length
, items
)
592 if not self
.pep_supported
:
594 item
= xmpp
.Node('tune', {'xmlns': xmpp
.NS_TUNE
})
596 i
= item
.addChild('artist')
599 i
= item
.addChild('title')
602 i
= item
.addChild('source')
605 i
= item
.addChild('track')
608 i
= item
.addChild('length')
611 item
.addChild(payload
=items
)
612 self
._pubsub
_connection
.send_pb_publish('', xmpp
.NS_TUNE
, item
, '0')
614 def retract_tune(self
):
615 if not self
.pep_supported
:
618 # not all client support new XEP, so we still retract
619 self
._pubsub
_connection
.send_pb_retract('', xmpp
.NS_TUNE
, '0')
621 def send_nickname(self
, nick
):
622 if self
.connected
== 1:
623 # We are connecting, keep nick in mem and send it when we'll be
625 self
.to_be_sent_nick
= nick
627 if not self
.pep_supported
:
629 item
= xmpp
.Node('nick', {'xmlns': xmpp
.NS_NICK
})
631 self
._pubsub
_connection
.send_pb_publish('', xmpp
.NS_NICK
, item
, '0')
633 def retract_nickname(self
):
634 if not self
.pep_supported
:
636 self
.send_nickname(None)
637 # not all client support new XEP, so we still retract
638 self
._pubsub
_connection
.send_pb_retract('', xmpp
.NS_NICK
, '0')
640 def send_location(self
, info
):
641 if self
.connected
== 1:
642 # We are connecting, keep location in mem and send it when we'll be
644 self
.to_be_sent_location
= info
646 if not self
.pep_supported
:
648 item
= xmpp
.Node('geoloc', {'xmlns': xmpp
.NS_LOCATION
})
649 for field
in LOCATION_DATA
:
650 if info
.get(field
, None):
651 i
= item
.addChild(field
)
652 i
.addData(info
[field
])
653 self
._pubsub
_connection
.send_pb_publish('', xmpp
.NS_LOCATION
, item
, '0')
655 def retract_location(self
):
656 if not self
.pep_supported
:
658 self
.send_location({})
659 # not all client support new XEP, so we still retract
660 self
._pubsub
_connection
.send_pb_retract('', xmpp
.NS_LOCATION
, '0')