[tuxdroid-svn] r5916 - in software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/exe
Status: Beta
Brought to you by:
ks156
From: jerome <c2m...@c2...> - 2009-11-27 10:54:13
|
Author: jerome Date: 2009-11-27 11:53:59 +0100 (Fri, 27 Nov 2009) New Revision: 5916 Added: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/EmoticonsToAttitunes.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/communicator.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/connector.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/errors.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/tests/ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/tests/AsyncTests.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/tests/ConnectionTests.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/tests/SyncTests.py software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/utils.py Modified: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/plugin-skype.py Log: * Added refactored project. Added: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/EmoticonsToAttitunes.py =================================================================== --- software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/EmoticonsToAttitunes.py (rev 0) +++ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/EmoticonsToAttitunes.py 2009-11-27 10:53:59 UTC (rev 5916) @@ -0,0 +1,95 @@ +# Copyright (C) 2009 Kysoh Sa +# Remi Jocaille <rem...@c2...> +# Distributed under the terms of the GNU General Public License +# http://www.gnu.org/copyleft/gpl.html + +EMOTICONS_TO_ATTITUNES = { + ':)' : "msn_happy", + ':-)' : "msn_happy", + ':d' : "msn_laughing", + ':-d' : "msn_laughing", + '=d' : "msn_laughing", + 'lol' : "msn_laughing", + 'mdr' : "msn_laughing_wild", + ':(' : "msn_sad", + ':-(' : "msn_sad", + ':p' : "msn_sticking_out_tongue", + ':-p' : "msn_sticking_out_tongue", + '=p' : "msn_sticking_out_tongue", + ';)' : "msn_winky", + ';-)' : "msn_winky", + ':o' : "msn_gasp", + ':-o' : "msn_gasp", + '(h)' : "msn_yeah", + 'b-)' : "msn_yeah", + '8-)' : "msn_duh", + ':@' : "msn_angry", + ':-@' : "msn_angry", + 'x(' : "msn_angry", + 'x-(' : "msn_angry", + ':s' : "msn_confused", + ':-s' : "msn_confused", + 'o_o' : "msn_confused", + 'o.o' : "msn_confused", + '^o)' : "msn_confused", + ':$' : "msn_embarrassed", + ':-$' : "msn_embarrassed", + ":'(" : "msn_crying", + ":'-(" : "msn_crying", + '(a)' : "msn_angel", + '8o|' : "msn_angry", + '8-|' : "msn_nerd", + ':-|' : "msn_disappointed", + ':|' : "msn_disappointed", + '+o(' : "msn_sick", + '<:o)' : "msn_party", + '|-)' : "msn_asleep", + ':-#' : "msn_shh", + ':#' : "msn_shh", + ':-*' : "msn_secret", + ':^)' : "msn_dunno", + '*-)' : "msn_dunno", + '(l)' : "msn_kiss", + '(u)' : "msn_crying", + '(@)' : "msn_cat", + '(&)' : "msn_dog", + '(bah)' : "msn_sheep", + '(sn)' : "msn_disappointed", + '(s)' : "msn_asleep", + '(t)' : "msn_telephone", + '(mp)' : "msn_mobile", + '(k)' : "msn_kiss", + '(6)' : "msn_laughing_evil", + ':-[' : "msn_laughing_evil", + ':[' : "msn_laughing_evil", + '(o)' : "msn_clock", + '(f)' : "msn_kiss", + '(ap)' : "msn_airplane", + '(au)' : "msn_auto", + '(p)' : "msn_camera", + '(mo)' : "msn_money", + '(~)' : "msn_filmstrip", + '(b)' : "msn_beer", + '(d)' : "msn_martini", + '(li)' : "msn_lightning", + '(st)' : "msn_stormy", + '(um)' : "msn_stormy", + '(e)' : "msn_email", + '(n)' : "msn_disappointed", + '(y)' : "msn_yeah", + '(c)' : "msn_coffee", + '(pl)' : "msn_eating", + '(pi)' : "msn_eating", + '(||)' : "msn_eating", + '(i)' : "msn_idea", + '(^)' : "msn_birthday", + '({)' : "msn_hugging", + '(})' : "msn_hugging", + '(co)' : "msn_computer", + '(w)' : "msn_sad", + '(ip)' : "msn_island", + '(so)' : "msn_soccer", + '(*)' : "msn_star", + '(8)' : "msn_note", + '(g)' : "msn_gift", +} Added: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/communicator.py =================================================================== --- software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/communicator.py (rev 0) +++ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/communicator.py 2009-11-27 10:53:59 UTC (rev 5916) @@ -0,0 +1,707 @@ +'''This file is part of "TuxDroid plugin Skype". + * Copyright 2009, kysoh + * Author : Conan Jerome. + * eMail : jer...@ky... + * Site : http://www.kysoh.com/ + * + * "TuxDroid plugin Skype" 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; either version 2.1 of + * the License, or (at your option) any later version. + * + * "TuxDroid plugin Skype" 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 "TuxDroid plugin Skype"; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + *''' + +import os +import time +import connector +import Skype4Py +import threading +from Skype4Py.utils import tounicode +from utils import AudioUtils, StringUtils +from EmoticonsToAttitunes import EMOTICONS_TO_ATTITUNES +from errors import EquipmentException, UnavailableContactException, UserNotFindException + + +class SynchroniousCommands(object): + ''' + Handle skype synchronious commands. + ''' + + connectionObj = None + currentUser = None + + #Audio card values. + IN = 'default' + OUT = 'default' + RINGER = 'default' + + #Online status + ONLINE = 'ONLINE' + AWAY = 'AWAY' + NA = 'NA' + DND = 'DND' + SKYPEME = 'SKYPEME' + OFFLINE = 'OFFLINE' + INVISIBLE = 'INVISIBLE' + + #Contacts hash datas. + contacts = [] + + #List available online statuses. + contacts_ols = ['ONLINE', 'AWAY', 'NA', 'DND', 'SKYPEOUT', 'SKYPEME'] + + #List available offline statuses. + contacts_ofs = [u'UNKNOWN', u'OFFLINE', u'INVISIBLE'] + + + +################################################### +####### Private functions ############ + + def __init__(self, Connector): + ''' + Init procedure. + ''' + self.connectionObj = Connector + + #Updating audio cards values. + if os.name == 'nt': + self.IN = AudioUtils.getSoundDeviceNameTuxdroidMicro() + self.OUT = AudioUtils.getSoundDeviceNameTuxdroidAudio() + self.RINGER = self.OUT + + #Getting current user. + try: + self.currentUser = connectionObj.getSkypeAPI().CurrentUser + except: + self.currentUser = 'default' + + + def __set_tux_in_out__(self): + ''' + Set Tux Droid as audio card for Skype client. + ''' + try: + self.connectionObj.getSkypeAPI().Settings.AudioIn = self.IN + self.connectionObj.getSkypeAPI().Settings.AudioOut = self.OUT + self.connectionObj.getSkypeAPI().Settings.Ringer = self.RINGER + + sleep(1) + #Checking if Tux Droid was successfully set as audio card. + if not self.isTuxDroidAudioCard(): + return False + return True + except: + #Return False, maybe Tux Droid is not connected. + return False + + + def __is_tux_audio__(self): + ''' + Return true if Tux Droid is set as audio card. + ''' + if not ( self.connectionObj.getSkypeAPI().Settings.AudioIn == self.IN ): + return False + if not ( self.connectionObj.getSkypeAPI().Settings.AudioOut == self.OUT ): + return False + if not ( self.connectionObj.getSkypeAPI().Settings.Ringer == self.RINGER ): + return False + + return True + + + + def __fetch_contacts__(self): + ''' + Querry client api to fetch contact list. + ''' + if self.currentUser != self.connectionObj.getSkypeAPI().CurrentUser: + self.contacts = [] + + #Getting Skype connector lock before starting. + if self.connectionObj.isSkypeLocked(): + #Waiting until connector object is available. + time.sleep(0.2) + try: + #Request connector for the commands lock. + self.connectionObj.requestSkypeLock() + #Getting contacts. + self.currentUser = self.connectionObj.getSkypeAPI().CurrentUser + + for user in self.connectionObj.getSkypeAPI().Friends: + uDatas = {} + uDatas['handle'] = tounicode(user.Handle) + uDatas['full_name'] = tounicode(user.FullName) + uDatas['display_name'] = tounicode(user.DisplayName) + + name = '' + #Getting tts name to use. + if tounicode(uDatas['display_name']) != u'': + name = StringUtils.toPrettyString(uDatas['display_name']) + elif tounicode(uDatas['full_name']) != u'': + name = StringUtils.toPrettyString(uDatas['full_name']) + else: + name = StringUtils.toPrettyString(uDatas['handle']) + + uDatas['name'] = tounicode(name).lower() + self.contacts.append(uDatas) + finally: + self.connectionObj.releaseSkypeLock() + return self.contacts + + + def __get_user_tts__(self, handle): + ''' + Return the 'name' value of a user for tts use. + @Param : User skype name. + ''' + for user in self.contacts: + if tounicode(handle) == tounicode(user['handle']): + return user['name'] + return None + + + + def __get_user_handle__(self, ttsName): + ''' + Return the user handle for a tts name. + ''' + for user in self.contacts: + if tounicode(ttsName) == tounicode(user['ttsName']): + return user['handle'] + return None + + + + def __get_online_list__(self): + ''' + Return a tts name online list + ''' + online = [] + for user in self.contacts: + try: + #usr = skype api user objet. + usr = self.connectionObj.getSkypeAPI().User(user['handle']) + + if usr.OnlineStatus in self.contacts_ols: + #append tts name of this user. + online.append(user['name']) + except: + #do not add this contact in case of exception. + pass + + return sorted(online) + + + def __call_contact__(self, ttsName): + ''' + Proceed to call a contact. + Raise equipement error + Raise unavailable contact error + ''' + #Getting user handle to call first of all. + skypeCall = None + for user in self.contacts: + if user['name'] == ttsName: + skypeCall = user['handle'] + + if skypeCall == None: + #The wanted contact is not into the contacts list. + raise UnavailableContactException() + + skypeCall = self.connectionObj.getSkypeAPI().User(skypeCall) + if skypeCall.OnlineStatus in self.contacts_ofs: + raise UnavailableContactException() + + if skypeCall.HasCallEquipment: + #User has call equipment, proceeding call. + self.connectionObj.getSkypeAPI().PlaceCall(skypeCall.Handle) + else: + #User has no equipment, exiting. + raise EquipmentException() + + + def __finish_call__(self): + ''' + Finish inprogress call. + ''' + for aCall in self.connectionObj.getSkypeAPI().ActiveCalls: + aCall.Finish() + + + def __get_call__(self, Call): + ''' + Place a call in progress. + ''' + Call.Answer() + + + + def __user_add__(self, handle): + ''' + Add a user into the contacts list. + ''' + user = None + + try: + #Getting user object. + user = self.connectionObj.getSkypeAPI().User(handle) + uDatas = {} + uDatas['handle'] = tounicode(user.Handle) + uDatas['full_name'] = tounicode(user.FullName) + uDatas['display_name'] = tounicode(user.DisplayName) + + name = '' + #Getting tts name to use. + if tounicode(uDatas['display_name']) != u'': + name = StringUtils.toPrettyString(uDatas['display_name']) + elif tounicode(uDatas['full_name']) != u'': + name = StringUtils.toPrettyString(uDatas['full_name']) + else: + name = StringUtils.toPrettyString(uDatas['handle']) + + uDatas['name'] = tounicode(name) + self.contacts.append(uDatas) + except: + #User was not find + raise UserNotFindException() + + + + def __user_del__(self, handle): + ''' + remove a user from the contacts list. + ''' + user = None + + try: + user = self.connectionObj.getSkypeAPI().User(handle) + #Searching for user in the contacts list. + for user in self.contacts: + if tounicode(user['handle']) == tounicode(handle): + #User was find, then removing its entry. + self.contacts.remove( user ) + except Excetion, e: + raise UserNotFindException + + + + def __is_valid_contact__(self, ttsName): + ''' + Return true if tts username is in the contacts list. + ''' + try: + for user in self.contacts: + if tounicode(user['name']) == tounicode(ttsName): + return True + return False + except: + pass + + + def __set_online_status__(self, status): + ''' + Change the user status. + ''' + try: + if ( status in self.contacts_ols ) or ( status in self.contacts_ofs): + #if status allowed then updating. + self.connectionObj.getSkypeAPI().ChangeUserStatus(status) + except: + pass + + + def __send_message__(self, handle, text): + ''' + Send a message to the user handle. + Return true if success, false otherwise. + ''' + try: + if ( handle != None ) and ( len(text) > 0 ): + message = self.connectionObj.getSkypeAPI().CreateChatWith(handle) + message.SendMessage(tounicode(text)) + return True + return False + except: + return False + + + +################################################### +####### User functions ############ + + def getCurrentUser(self): + ''' + Return the current Skype user. + ''' + return self.currentUser + + + def getContacts(self): + ''' + Fetch user contact list. + ''' + return self.__fetch_contacts__() + + + def isTuxDroidAudioCard(self): + ''' + Return true if Tux Droid is set as audio card. + ''' + return self.__is_tux_audio__() + + + def setAudioCards(self): + ''' + Set Tux Droid as audio card for Skype client. + ''' + self.__set_tux_in_out__() + + + def getTTSName(self, user): + ''' + Return the tts name for this user handle. + ''' + return self.__get_user_tts__(user) + + + def getHandle(self, ttsName): + ''' + Return the handle os a user. + ''' + return self.__get_user_handle__(ttsName) + + + def getOnlineList(self): + ''' + Return a complete online / skypeout user list. + ''' + return self.__get_online_list__() + + + def call(self, ttsName): + ''' + Place a call. + Raise 'EquimentException' and 'UnavailableContactException' + ''' + self.__call_contact__(ttsName) + + + def adduser(self, userHandle): + ''' ( in case of add user skype event ) + ''' + self.__user_add__(userHandle) + + + def deluser(self, userHandle): + ''' ( in case of remove user skype event ) + ''' + self.__user_del__(userHandle) + + + def isUserInList(self, ttsName): + ''' + Check if a user is in the contact list. + ''' + return self.__is_valid_contact__(ttsName) + + + def finishCall(self): + ''' + Finish a call. + ''' + self.__finish_call__() + + + def rejectCall(self): + ''' + Reject a call + ''' + self.finishCall() + + + def acceptCall(self, Call): + ''' + Accept an incomming call. + ''' + self.__get_call__(Call) + + + def setOnlineStatus(self, status): + ''' + Change use status. + ''' + self.__set_online_status__(status) + + + def sendTextMessage(self, handle, message): + ''' + Send a message to the user handle. + ''' + self.__send_message__(handle, message) + + + +class AsynchroniousCommands(object): + ''' + Handle Asynchronious commands and skype events. + ''' + + connectorObj = None + call = None + + ##### Incomming triggered events. #### + #@Params : ttsName skype name otherwise. + OnIncommingCall = None + + OnIncommingFinished = None + + #@Params : ttsName, skype name otherwise. + OnIncommingMessage = None + + #Incomming call was refused because a call is in progress. + # Sending event with user handle that tries to call. + OnIncommingCallRefused = None + + #### Outgoing triggered events #### + #@Params : ttsName, cut number otherwise. + OnOutgoingCall = None + + OnOutgoingFinished = None + + #Outgoing call was refused because a call is in progress. + OnOutgoingCallRefused = None + + #### Misc events #### + #@Params : ttsName , newStatus + OnlineContactStatusChanged = None + + #@Params : AttituneName. + OnAvailableEmoticon = None + + #@Params : TTS sentence. + OnAvailableTTSSentence = None + + +################################################### +####### Private functions ############ + + def __init__(self, Connector): + ''' + Init procedure + ''' + self.connectorObj = Connector + #Initializing events. + self.connectorObj.getSkypeAPI().OnOnlineStatus = self.__on_online_contact__ + self.connectorObj.getSkypeAPI().OnMessageStatus = self.__on_message_status__ + self.connectorObj.getSkypeAPI().OnCallStatus = self.__on_call_status__ + + + + def __on_online_contact__(self, User, Status): + ''' + ''' + if self.OnlineContactStatusChanged != None: + thread = threading.Thread(target=self.OnlineContactStatusChanged, args = [User.handle, Status]) + thread.start() + + + def __on_call_status__(self, Call, Status): + ''' + Event handler for skype client incomming / outgoing calls. + ''' + if Call.Type in [ Skype4Py.cltIncomingPSTN, Skype4Py.cltIncomingP2P]: + #Incomming call. + self.__on_incomming_call__(Call, Status) + else: + #Outgoing call. + self.__on_outgoing_call__(Call, Status) + + + + def __on_incomming_call__(self, Call, Status): + ''' + Trigger incomming call events. + ''' + if ( self.call is None ): + self.call = Call + + if ( self.call.Id == Call.Id ) : + self.call = Call + partnerHandle = Call.PartnerHandle + + if Status == Skype4Py.clsRinging: + if self.OnIncommingCall != None: + thread = threading.Thread(target=self.OnIncommingCall, args = [partnerHandle, ]) + thread.start() + + elif Status in [Skype4Py.clsFinished, Skype4Py.clsRefused, + Skype4Py.clsMissed, Skype4Py.clsFailed]: + if self.OnIncommingFinished != None: + self.call = None + thread = threading.Thread(target=self.OnIncommingFinished, args = [partnerHandle, ]) + thread.start() + + else: + callerHandle = None + try: + callerHandle = Call.PartnerHandle + try: + if Status == Skype4Py.clsRinging: + Call.Finish() + except: + pass + finally: + #Sending call refused event. + if ( self.OnIncommingCallRefused != None ) and ( Status == Skype4Py.clsRefused ): + thread = threading.Thread(target=self.OnIncommingCallRefused, args = [Call.PartnerHandle, ]) + thread.start() + + + + + def __on_outgoing_call__(self, Call, Status): + ''' + Trigger outgoing call event. + ''' + if ( self.call is None ): + self.call = Call + + if ( self.call.Id == Call.Id ) : + self.call = Call + partnerHandle = Call.PartnerHandle + + if Status == Skype4Py.clsRouting: + if self.OnOutgoingCall != None: + thread = threading.Thread(target=self.OnOutgoingCall, args = [partnerHandle, ]) + thread.start() + + elif Status in [Skype4Py.clsFinished, Skype4Py.clsRefused, + Skype4Py.clsCancelled, Skype4Py.clsFailed]: + if self.OnOutgoingFinished != None: + self.call = None + thread = threading.Thread(target=self.OnOutgoingFinished, args = [partnerHandle, ]) + thread.start() + + else: + callerHandle = None + try: + callerHandle = Call.PartnerHandle + try: + if Status == Skype4Py.clsRinging: + Call.Finish() + except: + pass + finally: + #Sending call refused event. + if ( self.OnOutgoingCallRefused != None ) and ( Status == Skype4Py.clsCancelled ): + thread = threading.Thread(target=self.OnOutgoingCallRefused, args = [Call.PartnerHandle, ]) + thread.start() + #Resume last call. + if self.call != None: + self.call.Resume() + + + + def __on_message_status__(self, Message, Status): + ''' + ''' + exclude_list = ['<partlist', '<part identity', '</name>', '<name>'] + exclude_message = False + if Status == 'RECEIVED': + + #Checking if message body is not empty. + try: + Message.Body.splitlines()[0] + except: + exclude_message = True + + #Excluding 'identity call' messages in case of most recent Skype clients. + + for excluded in exclude_list: + for msgPart in Message.Body.splitlines() : + if msgPart.find(excluded) != -1 : + exclude_message = True + break + + + if not exclude_message: + #Getting message user handle. + handle = tounicode(Message.FromHandle) + + missed = self.connectorObj.getSkypeAPI().MissedChats + for chat in missed: + #Checking chat state and throwing only if this message + #is in the 'missed chats' list. + if chat.Name == Message.ChatName: + + #throwing event. + if self.OnIncommingMessage != None: + thread = threading.Thread(target=self.OnIncommingMessage, args=[handle, ]) + thread.start() + + #Checking for matching emoticons / attitunes. + emoticons = threading.Thread(target = self.__check_emoticons__(Message.Body)) + emoticons.start() + + #Checking for matching tts requests. + tts = threading.Thread(target = self.__check_tts_sentence__, args=[Message.Body]) + tts.start() + + + + + def __check_emoticons__(self, message): + ''' + Check message for matching emoticons. + ''' + # Search for emoticon + for emoticon in EMOTICONS_TO_ATTITUNES.keys(): + if message.lower().find(emoticon) != -1: + if self.OnAvailableEmoticon != None: + arg = [EMOTICONS_TO_ATTITUNES[emoticon], ] + thread = threading.Thread(target = self.OnAvailableEmoticon, args = arg) + thread.start() + return + + + def __check_tts_sentence__(self, message): + ''' + Check if the message contains 'tux'. + ''' + # speak the text if begin is "tuxdroid>" + if message.find('tuxdroid>') == 0: + self.__trigger_tts__(message[9:]) + return + # speak the text if begin is "tuxdroid" + if message.find('tuxdroid') == 0: + self.__trigger_tts__(message[8:]) + return + # speak the text if begin is "tux>" + if message.find('tux>') == 0: + self.__trigger_tts__(message[4:]) + return + # speak the text if begin is "tux" + if message.find('tux') == 0: + self.__trigger_tts__(message[3:]) + return + + + def __trigger_tts__(self, message): + ''' + Trigger the 'tts sentence available' event. + ''' + if self.OnAvailableTTSSentence != None: + thread = threading.Thread(target = self.OnAvailableTTSSentence, args = [message, ]) + thread.start() + + \ No newline at end of file Added: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/connector.py =================================================================== --- software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/connector.py (rev 0) +++ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/connector.py 2009-11-27 10:53:59 UTC (rev 5916) @@ -0,0 +1,258 @@ +'''This file is part of "TuxDroid plugin Skype". + * Copyright 2009, kysoh + * Author : Conan Jerome. + * eMail : jer...@ky... + * Site : http://www.kysoh.com/ + * + * "TuxDroid plugin Skype" 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; either version 2.1 of + * the License, or (at your option) any later version. + * + * "TuxDroid plugin Skype" 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 "TuxDroid plugin Skype"; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + *''' + +import os +import Skype4Py +import threading +from time import sleep +from threading import Thread +from utils import ExtendedThread, SkypeClient + +class Connector(object): + '''' + This class manage the skype client-api connection + ''' + #This boolean flag indicates if Skype client was started by the connector + #To force close when connector shuts down. + startedByConnector = False + #Event sent if Skype client was closed. + OnSkypeClosed = None + OnAPIReady = None + triggerSkypeClosed = True + + #Skype4Py API object. + skype = None + timeout = 60 + #Allow plugin to start commands or not. + lock = False + apiReady = False + + #Skype client object. + skypeClient = None + + #Skype client Loop thread. + __clientLoop__ = None + + +################################################### +####### Private functions ############ + + def __new_skype_status__(self, status): + ''' Skype api Event handler. + If Skype is closed and reopened, it informs us about it + so we can reattach. + ''' + + #If api is not attached, then setting 'api not ready' values + if status != Skype4Py.apiAttachSuccess: + self.apiReady = False + + #API attach is available, meaning api was disconnected. + if status == Skype4Py.apiAttachAvailable: + self.skype.OnAttachmentStatus = self.__new_skype_status__ + #Attaching api. + self.skype.Attach() + + #Skype api was attached successfully, initializing values. + if status == Skype4Py.apiAttachSuccess: + self.apiReady = True + self.triggerSkypeClosed = True + if self.OnAPIReady != None: + #Notify client. + thread = Thread(target=self.OnAPIReady) + thread.start() + + + + def __client_loop__(self): + ''' + Check for unexcpected client shutdown. + Used with 'ExtendedThread' object. + ''' + #Check if skype is closed. + if not self.skypeClient.isRunning(): + self.__onSkypeClosed__() + + + + def __onSkypeClosed__(self): + ''' + Handle the case where Skype was closed, crashed, ... + ''' + self.apiReady = False + + #Sending event only if it was not triggered for the same api instance.. + if self.triggerSkypeClosed: + if(self.OnSkypeClosed != None): + self.triggerSkypeClosed = False + self.OnSkypeClosed() + + + def __connect_api__(self): + ''' + Connect skype api. + ''' + sleep(2.0) + try: + self.skype.OnAttachmentStatus = self.__new_skype_status__ + self.skype.Attach() + + except Exception: + self.apiReady = False + + + + def __connect_all__(self): + ''' + SkypeStarted received, so , connect all. + ''' + #Initializing Skype4Py object. + if os.name == 'nt': + self.skype = Skype4Py.Skype() + else: + self.skype = Skype4Py.Skype(Transport='x11') + + #Starting skype client check. + self.__clientLoop__ = ExtendedThread(target=self.__client_loop__) + self.__clientLoop__.start() + + self.skype.OnAttachmentStatus = self.__new_skype_status__ + + self.apiReady = False + apiConnection = Thread(target= self.__connect_api__) + apiConnection.start() + + + + def __start__(self): + ''' + Start connection between client-application + ''' + #Creating skype client helper object. + if os.name != 'nt': + self.skypeClient = SkypeClient(SkypeClient.LINUX) + else: + self.skypeClient = SkypeClient(SkypeClient.WINDOWS) + + #If skype client is not running start it and wait started to connect api + if not self.skypeClient.isRunning(): + self.startedByConnector = True + self.skypeClient.OnSkypeStarted = self.__connect_all__ + self.skypeClient.start() + #Else, connecting now. + else: + self.__connect_all__() + + + + def __stop__(self): + ''' + Stop all connection and close Skyp application if needed. + ''' + self.skype.OnAttachmentStatus = None + if self.__clientLoop__ != None: + self.__clientLoop__.stop() + + if ( KillClient == True ) or self.startedByConnector: + #Quitting Skype client. + self.skypeClient.stop() + self.skype = None + + +################################################### +####### User functions ################ + + def start(self): + ''' + Start connection. + ''' + self.__start__() + + + def stop(self, KillClient=False): + ''' + Stop connection. + ''' + self.__stop__() + + + def startClient(self, OnStarted=None): + ''' + Start skype client. + ''' + if OnStarted != None: + self.skypeClient.OnSkypeStarted = OnStarted + self.skypeClient.start() + + + def stopClient(self): + ''' + stop skype client. + ''' + self.skypeClient.stop() + + + def isClientRunning(self): + ''' + Return true if client is running, false otherwise. + ''' + return self.skypeClient.isRunning() + + + def isAPIReady(self): + ''' + Return true if skype api is ready. + ''' + return self.apiReady + + + def getSkypeAPI(self): + ''' + Return the skype api object. + ''' + return self.skype + + + def isSkypeLocked(self): + ''' + Return True if 'lock' was requested. + ''' + return self.lock + + + def requestSkypeLock(self): + ''' + Get the lock ( lock will avoid to start commands ). + ''' + if(not self.lock): + self.lock = True + return self.lock + + + def releaseSkypeLock(self): + ''' + Release a requested lock. + ''' + if(self.lock): + self.lock = False + return not self.lock + Added: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/errors.py =================================================================== --- software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/errors.py (rev 0) +++ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/errors.py 2009-11-27 10:53:59 UTC (rev 5916) @@ -0,0 +1,43 @@ +'''This file is part of "TuxDroid plugin Skype". + * Copyright 2009, kysoh + * Author : Conan Jerome. + * eMail : jer...@ky... + * Site : http://www.kysoh.com/ + * + * "TuxDroid plugin Skype" 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; either version 2.1 of + * the License, or (at your option) any later version. + * + * "TuxDroid plugin Skype" 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 "TuxDroid plugin Skype"; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + *''' + +class EquipmentException(Exception): + ''' + Handle Skype call equipment error. + ''' + pass + + +class UnavailableContactException(Exception): + ''' + Handle unavailable contact error. + ''' + pass + + +class UserNotFindException(Exception): + ''' + Handle 'not find' contact. + ''' + pass + + \ No newline at end of file Modified: software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/plugin-skype.py =================================================================== --- software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/plugin-skype.py 2009-11-27 10:50:17 UTC (rev 5915) +++ software_suite_v3/software/plugin/plugin-skype/branches/in_out_plugin/executables/plugin-skype.py 2009-11-27 10:53:59 UTC (rev 5916) @@ -1,682 +1,142 @@ -# -*- coding: utf-8 -*- - -# Copyright (C) 2009 Kysoh Sa -# Conan Jerome <jer...@ky...> -# Distributed under the terms of the GNU General Public License -# http://www.gnu.org/copyleft/gpl.html - +'''This file is part of "TuxDroid plugin Skype". + * Copyright 2009, kysoh + * Author : Conan Jerome. + * eMail : jer...@ky... + * Site : http://www.kysoh.com/ + * + * "TuxDroid plugin Skype" 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; either version 2.1 of + * the License, or (at your option) any later version. + * + * "TuxDroid plugin Skype" 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 "TuxDroid plugin Skype"; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + *''' + __author__ = "Conan Jerome" __appname__ = "Python gadget skype" __version__ = "0.0.4" __date__ = "2009/06/16" __license__ = "GPL" - -import os -import time + import sys +sys.path.append(os.environ['TUXDROID_SERVER_PYTHON_UTIL']) + import threading -import subprocess -import Skype4Py -import commands +import utils +import errors +import connector +import communicator -sys.path.append(os.environ['TUXDROID_SERVER_PYTHON_UTIL']) - from util.SimplePlugin.SimplePluginConfiguration import SimplePluginConfiguration from util.SimplePlugin.SimplePlugin import SimplePlugin -from util.system.TaskBar import refreshTaskBar -from util.system.Device import * -from Skype4Py.errors import SkypeAPIError - - -class Configuration(SimplePluginConfiguration): - """This class make an access to the plugin parameters. - Parameters are automatically filled by the SimpleGadget class at plugin - starting. - """ - + + class SkypePluginConfiguration(SimplePluginConfiguration): + ''' + Skype plugin configuration. + ''' + + throwEmoticons = True + + def __init__(self): - """Initialization of the class. - """ - # Call the super class + ''' + Configuration initialization. + ''' SimplePluginConfiguration.__init__(self) - # Initialize the parameters - self.__quitGadget = False - self.__startupStatus = "" - - def getQuitGadget(self): - return self.__quitGadget - - def setQuitGadget(self, quitGadget): - self.__quitGadget = quitGadget - - def getStartupStatus(self): - return self.__startupStatus - - def setStartupStatus(self, startupStatus): - self.__startupStatus = startupStatus - - + self.throwEmoticons = True + + + def getThrowEmoticons(): + ''' + Return true if emoticons will be thrown. + ''' + return self.throwEmoticons + + + def setThrowEmoticons(aThrowEmoticons): + ''' + Set the throwEmoticons parameter value. + ''' + self.throwEmoticons = aThrowEmoticons + + + class SkypePlugin(SimplePlugin): - """Skype plugin class. - """ - - def __init__(self): - """Initialization of the class. - """ - # Call the super class - SimplePlugin.__init__(self) - - # Initialize some values ... - self.__currentCall = None - self.__currentContactIndex = -1 - self.__contactsDict = {} - self.__contactsList = [] - self.__allowedStatus = { - "Online" : Skype4Py.olsOnline, - "Away" : Skype4Py.olsAway, - "Do not disturb" : Skype4Py.olsDoNotDisturb, - "Invisible" : Skype4Py.olsInvisible, - "Busy" : Skype4Py.olsNotAvailable, - "Skype me": Skype4Py.olsSkypeMe, - } - self.__mutexContacts = threading.Lock() - self.__mutexCall = threading.Lock() - self.__connectionMutex = threading.Lock() - # Skype api objects. - self.__skype = None - self.__apiAttachState = -1 - self.__activeMain = True - # Skype process - self.attemptTimeout = 0 - self.normalStart = False - self.quitSkypeClient = False - self.count = 1 - self.__skypeProcess = -1 - + ''' + Skype plugin base class. + ''' + + canRun = True + + #Skype client - api objects. + synchronious = None + asynchronious = None + connectorObj = None + def start(self): - """Plugin entry point. + ''' + Plugin entry point. This method should be used to dispatch commands. - """ - if os.name != 'nt': - self.__skype = Skype4Py.Skype(Transport='x11') - else: - self.__skype = Skype4Py.Skype() - + ''' if self.getCommand() == "run": self.run() else: self.run() - - def run(self): - """Plugin entry point for the "run" command. - """ - try: - self.__tuxBodyLedsBlink() - # Start Skype client if not started. - self.__startSkypeApp() - - if not self.__activeMain: - self.stop() - return - - # Connect to skype api. - - self.__connectSkypeAPI() - except SkypeAPIError: - pass - except: - self.throwMessage("I cannot get connected to your Skeyepe. Please, check if you are connected. And verify if I can access skype.") - self.stop() - - - def onPluginStop(self): - """Callback on plugin stop. - """ - def closeSkype(): - if self.__skype != None: - self.__activeMain = False - self.__skype._API.Close() - self.__skype = None - self.__apiAttachState = -1 - - if self.__skype.Client.IsRunning: - #Shutting down Skype application. - - if os.name != 'nt': - os.system("kill -15 `ps h --ppid " + str(self.__skypeProcess.pid ) + " | awk '{print $1}'` 2>/dev/null") - else: - self.__skype.Client.Shutdown() - refreshTaskBar() - try: - - #Sending 'Shutdown command' first to do not block the quit process that is quite long. - if self.quitSkypeClient: - if os.name != 'nt': - os.system("kill -15 `ps h --ppid " + str(self.__skypeProcess.pid ) + " | awk '{print $1}'` 2>/dev/null") - - else: - - self.__skype.Client.Shutdown() - - time.sleep(1.0) - - if self.__skype != None: - self.hangUp() - if self.__skype.Client.IsRunning: - closeSkype() - else: - self.__skype._API.Close() - self.__tuxBodyResetLeds() - self.__tuxBodyIdle() - except: - pass - - def onPluginEvent(self, eventName, eventValues): - """Callback on plugin event. - @param eventName: Event name. - @param eventValues: Event values. - """ - if self.__currentCall == None: - if eventName == "head": - if eventValues[0] == "True": - self.__userBtStartCall() - elif eventName == "left": - if eventValues[0] == "True": - self.__userBtNextContact() - elif eventName == "right": - if eventValues[0] == "True": - self.__userBtPreviousContact() - elif eventName == "remote": - if eventValues[0] == "K_OK": - self.__userBtStartCall() - elif eventValues[0] == "K_DOWN": - self.__userBtPreviousContact() - elif eventValues[0] == "K_UP": - self.__userBtNextContact() - elif eventValues[0] == "K_LEFT": - self.__userBtPreviousContact() - elif eventValues[0] == "K_RIGHT": - self.__userBtNextContact() - elif eventValues[0] == "K_RECEIVECALL": - self.__userBtStartCall() - else: - if eventName == "head": - if eventValues[0] == "True": - self.__userBtHangUp() - elif eventName == "remote": - if eventValues[0] == "K_OK": - self.__userBtHangUp() - elif eventValues[0] == "K_HANGUP": - self.__userBtHangUp() - - # ========================================================================== - # Skype API - # ========================================================================== - def __poolSkypeApplication(self): + + + def run(self): ''' - Pool Skype application to quit gadget in case of client not running. + Run plugin command. ''' + #Initialize skype client and api objects. + self.connectorObj = connector.Connector() + self.connectorObj.OnAPIReady = self.initialize + self.connectorObj.start() - while True: - time.sleep(0.2) - if ( not self.__skype.Client.IsRunning ): - self.stop() - - - - def __onOnlineStatus(self, User, Status): - ''' - Skype api online status received ( contact online status changed ). - ''' - #Refreshing contacts list. - if self.count <= 1: - self.__getContacts() - else: - self.__contact(User) - self.count = self.count + 1 + #Finally, starting daemon main loop + mainl = threading.Thread(target=self.mainloop) + mainl.start() - def __onSkypeAPIStatusReceived(self, value): - """Received api connection status. - """ - self.__apiAttachState = value + def initialize(self): + ''' + Initialize Skype objects and set events handlers. + ''' + #Api ready and connected so creatin other commands objects. + self.synchronious = SynchroniousCommands(self.connectorObj) + #Setting asynchronious obj. + self.asynchronious = AsynchroniousCommands(self.connectorObj) - if ( value in [Skype4Py.apiAttachAvailable, ] ): - self.__connectSkypeAPI() - - if ( value in [Skype4Py.apiAttachSuccess, ]) and (not (self.attemptTimeout == 8)): - # Set tux as audio card. - self.__selectTuxAsAudioCard() - time.sleep(0.2) - - # Get the contacts list. - if self.normalStart: - self.__getContacts() - - # Set the user status. - - status = self.configuration().getStartupStatus() - if self.__allowedStatus.has_key(status): - self.__skype.ChangeUserStatus(self.__allowedStatus[status]) - - #Start Pooling skype. - thread = threading.Thread(target = self.__poolSkypeApplication) - thread.start() - - - elif self.attemptTimeout == 8: - self.stop() - return - - - def __selectTuxAsAudioCard(self): - """Set tux as audio peripheral. - """ - if os.name == "nt": - audioIn = Device.getSoundDeviceNameTuxdroidMicro() - audioOut = Device.getSoundDeviceNameTuxdroidAudio() - if audioOut == None: - return - self.__sendCommandToSkype('SET AUDIO_IN %s' % audioIn) - self.__sendCommandToSkype('SET AUDIO_OUT %s' % audioOut) - self.__sendCommandToSkype('SET RINGER %s' % audioOut) - else: - self.__sendCommandToSkype('SET AUDIO_IN TuxDroid (plughw:TuxDroid,0)') - self.__sendCommandToSkype('SET AUDIO_OUT TuxDroid (plughw:TuxDroid,0)') - self.__sendCommandToSkype('SET RINGER TuxDroid (plughw:TuxDroid,0)') - - - def __sendCommandToSkype(self, command): - """Send a raw command to skype api. Return true if command was sent. - """ - if self.__skypeAPIIsConnected(): - try: - cmd_obj = self.__skype.Command(command, Block = True) - self.__skype.SendCommand(cmd_obj) - result = str((cmd_obj.Reply).encode('utf-8')) - return True - except: - return False - else: - return False - - def __connectSkypeAPI(self): - """Get connected to the Skype client. - """ - # Create and attach skype api - - self.__skype.OnOnlineStatus = self.__onOnlineStatus - self.__skype.OnAttachmentStatus = self.__onSkypeAPIStatusReceived - self.__skype.OnCallStatus = self.__onSkypeCall - - try: - self.__skype.Timeout = 10000 - self.__skype.Attach() - - except SkypeAPIError: - self.__skypeConnectionFailed() - - - def __skypeConnectionFailed(self): + def mainloop(self): ''' + Run main loop. ''' - while self.attemptTimeout != 6: - time.sleep(5) - try: - self.__skype.Attach() - except SkypeAPIError: - pass - else: - self.stop() - return - - - def __skypeAPIIsConnected(self): - """Return true if connected. - """ - return self.__apiAttachState in [0, ] - - - def __onOutgoingCall(self, Call, Status): - """Set outgoing calls functions. - """ - if Status == Skype4Py.clsRinging: - self.__tuxBodyLedsBlink() - if Status in [Skype4Py.clsFinished, Skype4Py.clsCancelled, - Skype4Py.clsRefused, Skype4Py.clsFailed, Skype4Py.clsBusy]: - self.__tuxBodyResetLeds() - self.__currentCall = None - self.__tuxBodyIdle() - if self.configuration().getQuitGadget(): - def async(): - time.sleep(2.0) - self.stop() - t = threading.Thread(target = async) - t.start() - return - elif Status in [Skype4Py.clsRinging, Skype4Py.clsEarlyMedia, - Skype4Py.clsRouting, Skype4Py.clsInProgress, Skype4Py.clsUnplaced]: - - if Status in [Skype4Py.clsInProgress, Skype4Py.clsUnplaced]: - self.__tuxBodyResetLeds() - self.__currentCall = Call - self.__tuxBodyOnCall() - - - def __onSkypeCall(self, Call, Status): - """Set calls callback. - """ - if (Call.Type == Skype4Py.cltOutgoingP2P) or \ - (Call.Type == Skype4Py.cltOutgoingPSTN): - self.__onOutgoingCall(Call, Status) - elif (Call.Type == Skype4Py.cltIncomingP2P) or \ - (Call.Type == Skype4Py.cltIncomingPSTN): - Call.Finish() - - def callCurrentContact(self, contact): - """Call a specified contact. - """ - try: - self.__mutexCall.acquire() - # Get skype name - callName = self.__contactsDict.get(contact) - # Place call - if (callName != None) and (self.__currentCall == None): - self.__currentCall = self.__skype.PlaceCall(callName) - self.__mutexCall.release() - except: - if self.__mutexCall.locked(): - self.__mutexCall.release() - return - - def hangUp(self): - """Finish all placed calls. - """ - try: - self.__mutexCall.acquire() - if self.__currentCall != None: - try: - self.__currentCall.Finish() - self.__currentCall = None - except: - self.__currentCall.Resume() - self.__currentCal = None - self.__mutexCall.release() - except: - if self.__mutexCall.locked(): - self.__mutexCall.release() - return - - # ========================================================================== - # Skype application control - # ========================================================================== - - def __getSkypeAppConnected(self): - """Check for skype connection - """ - if os.name != "nt": - return len(commands.getoutput("ps -A | grep skype")) > 0 - else: - cmd = ["tasklist", "/FI", "IMAGENAME eq skype.exe"] - process = subprocess.Popen(cmd, stdin = subprocess.PIPE, - stdout = subprocess.PIPE) - values = process.stdout.read().lower() - values = values.split() - return ("skype.exe" in values) - - def __startSkypeApp(self): - """This function starts skype. - """ - # Return if skype is already started. - if self.__getSkypeAppConnected(): - self.normalStart = True - return - self.quitSkypeClient = True - self.throwMessage("Please wait while I launch the skype application") - - if os.name == 'nt': - self.__skype.Client.Start(Minimized=False, Nosplash=True) - else: - self.__startSkypeAppLinux() + while self.canRun: + sleep(2.0) - def __startSkypeAppLinux(self): - """Start skype on linux ( thread needed to do not block the script ). - """ - # Searching for skype binary. - result = [] - found =False - - result = os.environ - - result = result['PATH'].split(':') - - for path in result: - cmd = 'ls ' + path + ' | grep skype' - res = commands.getoutput(cmd) - if res.find('skype') >= 0: - found = True - break - - if found: - #start skype - self.__skypeProcess = subprocess.Popen("skype", stdin = subprocess.PIPE, - stdout = subprocess.PIPE) - - while not self.__getSkypeAppConnected(): - time.sleep(1) - self.__activeMain = True - else: - self.throwMessage("Sorry, it looks like skype is not installed. Please go to the skype website to download the software.") - self.__activeMain = False - - - # ========================================================================== - # Tux Droid body - # ========================================================================== - - def __tuxBodyOnCall(self): - """ - """ - self.throwActuation("upFlippers") - self.throwActuation("openMouth") - - def __tuxBodyIdle(self): - """ - """ - self.throwActuation("downFlippers") - self.throwActuation("closeMouth") - - def __tuxBodyResetLeds(self): - """ - """ - self.throwActuation("ledsOff", "LED_BOTH") - self.throwActuation("ledsOn", "LED_BOTH", 1.0) - - def __tuxBodyLedsBlink(self): - """ - """ - self.throwActuation("ledsBlink", "LED_BOTH", 100, 0.5) - - # ========================================================================== - # Contacs list - # ========================================================================== - - def __contact(self, user): + def onPluginStop(self): ''' - Add a contacts from the contacts list. + OnPluginStop event. ''' - if user == self.__skype.CurrentUser: - return - - userFNEnc = user.FullName.encode("UTF-8").replace(" ", "_") - self.throwTrace(userFNEnc) - userDNEnc = user.DisplayName.encode("UTF-8").replace(" ", "_") - userHEnc = user.Handle.encode("UTF-8").replace(" ", "_") - - #Add contact. - if user.OnlineStatus not in [Skype4Py.olsUnknown, Skype4Py.olsOffline, Skype4Py.olsInvisible]: - - if not ( ( userHEnc, userDNEnc, userHEnc ) in self.__contactsList ): - - if len(userFNEnc) > 0: - self.__contactsDict[userFNEnc] = userHEnc - self.__contactsList.append(userFNEnc) - - elif len(userDNEnc) > 0: - self.__contactsDict[userDNEnc] = userHEnc - self.__contactsList.append(userDNEnc) - else: - self.__contactsDict[userHEnc] = userHEnc - self.__contactsList.append(userHEnc) - #Remove contact. - else: - - if userFNEnc in self.__contactsList: - self.__contactsList.remove(userFNEnc) - del self.__contactsDict[userFNEnc] - - elif userDNEnc in 0: - self.__contactsList.remove(userDNEnc) - del self.__contactsDict[userDNEnc] - - else: - self.__contactsList.remove(userHEnc) - del self.__contactsDict[userHEnc] - - - - - def __queryContacts(self): - ''' - Query for contacts. - ''' - try: - while (not ( self.__skype.AttachmentStatus in [Skype4Py.apiAttachSuccess, ])): - time.sleep(0.2) - - self.__contactsList = [] - self.__contactsDict = {} - - for user in self.__skype.Friends: - - if user.OnlineStatus not in [Skype4Py.olsUnknown, - Skype4Py.olsOffline, Skype4Py.olsInvisible]: - userFNEnc = user.FullName.encode("UTF-8").replace(" ", "_") - ... [truncated message content] |