Files reorganisation
This commit is contained in:
parent
c8b5f1db90
commit
e4452c4b03
9 changed files with 10 additions and 8 deletions
0
src/plugins/__init__.py
Normal file
0
src/plugins/__init__.py
Normal file
266
src/plugins/plugin_misc_cs.py
Executable file
266
src/plugins/plugin_misc_cs.py
Executable file
|
|
@ -0,0 +1,266 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0045
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, warning, error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import protocol, defer, threads, reactor
|
||||
from twisted.words.protocols.jabber import client, jid, xmlstream
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
from twisted.web.client import getPage
|
||||
import os.path
|
||||
import pdb
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from wokkel import disco, iwokkel, data_form
|
||||
from sat.tools.xml_tools import XMLUI
|
||||
import urllib
|
||||
import webbrowser
|
||||
|
||||
from BeautifulSoup import BeautifulSoup
|
||||
import re
|
||||
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "CouchSurfing plugin",
|
||||
"import_name": "CS",
|
||||
"type": "Misc",
|
||||
"protocols": [],
|
||||
"dependencies": [],
|
||||
"main": "CS_Plugin",
|
||||
"handler": "no",
|
||||
"description": _(u"""This plugin allow to manage your CouchSurfing account throught your SàT frontend""")
|
||||
}
|
||||
|
||||
AGENT = 'Salut à Toi XMPP/CS Plugin'
|
||||
|
||||
class CS_Plugin():
|
||||
|
||||
params = """
|
||||
<params>
|
||||
<individual>
|
||||
<category name="CouchSurfing">
|
||||
<param name="Login" type="string" />
|
||||
<param name="Password" type="password" />
|
||||
</category>
|
||||
</individual>
|
||||
</params>
|
||||
"""
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin CS initialization"))
|
||||
self.host = host
|
||||
#parameters
|
||||
host.memory.importParams(CS_Plugin.params)
|
||||
#menu
|
||||
host.importMenu(_("Plugin"), "CouchSurfing", self.menuSelected, help_string = _("Launch CoushSurfing management interface"))
|
||||
self.data=self.host.memory.getPrivate('plugin_cs_data') or {} #TODO: delete cookies/data after a while
|
||||
self.host.registerGeneralCB("plugin_CS_sendMessage", self.sendMessage)
|
||||
self.host.registerGeneralCB("plugin_CS_showUnreadMessages", self.showUnreadMessages)
|
||||
|
||||
def erroCB(self, e, id):
|
||||
"""Called when something is going wrong when contacting CS website"""
|
||||
#pdb.set_trace()
|
||||
message_data={"reason": "connection error", "message":_(u"Impossible to contact CS website, please check your login/password, connection or try again later")}
|
||||
self.host.bridge.actionResult("ERROR", id, message_data)
|
||||
|
||||
def menuSelected(self, id, profile):
|
||||
"""Called when the couchsurfing menu item is selected"""
|
||||
login = self.host.memory.getParamA("Login", "CouchSurfing", profile_key=profile)
|
||||
password = self.host.memory.getParamA("Password", "CouchSurfing", profile_key=profile)
|
||||
if not login or not password:
|
||||
message_data={"reason": "uncomplete", "message":_(u"You have to fill your CouchSurfing login & password in parameters before using this interface")}
|
||||
self.host.bridge.actionResult("ERROR", id, message_data)
|
||||
return
|
||||
|
||||
post_data = urllib.urlencode({'auth_login[un]':login,'auth_login[pw]':password,'auth_login[action]':'Login...'})
|
||||
|
||||
if not self.data.has_key(profile):
|
||||
self.data[profile] = {'cookies':{}}
|
||||
else:
|
||||
self.data[profile]['cookies'] = {}
|
||||
|
||||
d = getPage('http://www.couchsurfing.org/login.html', method='POST', postdata=post_data, headers={'Content-Type':'application/x-www-form-urlencoded'} , agent=AGENT, cookies=self.data[profile]['cookies'])
|
||||
d.addCallback(self.__connectionCB, id, profile)
|
||||
d.addErrback(self.erroCB, id)
|
||||
|
||||
#self.host.bridge.actionResult("SUPPRESS", id, {})
|
||||
|
||||
|
||||
#pages parsing callbacks
|
||||
def savePage(self, name, html):
|
||||
f = open ('/tmp/CS_'+name+'.html','w')
|
||||
f.write(html)
|
||||
f.close()
|
||||
print "page [%s] sauvee" % name
|
||||
#pdb.set_trace()
|
||||
|
||||
def __connectionCB(self, html, id, profile):
|
||||
print 'Response received'
|
||||
#self.savePage('principale',html)
|
||||
soup = BeautifulSoup(html)
|
||||
self.data[profile]['user_nick'] = soup.find('a','item_link',href='/home.html').contents[0]
|
||||
self.data[profile]['user_name'] = soup.html.head.title.string.split(' - ')[1]
|
||||
#unread messages
|
||||
try:
|
||||
self.data[profile]['unread_messages'] = int(soup.find(lambda tag: tag.name=='div' and ('class','item_bubble') in tag.attrs and tag.find('a', href="/messages.html?message_status=inbox")).find(text=True))
|
||||
except:
|
||||
self.data[profile]['unread_messages'] = 0
|
||||
#unread couchrequest messages
|
||||
try:
|
||||
self.data[profile]['unread_CR_messages'] = int(soup.find(lambda tag: tag.name=='div' and ('class','item_bubble') in tag.attrs and tag.find('a', href="/couchmanager")).find(text=True))
|
||||
except:
|
||||
self.data[profile]['unread_CR_messages'] = 0
|
||||
|
||||
#if we have already the list of friend, no need to make new requests
|
||||
if not self.data[profile].has_key('friends'):
|
||||
self.data[profile]['friends'] = {}
|
||||
d = getPage('http://www.couchsurfing.org/connections.html?type=myfriends&show=10000', agent=AGENT, cookies=self.data[profile]['cookies'])
|
||||
d.addCallback(self.__friendsPageCB, id=id, profile=profile)
|
||||
d.addErrback(self.erroCB, id)
|
||||
else:
|
||||
self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])})
|
||||
|
||||
def __buildUI(self, data):
|
||||
"""Build the XML UI of the plugin
|
||||
@param data: data store for the profile"""
|
||||
user_nick = data['user_nick']
|
||||
user_name = data['user_name']
|
||||
unread_mess = data['unread_messages']
|
||||
unread_CR_mess = data['unread_CR_messages']
|
||||
friends_list = data['friends'].keys()
|
||||
friends_list.sort()
|
||||
interface = XMLUI('window','tabs', title='CouchSurfing management')
|
||||
interface.addCategory(_("Messages"), "vertical")
|
||||
interface.addText(_("G'day %(name)s, you have %(nb_message)i unread message%(plural_mess)s and %(unread_CR_mess)s unread couch request message%(plural_CR)s\nIf you want to send a message, select the recipient(s) in the list below") % {'name':user_name, 'nb_message':unread_mess, 'plural_mess':'s' if unread_mess>1 else '', 'unread_CR_mess': unread_CR_mess, 'plural_CR':'s' if unread_CR_mess>1 else ''})
|
||||
if unread_mess:
|
||||
interface.addButton('plugin_CS_showUnreadMessages', 'showUnreadMessages', _('Show unread message%(plural)s in external web browser') % {'plural':'s' if unread_mess>1 else ''})
|
||||
interface.addList(friends_list, 'friends', style=['multi'])
|
||||
interface.changeLayout('pairs')
|
||||
interface.addLabel(_("Subject"))
|
||||
interface.addString('subject')
|
||||
interface.changeLayout('vertical')
|
||||
interface.addLabel(_("Message"))
|
||||
interface.addText("(use %name% for contact name and %firstname% for guessed first name)")
|
||||
interface.addTextBox('message')
|
||||
interface.addButton('plugin_CS_sendMessage', 'sendMessage', _('send'), fields_back=['friends','subject','message'])
|
||||
#interface.addCategory(_("Events"), "vertical") #TODO: coming soon, hopefuly :)
|
||||
#interface.addCategory(_("Couch search"), "vertical")
|
||||
return interface.toXml()
|
||||
|
||||
def __meetingPageCB(self, html):
|
||||
"""Called when the meeting page has been received"""
|
||||
|
||||
def __friendsPageCB(self, html, id, profile):
|
||||
"""Called when the friends list page has been received"""
|
||||
self.savePage('friends',html)
|
||||
soup = BeautifulSoup(html.replace('"formtable width="400','"formtable" width="400"')) #CS html fix #TODO: report the bug to CS dev team
|
||||
friends = self.data[profile]['friends']
|
||||
for _tr in soup.findAll('tr', {'class':re.compile("^msgRow*")}): #we parse the row with friends infos
|
||||
_nobr = _tr.find('nobr') #contain the friend name
|
||||
friend_name = unicode(_nobr.string)
|
||||
friend_link = u'http://www.couchsurfing.org'+_nobr.parent['href']
|
||||
regex_href = re.compile(r'/connections\.html\?id=([^&]+)')
|
||||
a_tag = _tr.find('a',href=regex_href)
|
||||
friend_id = regex_href.search(unicode(a_tag)).groups()[0]
|
||||
|
||||
debug(_("CS friend found: %(friend_name)s (id: %(friend_id)s, link: %(friend_link)s)") % {'friend_name':friend_name, 'friend_id':friend_id, 'friend_link':friend_link})
|
||||
friends[friend_name] = {'link':friend_link,'id':friend_id}
|
||||
a = soup.find('td','barmiddle next').a #is there several pages ?
|
||||
if a:
|
||||
#yes, we parse the next page
|
||||
d = getPage('http://www.couchsurfing.org/'+str(a['href']), agent=AGENT, cookies=self.data[profile]['cookies'])
|
||||
d.addCallback(self.__friendsPageCB, id=id, profile=profile)
|
||||
d.addErrback(self.erroCB, id)
|
||||
else:
|
||||
#no, we show the result
|
||||
self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])})
|
||||
#and save the data
|
||||
self.host.memory.setPrivate('plugin_cs_data', self.data)
|
||||
|
||||
def __sendMessage(self, answer, subject, message, data, recipient_list, id, profile):
|
||||
"""Send actually the message
|
||||
@param subject: subject of the message
|
||||
@param message: body of the message
|
||||
@param data: data of the profile
|
||||
@param recipient_list: list of friends names, names are removed once message is sent
|
||||
@param id: id of the action
|
||||
@param profile: profile who launched the action
|
||||
"""
|
||||
if answer:
|
||||
if not 'Here is a copy of the email that was sent' in answer:
|
||||
error(_("INTERNAL ERROR: no confirmation of message sent by CS, maybe the site has been modified ?"))
|
||||
#TODO: throw a warning to the frontend, saying that maybe the message has not been sent and to contact dev of this plugin
|
||||
#debug(_('HTML answer: %s') % answer)
|
||||
if recipient_list:
|
||||
recipient = recipient_list.pop()
|
||||
try:
|
||||
friend_id = data['friends'][recipient]['id']
|
||||
except KeyError:
|
||||
error('INTERNAL ERROR: unknown friend')
|
||||
return #send an error to the frontend
|
||||
mess = message.replace('%name%',recipient).replace('%firstname%',recipient.split(' ')[0])
|
||||
info(_('Sending message to %s') % recipient)
|
||||
debug(_("\nsubject: %(subject)s\nmessage: \n---\n%(message)s\n---\n\n") % {'subject':subject,'message':mess})
|
||||
post_data = urllib.urlencode({'email[subject]':subject.encode('utf-8'),'email[body]':mess.encode('utf-8'),'email[id]':friend_id,'email[action]':'Send Message','email[replied_id]':'','email[couchsurf]':'','email[directions_to_add]':''})
|
||||
d = getPage("http://www.couchsurfing.org/send_message.html", method='POST', postdata=post_data, headers={'Content-Type':'application/x-www-form-urlencoded'} , agent=AGENT, cookies=data['cookies'])
|
||||
d.addCallback(self.__sendMessage, subject, message, data, recipient_list, id, profile)
|
||||
d.addErrback(self.erroCB, id)
|
||||
else:
|
||||
interface = XMLUI('window', title=_('Message sent')) #TODO: create particular actionResult for alerts ?
|
||||
interface.addText(_('The message has been sent to every recipients'))
|
||||
self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":interface.toXml()})
|
||||
|
||||
def sendMessage(self, id, data, profile):
|
||||
"""Called to send a message to a friend
|
||||
@param data: dict with the following keys:
|
||||
friend: name of the recipient
|
||||
subject: subject of the message
|
||||
message: body of the message, with the following special keywords:
|
||||
- %name%: name of the friend
|
||||
- %firstname%: guessed first name of the friend (currently the first part of the name)
|
||||
"""
|
||||
if not data['friends']:
|
||||
message_data={"reason": "bad data", "message":_(u"There is not recipient selected for this message !")}
|
||||
self.host.bridge.actionResult("ERROR", id, message_data)
|
||||
return
|
||||
friends = data['friends'].split('\t')
|
||||
subject = data['subject']
|
||||
message = data['message']
|
||||
print "send message \o/ :) :) :)"
|
||||
info(_("sending message to %(friends)s with subject [%(subject)s]" % {'friends':friends, 'subject':subject}))
|
||||
self.__sendMessage(None, subject, message, self.data[profile], friends, id, profile)
|
||||
|
||||
def __showUnreadMessages2(self, html, id, profile):
|
||||
"""Called when the inbox page has been received"""
|
||||
#FIXME: that's really too fragile, only works if the unread messages are in the first page, and it would be too resources consuming for the website to DL each time all pages. In addition, the show attribute doesn't work as expected.
|
||||
soup = BeautifulSoup(html)
|
||||
for tag in soup.findAll(lambda tag: tag.name=='strong' and tag.a and tag.a['href'].startswith('messages.html?message_status=inbox')):
|
||||
link = "http://www.couchsurfing.org/"+str(tag.a['href'])
|
||||
webbrowser.open_new_tab(link) #TODO: the web browser need to already have CS cookies (i.e. already be opened & logged on CS, or be permanently loggued), a warning to the user should be sent/or a balloon-tip
|
||||
|
||||
def showUnreadMessages(self, id, data, profile):
|
||||
"""Called when user want to see all unread messages in the external browser"""
|
||||
d = getPage("http://www.couchsurfing.org/messages.html?message_status=inbox&show=10000", agent=AGENT, cookies=self.data[profile]['cookies'])
|
||||
d.addCallback(self.__showUnreadMessages2, id, profile)
|
||||
d.addErrback(self.erroCB, id)
|
||||
|
||||
713
src/plugins/plugin_misc_tarot.py
Executable file
713
src/plugins/plugin_misc_tarot.py
Executable file
|
|
@ -0,0 +1,713 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0045
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, warning, error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import protocol, defer, threads, reactor
|
||||
from twisted.words.protocols.jabber import client, jid, xmlstream
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
import os.path
|
||||
import pdb
|
||||
import random
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from wokkel import disco, iwokkel, data_form
|
||||
from sat.tools.xml_tools import dataForm2xml
|
||||
from sat.tools.games import TarotCard
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
|
||||
MESSAGE = '/message'
|
||||
NS_CG = 'http://www.goffi.org/protocol/card_game'
|
||||
CG_TAG = 'card_game'
|
||||
CG_REQUEST = MESSAGE + '/' + CG_TAG + '[@xmlns="' + NS_CG + '"]'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "Tarot cards plugin",
|
||||
"import_name": "Tarot",
|
||||
"type": "Misc",
|
||||
"protocols": [],
|
||||
"dependencies": ["XEP_0045"],
|
||||
"main": "Tarot",
|
||||
"handler": "yes",
|
||||
"description": _("""Implementation of Tarot card game""")
|
||||
}
|
||||
|
||||
|
||||
class Tarot():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin Tarot initialization"))
|
||||
self.host = host
|
||||
self.games={}
|
||||
self.contrats = [_('Passe'), _('Petite'), _('Garde'), _('Garde Sans'), _('Garde Contre')]
|
||||
host.bridge.addMethod("tarotGameCreate", ".communication", in_sign='sass', out_sign='', method=self.createGame) #args: room_jid, players, profile
|
||||
host.bridge.addMethod("tarotGameReady", ".communication", in_sign='sss', out_sign='', method=self.newPlayerReady) #args: player, referee, profile
|
||||
host.bridge.addMethod("tarotGameContratChoosed", ".communication", in_sign='ssss', out_sign='', method=self.contratChoosed) #args: player, referee, contrat, profile
|
||||
host.bridge.addMethod("tarotGamePlayCards", ".communication", in_sign='ssa(ss)s', out_sign='', method=self.play_cards) #args: player, referee, cards, profile
|
||||
host.bridge.addSignal("tarotGameStarted", ".communication", signature='ssass') #args: room_jid, referee, players, profile
|
||||
host.bridge.addSignal("tarotGameNew", ".communication", signature='sa(ss)s') #args: room_jid, hand, profile
|
||||
host.bridge.addSignal("tarotGameChooseContrat", ".communication", signature='sss') #args: room_jid, xml_data, profile
|
||||
host.bridge.addSignal("tarotGameShowCards", ".communication", signature='ssa(ss)a{ss}s') #args: room_jid, type ["chien", "poignée",...], cards, data[dict], profile
|
||||
host.bridge.addSignal("tarotGameCardsPlayed", ".communication", signature='ssa(ss)s') #args: room_jid, player, type ["chien", "poignée",...], cards, data[dict], profile
|
||||
host.bridge.addSignal("tarotGameYourTurn", ".communication", signature='ss') #args: room_jid, profile
|
||||
host.bridge.addSignal("tarotGameScore", ".communication", signature='ssasass') #args: room_jid, xml_data, winners (list of nicks), loosers (list of nicks), profile
|
||||
host.bridge.addSignal("tarotGameInvalidCards", ".communication", signature='ssa(ss)a(ss)s') #args: room_jid, game phase, played_cards, invalid_cards, profile
|
||||
self.deck_ordered = []
|
||||
for value in ['excuse']+map(str,range(1,22)):
|
||||
self.deck_ordered.append(TarotCard(("atout",value)))
|
||||
for suit in ["pique", "coeur", "carreau", "trefle"]:
|
||||
for value in map(str,range(1,11))+["valet","cavalier","dame","roi"]:
|
||||
self.deck_ordered.append(TarotCard((suit, value)))
|
||||
|
||||
def createGameElt(self, to_jid, type="normal"):
|
||||
type = "normal" if to_jid.resource else "groupchat"
|
||||
elt = domish.Element(('jabber:client','message'))
|
||||
elt["to"] = to_jid.full()
|
||||
elt["type"] = type
|
||||
elt.addElement((NS_CG, CG_TAG))
|
||||
return elt
|
||||
|
||||
def __card_list_to_xml(self, cards_list, elt_name):
|
||||
"""Convert a card list to domish element"""
|
||||
cards_list_elt = domish.Element(('',elt_name))
|
||||
for card in cards_list:
|
||||
card_elt = domish.Element(('','card'))
|
||||
card_elt['suit'] = card.suit
|
||||
card_elt['value'] = card.value
|
||||
cards_list_elt.addChild(card_elt)
|
||||
return cards_list_elt
|
||||
|
||||
def __xml_to_list(self, cards_list_elt):
|
||||
"""Convert a domish element with cards to a list of tuples"""
|
||||
cards_list = []
|
||||
for card in cards_list_elt.elements():
|
||||
cards_list.append((card['suit'], card['value']))
|
||||
return cards_list
|
||||
|
||||
def __create_started_elt(self, players):
|
||||
"""Create a game_started domish element"""
|
||||
started_elt = domish.Element(('','started'))
|
||||
idx = 0
|
||||
for player in players:
|
||||
player_elt = domish.Element(('','player'))
|
||||
player_elt.addContent(player)
|
||||
player_elt['index'] = str(idx)
|
||||
idx+=1
|
||||
started_elt.addChild(player_elt)
|
||||
return started_elt
|
||||
|
||||
def __ask_contrat(self):
|
||||
"""Create a element for asking contrat"""
|
||||
contrat_elt = domish.Element(('','contrat'))
|
||||
form = data_form.Form('form', title=_('contrat selection'))
|
||||
field = data_form.Field('list-single', 'contrat', options=map(data_form.Option, self.contrats), required=True)
|
||||
form.addField(field)
|
||||
contrat_elt.addChild(form.toElement())
|
||||
return contrat_elt
|
||||
|
||||
def __give_scores(self, scores, winners, loosers):
|
||||
"""Create an element to give scores
|
||||
@param scores: unicode (can contain line feed)
|
||||
@param winners: list of unicode nicks of winners
|
||||
@param loosers: list of unicode nicks of loosers"""
|
||||
|
||||
score_elt = domish.Element(('','score'))
|
||||
form = data_form.Form('form', title=_('scores'))
|
||||
for line in scores.split('\n'):
|
||||
field = data_form.Field('fixed', value = line)
|
||||
form.addField(field)
|
||||
score_elt.addChild(form.toElement())
|
||||
for winner in winners:
|
||||
winner_elt = domish.Element(('','winner'))
|
||||
winner_elt.addContent(winner)
|
||||
score_elt.addChild(winner_elt)
|
||||
for looser in loosers:
|
||||
looser_elt = domish.Element(('','looser'))
|
||||
looser_elt.addContent(looser)
|
||||
score_elt.addChild(looser_elt)
|
||||
return score_elt
|
||||
|
||||
def __invalid_cards_elt(self, played_cards, invalid_cards, game_phase):
|
||||
"""Create a element for invalid_cards error
|
||||
@param list_cards: list of Card
|
||||
@param game_phase: phase of the game ['ecart', 'play']"""
|
||||
error_elt = domish.Element(('','error'))
|
||||
played_elt = self.__card_list_to_xml(played_cards, 'played')
|
||||
invalid_elt = self.__card_list_to_xml(invalid_cards, 'invalid')
|
||||
error_elt['type'] = 'invalid_cards'
|
||||
error_elt['phase'] = game_phase
|
||||
error_elt.addChild(played_elt)
|
||||
error_elt.addChild(invalid_elt)
|
||||
return error_elt
|
||||
|
||||
def __next_player(self, game_data, next_pl = None):
|
||||
"""Increment player number & return player name
|
||||
@param next_pl: if given, then next_player is forced to this one
|
||||
"""
|
||||
if next_pl:
|
||||
game_data['current_player'] = game_data['players'].index(next_pl)
|
||||
return next_pl
|
||||
else:
|
||||
pl_idx = game_data['current_player'] = (game_data['current_player'] + 1) % len(game_data['players'])
|
||||
return game_data['players'][pl_idx]
|
||||
|
||||
def __winner(self, game_data):
|
||||
"""give the nick of the player who win this trick"""
|
||||
players_data = game_data['players_data']
|
||||
first = game_data['first_player']
|
||||
first_idx = game_data['players'].index(first)
|
||||
suit_asked = None
|
||||
strongest = None
|
||||
winner = None
|
||||
for idx in [(first_idx + i) % 4 for i in range(4)]:
|
||||
player = game_data['players'][idx]
|
||||
card = players_data[player]['played']
|
||||
if card.value == "excuse":
|
||||
continue
|
||||
if suit_asked == None:
|
||||
suit_asked = card.suit
|
||||
if (card.suit == suit_asked or card.suit == "atout") and card > strongest:
|
||||
strongest = card
|
||||
winner = player
|
||||
assert winner
|
||||
return winner
|
||||
|
||||
def __excuse_hack(self, game_data, played, winner):
|
||||
"""give a low card to other team and keep excuse if trick is lost
|
||||
@param game_data: data of the game
|
||||
@param played: cards currently on the table
|
||||
@param winner: nick of the trick winner"""
|
||||
#TODO: manage the case where excuse is played on the last trick (and lost)
|
||||
#TODO: gof: manage excuse (fool)
|
||||
players_data = game_data['players_data']
|
||||
excuse = TarotCard(("atout","excuse"))
|
||||
|
||||
#we first check if the Excuse was already player
|
||||
#and if somebody is waiting for a card
|
||||
for player in game_data['players']:
|
||||
if players_data[player]['wait_for_low']:
|
||||
#the excuse owner has to give a card to somebody
|
||||
if winner == player:
|
||||
#the excuse owner win the trick, we check if we have something to give
|
||||
for card in played:
|
||||
if card.points == 0.5:
|
||||
pl_waiting = players_data[player]['wait_for_low']
|
||||
played.remove(card)
|
||||
players_data[pl_waiting]['levees'].append(card)
|
||||
debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":player, "card_waited": card, "player_waiting":pl_waiting})
|
||||
return
|
||||
return
|
||||
|
||||
if not excuse in played:
|
||||
#the Excuse is not on the table, nothing to do
|
||||
return
|
||||
|
||||
excuse_player = None #Who has played the Excuse ?
|
||||
for player in game_data['players']:
|
||||
if players_data[player]['played'] == excuse:
|
||||
excuse_player = player
|
||||
break
|
||||
|
||||
if excuse_player == winner:
|
||||
return #the excuse player win the trick, nothing to do
|
||||
|
||||
#first we remove the excuse from played cards
|
||||
played.remove(excuse)
|
||||
#then we give it back to the original owner
|
||||
owner_levees = players_data[excuse_player]['levees']
|
||||
owner_levees.append(excuse)
|
||||
#finally we give a low card to the trick winner
|
||||
low_card = None
|
||||
#We look backward in cards won by the Excuse owner to
|
||||
#find a low value card
|
||||
for card_idx in range(len(owner_levees)-1, -1, -1):
|
||||
if owner_levees[card_idx].points == 0.5:
|
||||
low_card = owner_levees[card_idx]
|
||||
del owner_levees[card_idx]
|
||||
players_data[winner]['levees'].append(low_card)
|
||||
debug (_('Player %(excuse_owner)s give %(card_waited)s to %(player_waiting)s for Excuse compensation') % {"excuse_owner":excuse_player, "card_waited": low_card, "player_waiting":winner})
|
||||
break
|
||||
if not low_card: #The player has no low card yet
|
||||
#TODO: manage case when player never win a trick with low card
|
||||
players_data[excuse_player]['wait_for_low'] = winner
|
||||
debug(_("%(excuse_owner)s keep the Excuse but has not card to give, %(winner)s is waiting for one") % {'excuse_owner':excuse_player, 'winner':winner})
|
||||
|
||||
|
||||
def __calculate_scores(self, game_data):
|
||||
"""The game is finished, time to know who won :)
|
||||
@param game_data: data of the game
|
||||
@return: tuple with (string victory message, list of winners, list of loosers)"""
|
||||
players_data = game_data['players_data']
|
||||
levees = players_data[game_data['attaquant']]['levees']
|
||||
score = 0
|
||||
nb_bouts = 0
|
||||
bouts = []
|
||||
for card in levees:
|
||||
if card.bout:
|
||||
nb_bouts +=1
|
||||
bouts.append(card.value)
|
||||
score += card.points
|
||||
|
||||
#We we do a basic check on score calculation
|
||||
check_score = 0
|
||||
defenseurs = game_data['players'][:]
|
||||
defenseurs.remove(game_data['attaquant'])
|
||||
for defenseur in defenseurs:
|
||||
for card in players_data[defenseur]['levees']:
|
||||
check_score+=card.points
|
||||
if game_data['contrat'] == "Garde Contre":
|
||||
for card in game_data['chien']:
|
||||
check_score+=card.points
|
||||
assert (score + check_score == 91)
|
||||
|
||||
point_limit = None
|
||||
if nb_bouts == 3:
|
||||
point_limit = 36
|
||||
elif nb_bouts == 2:
|
||||
point_limit = 41
|
||||
elif nb_bouts == 1:
|
||||
point_limit = 51
|
||||
else:
|
||||
point_limit = 56
|
||||
if game_data['contrat'] == 'Petite':
|
||||
contrat_mult = 1
|
||||
elif game_data['contrat'] == 'Garde':
|
||||
contrat_mult = 2
|
||||
elif game_data['contrat'] == 'Garde Sans':
|
||||
contrat_mult = 4
|
||||
elif game_data['contrat'] == 'Garde Contre':
|
||||
contrat_mult = 6
|
||||
else:
|
||||
error(_('INTERNAL ERROR: contrat not managed (mispelled ?)'))
|
||||
assert(False)
|
||||
|
||||
victory = (score >= point_limit)
|
||||
margin = abs(score - point_limit)
|
||||
points_defenseur = (margin + 25) * contrat_mult * (-1 if victory else 1)
|
||||
winners = []
|
||||
loosers = []
|
||||
player_score = {}
|
||||
for player in game_data['players']:
|
||||
#TODO: adjust this for 3 and 5 players variants
|
||||
#TODO: manage bonuses (petit au bout, poignée, chelem)
|
||||
player_score[player] = points_defenseur if player != game_data['attaquant'] else points_defenseur * -3
|
||||
players_data[player]['score'] += player_score[player] #we add score of this game to the global score
|
||||
if player_score[player] > 0:
|
||||
winners.append(player)
|
||||
else:
|
||||
loosers.append(player)
|
||||
|
||||
scores_str = _('The attacker (%(attaquant)s) makes %(points)i and needs to make %(point_limit)i (%(nb_bouts)s oulder%(plural)s%(separator)s%(bouts)s): he %(victory)s') % {'attaquant':game_data['attaquant'], 'points':score, 'point_limit':point_limit, 'nb_bouts': nb_bouts, 'plural': 's' if nb_bouts>1 else '', 'separator':': ' if nb_bouts != 0 else '', 'bouts':','.join(map(str,bouts)), 'victory': 'win' if victory else 'loose'}
|
||||
scores_str+='\n'
|
||||
for player in game_data['players']:
|
||||
scores_str+=_("\n--\n%(player)s:\nscore for this game ==> %(score_game)i\ntotal score ==> %(total_score)i") % {'player':player, 'score_game':player_score[player], 'total_score': players_data[player]['score']}
|
||||
debug(scores_str)
|
||||
|
||||
return (scores_str, winners, loosers)
|
||||
|
||||
def __invalid_cards(self, game_data, cards):
|
||||
"""Checks that the player has the right to play what he wants to
|
||||
@param game_data: Game data
|
||||
@param cards: cards the player want to play
|
||||
@return forbidden_cards cards or empty list if cards are ok"""
|
||||
forbidden_cards = []
|
||||
if game_data['stage'] == 'ecart':
|
||||
for card in cards:
|
||||
if card.bout or card.value=="roi":
|
||||
forbidden_cards.append(card)
|
||||
#TODO: manage case where atouts (trumps) are in the dog
|
||||
elif game_data['stage'] == 'play':
|
||||
biggest_atout = None
|
||||
suit_asked = None
|
||||
players = game_data['players']
|
||||
players_data = game_data['players_data']
|
||||
idx = players.index(game_data['first_player'])
|
||||
current_idx = game_data['current_player']
|
||||
current_player = players[current_idx]
|
||||
if idx == current_idx:
|
||||
#the player is the first to play, he can play what he wants
|
||||
return forbidden_cards
|
||||
while (idx != current_idx):
|
||||
player = players[idx]
|
||||
played_card = players_data[player]['played']
|
||||
if not suit_asked and played_card.value != "excuse":
|
||||
suit_asked = played_card.suit
|
||||
if played_card.suit == "atout" and played_card > biggest_atout:
|
||||
biggest_atout = played_card
|
||||
idx = (idx + 1) % len(players)
|
||||
has_suit = False #True if there is one card of the asked suit in the hand of the player
|
||||
has_atout = False
|
||||
biggest_hand_atout = None
|
||||
|
||||
for hand_card in game_data['hand'][current_player]:
|
||||
if hand_card.suit == suit_asked:
|
||||
has_suit = True
|
||||
if hand_card.suit == "atout":
|
||||
has_atout = True
|
||||
if hand_card.suit == "atout" and hand_card > biggest_hand_atout:
|
||||
biggest_hand_atout = hand_card
|
||||
|
||||
assert len(cards) == 1
|
||||
card = cards[0]
|
||||
if card.suit != suit_asked and has_suit and card.value != "excuse":
|
||||
forbidden_cards.append(card)
|
||||
return forbidden_cards
|
||||
if card.suit != suit_asked and card.suit != "atout" and has_atout:
|
||||
forbidden_cards.append(card)
|
||||
return forbidden_cards
|
||||
if card.suit == "atout" and card < biggest_atout and biggest_hand_atout > biggest_atout and card.value != "excuse":
|
||||
forbidden_cards.append(card)
|
||||
else:
|
||||
error(_('Internal error: unmanaged game stage'))
|
||||
return forbidden_cards
|
||||
|
||||
|
||||
def __start_play(self, room_jid, game_data, profile):
|
||||
"""Start the game (tell to the first player after dealer to play"""
|
||||
game_data['stage'] = "play"
|
||||
next_player_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(game_data['players']) #the player after the dealer start
|
||||
game_data['first_player'] = next_player = game_data['players'][next_player_idx]
|
||||
to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
yourturn_elt = mess.firstChildElement().addElement('your_turn')
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
|
||||
def createGame(self, room_jid_param, players, profile_key='@DEFAULT@'):
|
||||
"""Create a new game"""
|
||||
debug (_("Creating Tarot game"))
|
||||
room_jid = jid.JID(room_jid_param)
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_("profile %s is unknown") % profile_key)
|
||||
return
|
||||
if self.games.has_key(room_jid):
|
||||
warning (_("Tarot game already started in room %s") % room_jid.userhost())
|
||||
else:
|
||||
room_nick = self.host.plugins["XEP_0045"].getRoomNick(room_jid.userhost(), profile)
|
||||
if not room_nick:
|
||||
error ('Internal error')
|
||||
return
|
||||
referee = room_jid.userhost() + '/' + room_nick
|
||||
status = {}
|
||||
players_data = {}
|
||||
for player in players:
|
||||
players_data[player] = {'score':0}
|
||||
status[player] = "init"
|
||||
self.games[room_jid.userhost()] = {'referee':referee, 'players':players, 'status':status, 'players_data':players_data, 'hand_size':18, 'init_player':0, 'current_player': None, 'contrat': None, 'stage': None}
|
||||
for player in players:
|
||||
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+player))
|
||||
mess.firstChildElement().addChild(self.__create_started_elt(players))
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
def newPlayerReady(self, player, referee, profile_key='@DEFAULT@'):
|
||||
"""Must be called when player is ready to start a new game"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_("profile %s is unknown") % profile_key)
|
||||
return
|
||||
debug ('new player ready: %s' % profile)
|
||||
mess = self.createGameElt(jid.JID(referee))
|
||||
ready_elt = mess.firstChildElement().addElement('player_ready')
|
||||
ready_elt['player'] = player
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
def contratChoosed(self, player, referee, contrat, profile_key='@DEFAULT@'):
|
||||
"""Must be call by player when the contrat is selected
|
||||
@param player: player's name
|
||||
@param referee: arbiter jid
|
||||
@contrat: contrat choosed (must be the exact same string than in the give list options)
|
||||
@profile_key: profile
|
||||
"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_("profile %s is unknown") % profile_key)
|
||||
return
|
||||
debug (_('contrat [%(contrat)s] choosed by %(profile)s') % {'contrat':contrat, 'profile':profile})
|
||||
mess = self.createGameElt(jid.JID(referee))
|
||||
contrat_elt = mess.firstChildElement().addElement(('','contrat_choosed'), content=contrat)
|
||||
contrat_elt['player'] = player
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
def play_cards(self, player, referee, cards, profile_key='@DEFAULT@'):
|
||||
"""Must be call by player when the contrat is selected
|
||||
@param player: player's name
|
||||
@param referee: arbiter jid
|
||||
@cards: cards played (list of tuples)
|
||||
@profile_key: profile
|
||||
"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not profile:
|
||||
error (_("profile %s is unknown") % profile_key)
|
||||
return
|
||||
debug (_('Cards played by %(profile)s: [%(cards)s]') % {'profile':profile,'cards':cards})
|
||||
mess = self.createGameElt(jid.JID(referee))
|
||||
playcard_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(TarotCard.from_tuples(cards), 'cards_played'))
|
||||
playcard_elt['player'] = player
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
def newGame(self, room_jid, profile):
|
||||
"""Launch a new round"""
|
||||
debug (_('new Tarot game'))
|
||||
deck = self.deck_ordered[:]
|
||||
random.shuffle(deck)
|
||||
game_data = self.games[room_jid.userhost()]
|
||||
players = game_data['players']
|
||||
players_data = game_data['players_data']
|
||||
current_player = game_data['current_player']
|
||||
game_data['stage'] = "init"
|
||||
game_data['first_player'] = None #first player for the current trick
|
||||
game_data['contrat'] = None
|
||||
hand = game_data['hand'] = {}
|
||||
hand_size = game_data['hand_size']
|
||||
chien = game_data['chien'] = []
|
||||
for i in range(4): #TODO: distribute according to real Tarot rules (3 by 3 counter-clockwise, 1 card at once to chien)
|
||||
hand[players[i]] = deck[0:hand_size]
|
||||
del deck[0:hand_size]
|
||||
chien.extend(deck)
|
||||
del(deck[:])
|
||||
|
||||
for player in players:
|
||||
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
mess.firstChildElement().addChild(self.__card_list_to_xml(hand[player], 'hand'))
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
players_data[player]['contrat'] = None
|
||||
players_data[player]['levees'] = [] #cards won
|
||||
players_data[player]['played'] = None #card on the table
|
||||
players_data[player]['wait_for_low'] = None #Used when a player wait for a low card because of excuse
|
||||
|
||||
pl_idx = game_data['current_player'] = (game_data['init_player'] + 1) % len(players) #the player after the dealer start
|
||||
player = players[pl_idx]
|
||||
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
mess.firstChildElement().addChild(self.__ask_contrat())
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
|
||||
def card_game_cmd(self, mess_elt, profile):
|
||||
from_jid = jid.JID(mess_elt['from'])
|
||||
room_jid = jid.JID(from_jid.userhost())
|
||||
game_elt = mess_elt.firstChildElement()
|
||||
game_data = self.games[room_jid.userhost()]
|
||||
players_data = game_data['players_data']
|
||||
|
||||
for elt in game_elt.elements():
|
||||
|
||||
if elt.name == 'started': #new game created
|
||||
players = []
|
||||
for player in elt.elements():
|
||||
players.append(unicode(player))
|
||||
self.host.bridge.tarotGameStarted(room_jid.userhost(), from_jid.full(), players, profile)
|
||||
|
||||
elif elt.name == 'player_ready': #ready to play
|
||||
player = elt['player']
|
||||
status = self.games[room_jid.userhost()]['status']
|
||||
nb_players = len(self.games[room_jid.userhost()]['players'])
|
||||
status[player] = 'ready'
|
||||
debug (_('Player %(player)s is ready to start [status: %(status)s]') % {'player':player, 'status':status})
|
||||
if status.values().count('ready') == nb_players: #everybody is ready, we can start the game
|
||||
self.newGame(room_jid, profile)
|
||||
|
||||
elif elt.name == 'hand': #a new hand has been received
|
||||
self.host.bridge.tarotGameNew(room_jid.userhost(), self.__xml_to_list(elt), profile)
|
||||
|
||||
elif elt.name == 'contrat': #it's time to choose contrat
|
||||
form = data_form.Form.fromElement(elt.firstChildElement())
|
||||
xml_data = dataForm2xml(form)
|
||||
self.host.bridge.tarotGameChooseContrat(room_jid.userhost(), xml_data, profile)
|
||||
|
||||
elif elt.name == 'contrat_choosed':
|
||||
#TODO: check we receive the contrat from the right person
|
||||
#TODO: use proper XEP-0004 way for answering form
|
||||
player = elt['player']
|
||||
players_data[player]['contrat'] = unicode(elt)
|
||||
contrats = [players_data[player]['contrat'] for player in game_data['players']]
|
||||
if contrats.count(None):
|
||||
#not everybody has choosed his contrat, it's next one turn
|
||||
player = self.__next_player(game_data)
|
||||
to_jid = jid.JID(room_jid.userhost()+"/"+player) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
mess.firstChildElement().addChild(self.__ask_contrat())
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
else:
|
||||
#TODO: gof: manage "everybody pass" case
|
||||
best_contrat = [None, "Passe"]
|
||||
for player in game_data['players']:
|
||||
contrat = players_data[player]['contrat']
|
||||
idx_best = self.contrats.index(best_contrat[1])
|
||||
idx_pl = self.contrats.index(contrat)
|
||||
if idx_pl > idx_best:
|
||||
best_contrat[0] = player
|
||||
best_contrat[1] = contrat
|
||||
debug (_("%(player)s win the bid with %(contrat)s") % {'player':best_contrat[0],'contrat':best_contrat[1]})
|
||||
game_data['contrat'] = best_contrat[1]
|
||||
|
||||
if game_data['contrat'] == "Garde Sans" or game_data['contrat'] == "Garde Contre":
|
||||
self.__start_play(room_jid, game_data, profile)
|
||||
game_data['attaquant'] = best_contrat[0]
|
||||
else:
|
||||
#Time to show the chien to everybody
|
||||
to_jid = jid.JID(room_jid.userhost()) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
chien_elt = mess.firstChildElement().addChild(self.__card_list_to_xml(game_data['chien'], 'chien'))
|
||||
chien_elt['attaquant'] = best_contrat[0]
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
#the attacker (attaquant) get the chien
|
||||
game_data['hand'][best_contrat[0]].extend(game_data['chien'])
|
||||
del game_data['chien'][:]
|
||||
|
||||
if game_data['contrat'] == "Garde Sans":
|
||||
#The chien go into attaquant's (attacker) levees
|
||||
players_data[best_contrat[0]]['levees'].extend(game_data['chien'])
|
||||
del game_data['chien'][:]
|
||||
|
||||
|
||||
elif elt.name == 'chien': #we have received the chien
|
||||
debug (_("tarot: chien received"))
|
||||
data = {"attaquant":elt['attaquant']}
|
||||
game_data['stage'] = "ecart"
|
||||
game_data['attaquant'] = elt['attaquant']
|
||||
self.host.bridge.tarotGameShowCards(room_jid.userhost(), "chien", self.__xml_to_list(elt), data, profile)
|
||||
|
||||
elif elt.name == 'cards_played':
|
||||
if game_data['stage'] == "ecart":
|
||||
#TODO: show atouts (trumps) if player put some in écart
|
||||
assert (game_data['attaquant'] == elt['player']) #TODO: throw an xml error here
|
||||
list_cards = TarotCard.from_tuples(self.__xml_to_list(elt))
|
||||
#we now check validity of card
|
||||
invalid_cards = self.__invalid_cards(game_data, list_cards)
|
||||
if invalid_cards:
|
||||
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+elt['player']))
|
||||
mess.firstChildElement().addChild(self.__invalid_cards_elt(list_cards, invalid_cards, game_data['stage']))
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
return
|
||||
|
||||
#FIXME: gof: manage Garde Sans & Garde Contre cases
|
||||
players_data[elt['player']]['levees'].extend(list_cards) #we add the chien to attaquant's levées
|
||||
for card in list_cards:
|
||||
game_data['hand'][elt['player']].remove(card)
|
||||
|
||||
self.__start_play(room_jid, game_data, profile)
|
||||
|
||||
elif game_data['stage'] == "play":
|
||||
current_player = game_data['players'][game_data['current_player']]
|
||||
cards = TarotCard.from_tuples(self.__xml_to_list(elt))
|
||||
|
||||
if mess_elt['type'] == 'groupchat':
|
||||
self.host.bridge.tarotGameCardsPlayed(room_jid.userhost(), elt['player'], self.__xml_to_list(elt), profile)
|
||||
else:
|
||||
#we first check validity of card
|
||||
invalid_cards = self.__invalid_cards(game_data, cards)
|
||||
if invalid_cards:
|
||||
mess = self.createGameElt(jid.JID(room_jid.userhost()+'/'+current_player))
|
||||
mess.firstChildElement().addChild(self.__invalid_cards_elt(cards, invalid_cards, game_data['stage']))
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
return
|
||||
#the card played is ok, we forward it to everybody
|
||||
#first we remove it from the hand and put in on the table
|
||||
game_data['hand'][current_player].remove(cards[0])
|
||||
players_data[current_player]['played'] = cards[0]
|
||||
|
||||
#then we forward the message
|
||||
mess = self.createGameElt(room_jid)
|
||||
playcard_elt = mess.firstChildElement().addChild(elt)
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
#Did everybody played ?
|
||||
played = [players_data[player]['played'] for player in game_data['players']]
|
||||
if all(played):
|
||||
#everybody has played
|
||||
winner = self.__winner(game_data)
|
||||
debug (_('The winner of this trick is %s') % winner)
|
||||
#the winner win the trick
|
||||
self.__excuse_hack(game_data, played, winner)
|
||||
players_data[elt['player']]['levees'].extend(played)
|
||||
#nothing left on the table
|
||||
for player in game_data['players']:
|
||||
players_data[player]['played'] = None
|
||||
if len(game_data['hand'][current_player]) == 0:
|
||||
#no card lef: the game is finished
|
||||
to_jid = jid.JID(room_jid.userhost()) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
chien_elt = mess.firstChildElement().addChild(self.__give_scores(*self.__calculate_scores(game_data)))
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
return
|
||||
#next player is the winner
|
||||
next_player = game_data['first_player'] = self.__next_player(game_data, winner)
|
||||
else:
|
||||
next_player = self.__next_player(game_data)
|
||||
|
||||
#finally, we tell to the next player to play
|
||||
to_jid = jid.JID(room_jid.userhost()+"/"+next_player) #FIXME: gof:
|
||||
mess = self.createGameElt(to_jid)
|
||||
yourturn_elt = mess.firstChildElement().addElement('your_turn')
|
||||
self.host.profiles[profile].xmlstream.send(mess)
|
||||
|
||||
elif elt.name == 'your_turn':
|
||||
self.host.bridge.tarotGameYourTurn(room_jid.userhost(), profile)
|
||||
|
||||
elif elt.name == 'score':
|
||||
form_elt = elt.elements(name='x',uri='jabber:x:data').next()
|
||||
winners = []
|
||||
loosers = []
|
||||
for winner in elt.elements(name='winner', uri=''):
|
||||
winners.append(unicode(winner))
|
||||
for looser in elt.elements(name='looser', uri=''):
|
||||
loosers.append(unicode(looser))
|
||||
form = data_form.Form.fromElement(form_elt)
|
||||
xml_data = dataForm2xml(form)
|
||||
self.host.bridge.tarotGameScore(room_jid.userhost(), xml_data, winners, loosers, profile)
|
||||
elif elt.name == 'error':
|
||||
if elt['type'] == 'invalid_cards':
|
||||
played_cards = self.__xml_to_list(elt.elements(name='played',uri='').next())
|
||||
invalid_cards = self.__xml_to_list(elt.elements(name='invalid',uri='').next())
|
||||
self.host.bridge.tarotGameInvalidCards(room_jid.userhost(), elt['phase'], played_cards, invalid_cards, profile)
|
||||
else:
|
||||
error (_('Unmanaged error type: %s') % elt['type'])
|
||||
else:
|
||||
error (_('Unmanaged card game element: %s') % elt.name)
|
||||
|
||||
def getHandler(self, profile):
|
||||
return CardGameHandler(self)
|
||||
|
||||
class CardGameHandler (XMPPHandler):
|
||||
implements(iwokkel.IDisco)
|
||||
|
||||
def __init__(self, plugin_parent):
|
||||
self.plugin_parent = plugin_parent
|
||||
self.host = plugin_parent.host
|
||||
|
||||
def connectionInitialized(self):
|
||||
self.xmlstream.addObserver(CG_REQUEST, self.plugin_parent.card_game_cmd, profile = self.parent.profile)
|
||||
|
||||
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
return [disco.DiscoFeature(NS_CG)]
|
||||
|
||||
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
||||
return []
|
||||
|
||||
197
src/plugins/plugin_xep_0045.py
Executable file
197
src/plugins/plugin_xep_0045.py
Executable file
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0045
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, warning, error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import protocol, defer, threads, reactor
|
||||
from twisted.words.protocols.jabber import client, jid, xmlstream
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
import os.path
|
||||
import pdb
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from wokkel import disco, iwokkel, muc
|
||||
|
||||
from base64 import b64decode
|
||||
from hashlib import sha1
|
||||
from time import sleep
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
|
||||
AVATAR_PATH = "/avatars"
|
||||
|
||||
IQ_GET = '/iq[@type="get"]'
|
||||
NS_VCARD = 'vcard-temp'
|
||||
VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests
|
||||
|
||||
PRESENCE = '/presence'
|
||||
NS_VCARD_UPDATE = 'vcard-temp:x:update'
|
||||
VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "XEP 0045 Plugin",
|
||||
"import_name": "XEP_0045",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0045"],
|
||||
"dependencies": [],
|
||||
"main": "XEP_0045",
|
||||
"handler": "yes",
|
||||
"description": _("""Implementation of Multi-User Chat""")
|
||||
}
|
||||
|
||||
class XEP_0045():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin XEP_0045 initialization"))
|
||||
self.host = host
|
||||
self.clients={}
|
||||
host.bridge.addMethod("joinMUC", ".communication", in_sign='ssss', out_sign='', method=self.join)
|
||||
host.bridge.addMethod("getRoomJoined", ".communication", in_sign='s', out_sign='a(ssass)', method=self.getRoomJoined)
|
||||
host.bridge.addMethod("getRoomSubjects", ".communication", in_sign='s', out_sign='a(sss)', method=self.getRoomSubjects)
|
||||
host.bridge.addSignal("roomJoined", ".communication", signature='ssasss') #args: room_id, room_service, room_nicks, user_nick, profile
|
||||
host.bridge.addSignal("roomUserJoined", ".communication", signature='sssa{ss}s') #args: room_id, room_service, user_nick, user_data, profile
|
||||
host.bridge.addSignal("roomUserLeft", ".communication", signature='sssa{ss}s') #args: room_id, room_service, user_nick, user_data, profile
|
||||
host.bridge.addSignal("roomNewSubject", ".communication", signature='ssss') #args: room_id, room_service, subject, profile
|
||||
|
||||
def __check_profile(self, profile):
|
||||
"""check if profile is used and connected
|
||||
if profile known but disconnected, remove it from known profiles
|
||||
@param profile: profile to check
|
||||
@return: True if the profile is known and connected, else False"""
|
||||
if not profile or not self.clients.has_key(profile) or not self.host.isConnected(profile):
|
||||
error (_('Unknown or disconnected profile (%s)') % profile)
|
||||
if self.clients.has_key(profile):
|
||||
del self.clients[profile]
|
||||
return False
|
||||
return True
|
||||
|
||||
def __room_joined(self, room, profile):
|
||||
"""Called when the user is in the requested room"""
|
||||
room_jid = room.roomIdentifier+'@'+room.service
|
||||
self.clients[profile].joined_rooms[room_jid] = room
|
||||
self.host.bridge.roomJoined(room.roomIdentifier, room.service, [user.nick for user in room.roster.values()], room.nick, profile)
|
||||
|
||||
def __err_joining_room(self, failure, profile):
|
||||
"""Called when something is going wrong when joining the room"""
|
||||
mess = _("Error when joining the room")
|
||||
error (mess)
|
||||
self.host.bridge.newAlert(mess, _("Group chat error"), "ERROR", profile)
|
||||
|
||||
def getRoomJoined(self, profile_key='@DEFAULT@'):
|
||||
"""Return room where user is"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
result = []
|
||||
if not self.__check_profile(profile):
|
||||
return result
|
||||
for room in self.clients[profile].joined_rooms.values():
|
||||
result.append((room.roomIdentifier, room.service, [user.nick for user in room.roster.values()], room.nick))
|
||||
return result
|
||||
|
||||
def getRoomNick(self, room_jid, profile_key='@DEFAULT@'):
|
||||
"""return nick used in room by user
|
||||
@param room_jid: unicode room id
|
||||
@profile_key: profile
|
||||
@return: nick or empty string in case of error"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.__check_profile(profile) or not self.clients[profile].joined_rooms.has_key(room_jid):
|
||||
return ''
|
||||
return self.clients[profile].joined_rooms[room_jid].nick
|
||||
|
||||
|
||||
def getRoomSubjects(self, profile_key='@DEFAULT@'):
|
||||
"""Return received subjects of rooms"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.__check_profile(profile):
|
||||
return []
|
||||
return self.clients[profile].rec_subjects.values()
|
||||
|
||||
def join(self, service, roomId, nick, profile_key='@DEFAULT@'):
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
if not self.__check_profile(profile):
|
||||
return
|
||||
room_jid = roomId+'@'+service
|
||||
if self.clients[profile].joined_rooms.has_key(room_jid):
|
||||
warning(_('%(profile)s is already in room %(room_jid)s') % {'profile':profile, 'room_jid':room_jid})
|
||||
return
|
||||
info (_("[%(profile)s] is joining room %(room)s with nick %(nick)s") % {'profile':profile,'room':roomId+'@'+service, 'nick':nick})
|
||||
try:
|
||||
self.clients[profile].join(service, roomId, nick).addCallbacks(self.__room_joined, self.__err_joining_room, callbackKeywords={'profile':profile}, errbackKeywords={'profile':profile})
|
||||
except:
|
||||
#XXX: this is a ugly workaround as MUCClient thrown an error if there is invalid chars in the room jid (like with the default string)
|
||||
#FIXME: must be removed when MUCClient manage this better
|
||||
self.__err_joining_room(None, profile)
|
||||
|
||||
def getHandler(self, profile):
|
||||
#reactor.callLater(15,self.join,"conference.necton2.int", "test", "Goffi \o/", profile)
|
||||
self.clients[profile] = SatMUCClient(self)
|
||||
return self.clients[profile]
|
||||
|
||||
|
||||
|
||||
class SatMUCClient (muc.MUCClient):
|
||||
#implements(iwokkel.IDisco)
|
||||
|
||||
def __init__(self, plugin_parent):
|
||||
self.plugin_parent = plugin_parent
|
||||
self.host = plugin_parent.host
|
||||
muc.MUCClient.__init__(self)
|
||||
self.joined_rooms = {}
|
||||
self.rec_subjects = {}
|
||||
print "init SatMUCClient OK"
|
||||
|
||||
def receivedGroupChat(self, room, user, body):
|
||||
debug('receivedGroupChat: room=%s user=%s body=%s', room, user, body)
|
||||
|
||||
def userJoinedRoom(self, room, user):
|
||||
debug (_("user %(nick)s has joined room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
|
||||
user_data={'entity':user.entity or '', 'affiliation':user.affiliation, 'role':user.role}
|
||||
self.host.bridge.roomUserJoined(room.roomIdentifier, room.service, user.nick, user_data, self.parent.profile)
|
||||
|
||||
def userLeftRoom(self, room, user):
|
||||
debug (_("user %(nick)s left room (%(room_id)s)") % {'nick':user.nick, 'room_id':room.occupantJID.userhost()})
|
||||
user_data={'entity':user.entity or '', 'affiliation':user.affiliation, 'role':user.role}
|
||||
self.host.bridge.roomUserLeft(room.roomIdentifier, room.service, user.nick, user_data, self.parent.profile)
|
||||
|
||||
def userUpdatedStatus(self, room, user, show, status):
|
||||
print("FIXME: MUC status not managed yet")
|
||||
#FIXME: gof
|
||||
|
||||
def receivedSubject(self, occupantJID, subject):
|
||||
room = self._getRoom(occupantJID)
|
||||
debug (_("New subject for room (%(room_id)s): %(subject)s") % {'room_id':room.occupantJID.userhost(),'subject':subject})
|
||||
room_jid = room.roomIdentifier+'@'+room.service
|
||||
self.rec_subjects[room_jid] = (room.roomIdentifier, room.service, subject)
|
||||
self.host.bridge.roomNewSubject(room.roomIdentifier, room.service, subject, self.parent.profile)
|
||||
|
||||
#def connectionInitialized(self):
|
||||
#pass
|
||||
|
||||
#def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
#return [disco.DiscoFeature(NS_VCARD)]
|
||||
|
||||
#def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
||||
#return []
|
||||
|
||||
246
src/plugins/plugin_xep_0054.py
Executable file
246
src/plugins/plugin_xep_0054.py
Executable file
|
|
@ -0,0 +1,246 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0054
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import protocol, defer, threads, reactor
|
||||
from twisted.words.protocols.jabber import client, jid, xmlstream
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
import os.path
|
||||
import pdb
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
from wokkel import disco, iwokkel
|
||||
|
||||
from base64 import b64decode
|
||||
from hashlib import sha1
|
||||
from time import sleep
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
|
||||
AVATAR_PATH = "/avatars"
|
||||
|
||||
IQ_GET = '/iq[@type="get"]'
|
||||
NS_VCARD = 'vcard-temp'
|
||||
VCARD_REQUEST = IQ_GET + '/vCard[@xmlns="' + NS_VCARD + '"]' #TODO: manage requests
|
||||
|
||||
PRESENCE = '/presence'
|
||||
NS_VCARD_UPDATE = 'vcard-temp:x:update'
|
||||
VCARD_UPDATE = PRESENCE + '/x[@xmlns="' + NS_VCARD_UPDATE + '"]'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "XEP 0054 Plugin",
|
||||
"import_name": "XEP_0054",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0054", "XEP-0153"],
|
||||
"dependencies": [],
|
||||
"main": "XEP_0054",
|
||||
"handler": "yes",
|
||||
"description": _("""Implementation of vcard-temp""")
|
||||
}
|
||||
|
||||
class XEP_0054():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin XEP_0054 initialization"))
|
||||
self.host = host
|
||||
self.avatar_path = os.path.expanduser(self.host.get_const('local_dir') + AVATAR_PATH)
|
||||
self.vcard_cache = host.memory.getPrivate("vcard_cache") or {} #used to store nicknames and avatar, key = jid
|
||||
if not os.path.exists(self.avatar_path):
|
||||
os.makedirs(self.avatar_path)
|
||||
host.bridge.addMethod("getCard", ".communication", in_sign='ss', out_sign='s', method=self.getCard)
|
||||
host.bridge.addMethod("getAvatarFile", ".communication", in_sign='s', out_sign='s', method=self.getAvatarFile)
|
||||
host.bridge.addMethod("getCardCache", ".communication", in_sign='s', out_sign='a{ss}', method=self.getCardCache)
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0054_handler(self)
|
||||
|
||||
def update_cache(self, jid, name, value):
|
||||
"""update cache value
|
||||
- save value in memory in case of change
|
||||
- send updatedValue signal if the value is new or updated
|
||||
"""
|
||||
if not self.vcard_cache.has_key(jid.userhost()):
|
||||
self.vcard_cache[jid.userhost()] = {}
|
||||
|
||||
cache = self.vcard_cache[jid.userhost()]
|
||||
old_value = cache[name] if cache.has_key(name) else None
|
||||
if not old_value or value != old_value:
|
||||
cache[name] = value
|
||||
self.host.memory.setPrivate("vcard_cache", self.vcard_cache)
|
||||
self.host.bridge.updatedValue('card_'+name, {'jid':jid.userhost(), name:value})
|
||||
|
||||
def get_cache(self, jid, name):
|
||||
"""return cached value for jid
|
||||
@param jid: target contact
|
||||
@param name: name of the value ('nick' or 'avatar')
|
||||
@return: wanted value or None"""
|
||||
try:
|
||||
return self.vcard_cache[jid.userhost()][name]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def save_photo(self, photo_xml):
|
||||
"""Parse a <PHOTO> elem and save the picture"""
|
||||
for elem in photo_xml.elements():
|
||||
if elem.name == 'TYPE':
|
||||
info(_('Photo of type [%s] found') % str(elem))
|
||||
if elem.name == 'BINVAL':
|
||||
debug(_('Decoding binary'))
|
||||
decoded = b64decode(str(elem))
|
||||
hash = sha1(decoded).hexdigest()
|
||||
filename = self.avatar_path+'/'+hash
|
||||
if not os.path.exists(filename):
|
||||
with open(filename,'wb') as file:
|
||||
file.write(decoded)
|
||||
debug(_("file saved to %s") % hash)
|
||||
else:
|
||||
debug(_("file [%s] already in cache") % hash)
|
||||
return hash
|
||||
|
||||
@defer.deferredGenerator
|
||||
def vCard2Dict(self, vcard, target):
|
||||
"""Convert a VCard to a dict, and save binaries"""
|
||||
debug (_("parsing vcard"))
|
||||
dictionary = {}
|
||||
d = defer.Deferred()
|
||||
|
||||
for elem in vcard.elements():
|
||||
if elem.name == 'FN':
|
||||
dictionary['fullname'] = unicode(elem)
|
||||
elif elem.name == 'NICKNAME':
|
||||
dictionary['nick'] = unicode(elem)
|
||||
self.update_cache(target, 'nick', dictionary['nick'])
|
||||
elif elem.name == 'URL':
|
||||
dictionary['website'] = unicode(elem)
|
||||
elif elem.name == 'EMAIL':
|
||||
dictionary['email'] = unicode(elem)
|
||||
elif elem.name == 'BDAY':
|
||||
dictionary['birthday'] = unicode(elem)
|
||||
elif elem.name == 'PHOTO':
|
||||
d2 = defer.waitForDeferred(
|
||||
threads.deferToThread(self.save_photo, elem))
|
||||
yield d2
|
||||
dictionary["avatar"] = d2.getResult()
|
||||
if not dictionary["avatar"]: #can happen in case of e.g. empty photo elem
|
||||
del dictionary['avatar']
|
||||
else:
|
||||
self.update_cache(target, 'avatar', dictionary['avatar'])
|
||||
else:
|
||||
info (_('FIXME: [%s] VCard tag is not managed yet') % elem.name)
|
||||
|
||||
yield dictionary
|
||||
|
||||
def vcard_ok(self, answer):
|
||||
"""Called after the first get IQ"""
|
||||
debug (_("VCard found"))
|
||||
|
||||
if answer.firstChildElement().name == "vCard":
|
||||
d = self.vCard2Dict(answer.firstChildElement(), jid.JID(answer["from"]))
|
||||
d.addCallback(lambda data: self.host.bridge.actionResult("RESULT", answer['id'], data))
|
||||
else:
|
||||
error (_("FIXME: vCard not found as first child element"))
|
||||
self.host.bridge.actionResult("SUPPRESS", answer['id'], {}) #FIXME: maybe an error message would be best
|
||||
|
||||
def vcard_err(self, failure):
|
||||
"""Called when something is wrong with registration"""
|
||||
error (_("Can't find VCard of %s") % failure.value.stanza['from'])
|
||||
self.host.bridge.actionResult("SUPPRESS", failure.value.stanza['id'], {}) #FIXME: maybe an error message would be best
|
||||
|
||||
def getCard(self, target, profile_key='@DEFAULT@'):
|
||||
"""Ask server for VCard
|
||||
@param target: jid from which we want the VCard
|
||||
@result: id to retrieve the profile"""
|
||||
current_jid, xmlstream = self.host.getJidNStream(profile_key)
|
||||
if not xmlstream:
|
||||
error (_('Asking vcard for an non-existant or not connected profile'))
|
||||
return ""
|
||||
to_jid = jid.JID(target)
|
||||
debug(_("Asking for %s's VCard") % to_jid.userhost())
|
||||
reg_request=IQ(xmlstream,'get')
|
||||
reg_request["from"]=current_jid.full()
|
||||
reg_request["to"] = to_jid.userhost()
|
||||
query=reg_request.addElement('vCard', NS_VCARD)
|
||||
reg_request.send(to_jid.userhost()).addCallbacks(self.vcard_ok, self.vcard_err)
|
||||
return reg_request["id"]
|
||||
|
||||
def getAvatarFile(self, hash):
|
||||
"""Give the full path of avatar from hash
|
||||
@param hash: SHA1 hash
|
||||
@return full_path
|
||||
"""
|
||||
filename = self.avatar_path+'/'+hash
|
||||
if not os.path.exists(filename):
|
||||
error (_("Asking for an uncached avatar [%s]") % hash)
|
||||
return ""
|
||||
return filename
|
||||
|
||||
def getCardCache(self, target):
|
||||
"""Request for cached values of profile
|
||||
return the cached nickname and avatar if exists, else get VCard
|
||||
"""
|
||||
to_jid = jid.JID(target)
|
||||
result = {}
|
||||
nick = self.get_cache(to_jid, 'nick')
|
||||
if nick:
|
||||
result['nick'] = nick
|
||||
avatar = self.get_cache(to_jid, 'avatar')
|
||||
if avatar:
|
||||
result['avatar'] = avatar
|
||||
return result
|
||||
|
||||
|
||||
|
||||
class XEP_0054_handler(XMPPHandler):
|
||||
implements(iwokkel.IDisco)
|
||||
|
||||
def __init__(self, plugin_parent):
|
||||
self.plugin_parent = plugin_parent
|
||||
self.host = plugin_parent.host
|
||||
|
||||
def connectionInitialized(self):
|
||||
self.xmlstream.addObserver(VCARD_UPDATE, self.update)
|
||||
|
||||
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
return [disco.DiscoFeature(NS_VCARD)]
|
||||
|
||||
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
||||
return []
|
||||
|
||||
def update(self, presence):
|
||||
"""Request for VCard's nickname
|
||||
return the cached nickname if exists, else get VCard
|
||||
"""
|
||||
to_jid = jid.JID(presence['from'])
|
||||
x_elem = filter (lambda x:x.name == "x", presence.elements())[0] #We only want the "x" element
|
||||
for elem in x_elem.elements():
|
||||
if elem.name == 'photo':
|
||||
hash = str(elem)
|
||||
old_avatar = self.plugin_parent.get_cache(to_jid, 'avatar')
|
||||
if not old_avatar or old_avatar != hash:
|
||||
debug(_('New avatar found, requesting vcard'))
|
||||
self.plugin_parent.getCard(to_jid.userhost(), self.parent.profile)
|
||||
558
src/plugins/plugin_xep_0065.py
Executable file
558
src/plugins/plugin_xep_0065.py
Executable file
|
|
@ -0,0 +1,558 @@
|
|||
#!/usr/bin/python
|
||||
#-*- coding: utf-8 -*-
|
||||
"""
|
||||
SAT plugin for managing xep-0065
|
||||
|
||||
Copyright (C)
|
||||
2002-2004 Dave Smith (dizzyd@jabber.org)
|
||||
2007-2008 Fabio Forno (xmpp:ff@jabber.bluendo.com)
|
||||
2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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/>.
|
||||
|
||||
--
|
||||
|
||||
This program is based on proxy65 (http://code.google.com/p/proxy65),
|
||||
originaly written by David Smith and modified by Fabio Forno.
|
||||
It is sublicensed under GPL v3 (or any later version) as allowed by the original
|
||||
license.
|
||||
|
||||
--
|
||||
|
||||
Here is a copy of the original license:
|
||||
|
||||
Copyright (C)
|
||||
2002-2004 Dave Smith (dizzyd@jabber.org)
|
||||
2007-2008 Fabio Forno (xmpp:ff@jabber.bluendo.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
from logging import debug, info, error
|
||||
from twisted.internet import protocol, reactor
|
||||
from twisted.protocols.basic import FileSender
|
||||
from twisted.words.xish import domish
|
||||
from twisted.web.client import getPage
|
||||
import struct
|
||||
import urllib
|
||||
import hashlib, pdb
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
|
||||
from wokkel import disco, iwokkel
|
||||
|
||||
IQ_SET = '/iq[@type="set"]'
|
||||
NS_BS = 'http://jabber.org/protocol/bytestreams'
|
||||
BS_REQUEST = IQ_SET + '/query[@xmlns="' + NS_BS + '"]'
|
||||
|
||||
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "XEP 0065 Plugin",
|
||||
"import_name": "XEP_0065",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0065"],
|
||||
"main": "XEP_0065",
|
||||
"handler": "yes",
|
||||
"description": _("""Implementation of SOCKS5 Bytestreams""")
|
||||
}
|
||||
|
||||
STATE_INITIAL = 0
|
||||
STATE_AUTH = 1
|
||||
STATE_REQUEST = 2
|
||||
STATE_READY = 3
|
||||
STATE_AUTH_USERPASS = 4
|
||||
STATE_TARGET_INITIAL = 5
|
||||
STATE_TARGET_AUTH = 6
|
||||
STATE_TARGET_REQUEST = 7
|
||||
STATE_TARGET_READY = 8
|
||||
STATE_LAST = 9
|
||||
|
||||
STATE_CONNECT_PENDING = STATE_LAST + 1
|
||||
|
||||
SOCKS5_VER = 0x05
|
||||
|
||||
ADDR_IPV4 = 0x01
|
||||
ADDR_DOMAINNAME = 0x03
|
||||
ADDR_IPV6 = 0x04
|
||||
|
||||
CMD_CONNECT = 0x01
|
||||
CMD_BIND = 0x02
|
||||
CMD_UDPASSOC = 0x03
|
||||
|
||||
AUTHMECH_ANON = 0x00
|
||||
AUTHMECH_USERPASS = 0x02
|
||||
AUTHMECH_INVALID = 0xFF
|
||||
|
||||
REPLY_SUCCESS = 0x00
|
||||
REPLY_GENERAL_FAILUR = 0x01
|
||||
REPLY_CONN_NOT_ALLOWED = 0x02
|
||||
REPLY_NETWORK_UNREACHABLE = 0x03
|
||||
REPLY_HOST_UNREACHABLE = 0x04
|
||||
REPLY_CONN_REFUSED = 0x05
|
||||
REPLY_TTL_EXPIRED = 0x06
|
||||
REPLY_CMD_NOT_SUPPORTED = 0x07
|
||||
REPLY_ADDR_NOT_SUPPORTED = 0x08
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class SOCKSv5(protocol.Protocol, FileSender):
|
||||
def __init__(self):
|
||||
debug(_("Protocol init"))
|
||||
self.state = STATE_INITIAL
|
||||
self.buf = ""
|
||||
self.supportedAuthMechs = [ AUTHMECH_ANON ]
|
||||
self.supportedAddrs = [ ADDR_DOMAINNAME ]
|
||||
self.enabledCommands = [ CMD_CONNECT ]
|
||||
self.peersock = None
|
||||
self.addressType = 0
|
||||
self.requestType = 0
|
||||
self.activeConns = {}
|
||||
self.pendingConns = {}
|
||||
self.transfered = 0 #nb of bytes already copied
|
||||
|
||||
def _startNegotiation(self):
|
||||
debug("_startNegotiation")
|
||||
self.state = STATE_TARGET_AUTH
|
||||
self.transport.write(struct.pack('!3B', SOCKS5_VER, 1, AUTHMECH_ANON))
|
||||
|
||||
def _parseNegotiation(self):
|
||||
debug("_parseNegotiation")
|
||||
try:
|
||||
# Parse out data
|
||||
ver, nmethod = struct.unpack('!BB', self.buf[:2])
|
||||
methods = struct.unpack('%dB' % nmethod, self.buf[2:nmethod+2])
|
||||
|
||||
# Ensure version is correct
|
||||
if ver != 5:
|
||||
self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID))
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
|
||||
# Trim off front of the buffer
|
||||
self.buf = self.buf[nmethod+2:]
|
||||
|
||||
# Check for supported auth mechs
|
||||
for m in self.supportedAuthMechs:
|
||||
if m in methods:
|
||||
# Update internal state, according to selected method
|
||||
if m == AUTHMECH_ANON:
|
||||
self.state = STATE_REQUEST
|
||||
elif m == AUTHMECH_USERPASS:
|
||||
self.state = STATE_AUTH_USERPASS
|
||||
# Complete negotiation w/ this method
|
||||
self.transport.write(struct.pack('!BB', SOCKS5_VER, m))
|
||||
return
|
||||
|
||||
# No supported mechs found, notify client and close the connection
|
||||
self.transport.write(struct.pack('!BB', SOCKS5_VER, AUTHMECH_INVALID))
|
||||
self.transport.loseConnection()
|
||||
except struct.error:
|
||||
pass
|
||||
|
||||
def _parseUserPass(self):
|
||||
debug("_parseUserPass")
|
||||
try:
|
||||
# Parse out data
|
||||
ver, ulen = struct.unpack('BB', self.buf[:2])
|
||||
uname, = struct.unpack('%ds' % ulen, self.buf[2:ulen + 2])
|
||||
plen, = struct.unpack('B', self.buf[ulen + 2])
|
||||
password, = struct.unpack('%ds' % plen, self.buf[ulen + 3:ulen + 3 + plen])
|
||||
# Trim off fron of the buffer
|
||||
self.buf = self.buf[3 + ulen + plen:]
|
||||
# Fire event to authenticate user
|
||||
if self.authenticateUserPass(uname, password):
|
||||
# Signal success
|
||||
self.state = STATE_REQUEST
|
||||
self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x00))
|
||||
else:
|
||||
# Signal failure
|
||||
self.transport.write(struct.pack('!BB', SOCKS5_VER, 0x01))
|
||||
self.transport.loseConnection()
|
||||
except struct.error:
|
||||
pass
|
||||
|
||||
def sendErrorReply(self, errorcode):
|
||||
debug("sendErrorReply")
|
||||
# Any other address types are not supported
|
||||
result = struct.pack('!BBBBIH', SOCKS5_VER, errorcode, 0, 1, 0, 0)
|
||||
self.transport.write(result)
|
||||
self.transport.loseConnection()
|
||||
|
||||
def addConnection(self, address, connection):
|
||||
info(_("Adding connection: %(address)s, %(connection)s") % {'address':address, 'connection':connection})
|
||||
olist = self.pendingConns.get(address, [])
|
||||
if len(olist) <= 1:
|
||||
olist.append(connection)
|
||||
self.pendingConns[address] = olist
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def removePendingConnection(self, address, connection):
|
||||
olist = self.pendingConns[address]
|
||||
if len(olist) == 1:
|
||||
del self.pendingConns[address]
|
||||
else:
|
||||
olist.remove(connection)
|
||||
self.pendingConns[address] = olist
|
||||
|
||||
def removeActiveConnection(self, address):
|
||||
del self.activeConns[address]
|
||||
|
||||
def _parseRequest(self):
|
||||
debug("_parseRequest")
|
||||
try:
|
||||
# Parse out data and trim buffer accordingly
|
||||
ver, cmd, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4])
|
||||
|
||||
# Ensure we actually support the requested address type
|
||||
if self.addressType not in self.supportedAddrs:
|
||||
self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
# Deal with addresses
|
||||
if self.addressType == ADDR_IPV4:
|
||||
addr, port = struct.unpack('!IH', self.buf[4:10])
|
||||
self.buf = self.buf[10:]
|
||||
elif self.addressType == ADDR_DOMAINNAME:
|
||||
nlen = ord(self.buf[4])
|
||||
addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:])
|
||||
self.buf = self.buf[7 + len(addr):]
|
||||
else:
|
||||
# Any other address types are not supported
|
||||
self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
# Ensure command is supported
|
||||
if cmd not in self.enabledCommands:
|
||||
# Send a not supported error
|
||||
self.sendErrorReply(REPLY_CMD_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
# Process the command
|
||||
if cmd == CMD_CONNECT:
|
||||
self.connectRequested(addr, port)
|
||||
elif cmd == CMD_BIND:
|
||||
self.bindRequested(addr, port)
|
||||
else:
|
||||
# Any other command is not supported
|
||||
self.sendErrorReply(REPLY_CMD_NOT_SUPPORTED)
|
||||
|
||||
except struct.error, why:
|
||||
return None
|
||||
|
||||
def _makeRequest(self):
|
||||
debug("_makeRequest")
|
||||
self.state = STATE_TARGET_REQUEST
|
||||
sha1 = hashlib.sha1(self.sid + self.initiator_jid + self.target_jid).hexdigest()
|
||||
request = struct.pack('!5B%dsH' % len(sha1), SOCKS5_VER, CMD_CONNECT, 0, ADDR_DOMAINNAME, len(sha1), sha1, 0)
|
||||
self.transport.write(request)
|
||||
|
||||
def _parseRequestReply(self):
|
||||
debug("_parseRequestReply")
|
||||
try:
|
||||
ver, rep, rsvd, self.addressType = struct.unpack('!BBBB', self.buf[:4])
|
||||
# Ensure we actually support the requested address type
|
||||
if self.addressType not in self.supportedAddrs:
|
||||
self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
# Deal with addresses
|
||||
if self.addressType == ADDR_IPV4:
|
||||
addr, port = struct.unpack('!IH', self.buf[4:10])
|
||||
self.buf = self.buf[10:]
|
||||
elif self.addressType == ADDR_DOMAINNAME:
|
||||
nlen = ord(self.buf[4])
|
||||
addr, port = struct.unpack('!%dsH' % nlen, self.buf[5:])
|
||||
self.buf = self.buf[7 + len(addr):]
|
||||
else:
|
||||
# Any other address types are not supported
|
||||
self.sendErrorReply(REPLY_ADDR_NOT_SUPPORTED)
|
||||
return
|
||||
|
||||
# Ensure reply is OK
|
||||
if rep != REPLY_SUCCESS:
|
||||
self.loseConnection()
|
||||
return
|
||||
|
||||
debug(_("Saving file in %s."), self.data["dest_path"])
|
||||
self.dest_file = open(self.data["dest_path"], 'w')
|
||||
self.state = STATE_TARGET_READY
|
||||
self.activateCB(self.target_jid, self.initiator_jid, self.sid, self.IQ_id, self.xmlstream)
|
||||
|
||||
|
||||
except struct.error, why:
|
||||
return None
|
||||
|
||||
def connectionMade(self):
|
||||
debug("connectionMade (mode = %s)" % self.mode)
|
||||
self.host.registerProgressCB(self.transfert_id, self.getProgress)
|
||||
|
||||
if self.mode == "target":
|
||||
self.state = STATE_TARGET_INITIAL
|
||||
self._startNegotiation()
|
||||
|
||||
def connectRequested(self, addr, port):
|
||||
debug("connectRequested")
|
||||
# Check for special connect to the namespace -- this signifies that the client
|
||||
# is just checking to ensure it can connect to the streamhost
|
||||
if addr == "http://jabber.org/protocol/bytestreams":
|
||||
self.connectCompleted(addr, 0)
|
||||
self.transport.loseConnection()
|
||||
return
|
||||
|
||||
# Save addr, for cleanup
|
||||
self.addr = addr
|
||||
|
||||
# Check to see if the requested address is already
|
||||
# activated -- send an error if so
|
||||
if addr in self.activeConns:
|
||||
self.sendErrorReply(socks5.REPLY_CONN_NOT_ALLOWED)
|
||||
return
|
||||
|
||||
# Add this address to the pending connections
|
||||
if self.addConnection(addr, self):
|
||||
self.connectCompleted(addr, 0)
|
||||
self.transport.stopReading()
|
||||
else:
|
||||
self.sendErrorReply(socks5.REPLY_CONN_REFUSED)
|
||||
|
||||
def getProgress(self, data):
|
||||
"""Fill data with position of current transfert"""
|
||||
try:
|
||||
data["position"] = str(self.dest_file.tell())
|
||||
data["size"] = self.filesize
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
|
||||
def fileTransfered(self, d):
|
||||
info(_("File transfer completed, closing connection"))
|
||||
self.transport.loseConnection()
|
||||
try:
|
||||
self.dest_file.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
def updateTransfered(self, data):
|
||||
self.transfered+=len(data)
|
||||
return data
|
||||
|
||||
def connectCompleted(self, remotehost, remoteport):
|
||||
debug("connectCompleted")
|
||||
if self.addressType == ADDR_IPV4:
|
||||
result = struct.pack('!BBBBIH', SOCKS5_VER, REPLY_SUCCESS, 0, 1, remotehost, remoteport)
|
||||
elif self.addressType == ADDR_DOMAINNAME:
|
||||
result = struct.pack('!BBBBB%dsH' % len(remotehost), SOCKS5_VER, REPLY_SUCCESS, 0,
|
||||
ADDR_DOMAINNAME, len(remotehost), remotehost, remoteport)
|
||||
self.transport.write(result)
|
||||
self.state = STATE_READY
|
||||
self.dest_file=open(self.filepath)
|
||||
d=self.beginFileTransfer(self.dest_file, self.transport, self.updateTransfered)
|
||||
d.addCallback(self.fileTransfered)
|
||||
|
||||
def bindRequested(self, addr, port):
|
||||
pass
|
||||
|
||||
def authenticateUserPass(self, user, passwd):
|
||||
debug("User/pass: %s/%s", user, passwd)
|
||||
return True
|
||||
|
||||
def dataReceived(self, buf):
|
||||
if self.state == STATE_TARGET_READY:
|
||||
self.dest_file.write(buf)
|
||||
self.transfered+=len(buf)
|
||||
return
|
||||
|
||||
self.buf = self.buf + buf
|
||||
if self.state == STATE_INITIAL:
|
||||
self._parseNegotiation()
|
||||
if self.state == STATE_AUTH_USERPASS:
|
||||
self._parseUserPass()
|
||||
if self.state == STATE_REQUEST:
|
||||
self._parseRequest()
|
||||
if self.state == STATE_TARGET_AUTH:
|
||||
ver, method = struct.unpack('!BB', buf)
|
||||
self.buf = self.buf[2:]
|
||||
if ver!=SOCKS5_VER or method!=AUTHMECH_ANON:
|
||||
self.transport.loseConnection()
|
||||
else:
|
||||
self._makeRequest()
|
||||
if self.state == STATE_TARGET_REQUEST:
|
||||
self._parseRequestReply()
|
||||
|
||||
|
||||
def clientConnectionLost(self, reason):
|
||||
debug("clientConnectionLost")
|
||||
self.transport.loseConnection()
|
||||
|
||||
def connectionLost(self, reason):
|
||||
debug("connectionLost")
|
||||
self.host.removeProgressCB(self.transfert_id)
|
||||
if self.state == STATE_CONNECT_PENDING:
|
||||
self.removePendingConnection(self.addr, self)
|
||||
else:
|
||||
self.transport.unregisterProducer()
|
||||
if self.peersock != None:
|
||||
self.peersock.peersock = None
|
||||
self.peersock.transport.unregisterProducer()
|
||||
self.peersock = None
|
||||
self.removeActiveConnection(self.addr)
|
||||
|
||||
class Socks5ServerFactory(protocol.ServerFactory):
|
||||
protocol = SOCKSv5
|
||||
protocol.mode = "initiator" #FIXME: Q&D way, fix it
|
||||
|
||||
|
||||
def startedConnecting(self, connector):
|
||||
debug (_("Socks 5 server connection started"))
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
debug (_("Socks 5 server connection lost (reason: %s)"), reason)
|
||||
|
||||
class Socks5ClientFactory(protocol.ClientFactory):
|
||||
protocol = SOCKSv5
|
||||
protocol.mode = "target" #FIXME: Q&D way, fix it
|
||||
|
||||
def startedConnecting(self, connector):
|
||||
debug (_("Socks 5 client connection started"))
|
||||
|
||||
def clientConnectionLost(self, connector, reason):
|
||||
debug (_("Socks 5 client connection lost (reason: %s)"), reason)
|
||||
|
||||
|
||||
class XEP_0065():
|
||||
|
||||
params = """
|
||||
<params>
|
||||
<general>
|
||||
<category name="File Transfert">
|
||||
<param name="IP" value='0.0.0.0' default_cb='yes' type="string" />
|
||||
<param name="Port" value="28915" type="string" />
|
||||
</category>
|
||||
</general>
|
||||
</params>
|
||||
"""
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin XEP_0065 initialization"))
|
||||
self.host = host
|
||||
debug(_("registering"))
|
||||
self.server_factory = Socks5ServerFactory()
|
||||
self.server_factory.protocol.host = self.host #needed for progress CB
|
||||
self.client_factory = Socks5ClientFactory()
|
||||
|
||||
#parameters
|
||||
host.memory.importParams(XEP_0065.params)
|
||||
host.memory.setDefault("IP", "File Transfert", self.getExternalIP)
|
||||
|
||||
port = int(self.host.memory.getParamA("Port", "File Transfert"))
|
||||
info(_("Launching Socks5 Stream server on port %d"), port)
|
||||
reactor.listenTCP(port, self.server_factory)
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0065_handler(self)
|
||||
|
||||
def getExternalIP(self):
|
||||
"""Return IP visible from outside, by asking to a website"""
|
||||
return getPage("http://www.goffi.org/sat_tools/get_ip.php")
|
||||
|
||||
def setData(self, data, id):
|
||||
self.data = data
|
||||
self.transfert_id = id
|
||||
|
||||
def sendFile(self, id, filepath, size):
|
||||
#lauching socks5 initiator
|
||||
debug(_("Launching socks5 initiator"))
|
||||
self.server_factory.protocol.mode = "initiator"
|
||||
self.server_factory.protocol.filepath = filepath
|
||||
self.server_factory.protocol.filesize = size
|
||||
self.server_factory.protocol.transfert_id = id
|
||||
|
||||
def getFile(self, iq, profile_key='@DEFAULT@'):
|
||||
"""Get file using byte stream"""
|
||||
client = self.host.getClient(profile_key)
|
||||
assert(client)
|
||||
iq.handled = True
|
||||
SI_elem = iq.firstChildElement()
|
||||
IQ_id = iq['id']
|
||||
for element in SI_elem.elements():
|
||||
if element.name == "streamhost":
|
||||
info (_("Stream proposed: host=[%(host)s] port=[%(port)s]") % {'host':element['host'], 'port':element['port']})
|
||||
factory = self.client_factory
|
||||
self.server_factory.protocol.mode = "target"
|
||||
factory.protocol.host = self.host #needed for progress CB
|
||||
factory.protocol.xmlstream = client.xmlstream
|
||||
factory.protocol.data = self.data
|
||||
factory.protocol.transfert_id = self.transfert_id
|
||||
factory.protocol.filesize = self.data["size"]
|
||||
factory.protocol.sid = SI_elem['sid']
|
||||
factory.protocol.initiator_jid = element['jid']
|
||||
factory.protocol.target_jid = client.jid.full()
|
||||
factory.protocol.IQ_id = IQ_id
|
||||
factory.protocol.activateCB = self.activateStream
|
||||
reactor.connectTCP(element['host'], int(element['port']), factory)
|
||||
|
||||
def activateStream(self, from_jid, to_jid, sid, IQ_id, xmlstream):
|
||||
debug(_("activating stream"))
|
||||
result = domish.Element(('', 'iq'))
|
||||
result['type'] = 'result'
|
||||
result['id'] = IQ_id
|
||||
result['from'] = from_jid
|
||||
result['to'] = to_jid
|
||||
query = result.addElement('query', 'http://jabber.org/protocol/bytestreams')
|
||||
query['sid'] = sid
|
||||
streamhost = query.addElement('streamhost-used')
|
||||
streamhost['jid'] = to_jid #FIXME: use real streamhost
|
||||
xmlstream.send(result)
|
||||
|
||||
class XEP_0065_handler(XMPPHandler):
|
||||
implements(iwokkel.IDisco)
|
||||
|
||||
def __init__(self, plugin_parent):
|
||||
self.plugin_parent = plugin_parent
|
||||
self.host = plugin_parent.host
|
||||
|
||||
def connectionInitialized(self):
|
||||
self.xmlstream.addObserver(BS_REQUEST, self.plugin_parent.getFile)
|
||||
|
||||
|
||||
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
return [disco.DiscoFeature(NS_BS)]
|
||||
|
||||
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
||||
return []
|
||||
143
src/plugins/plugin_xep_0077.py
Executable file
143
src/plugins/plugin_xep_0077.py
Executable file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0077
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, error
|
||||
from twisted.words.protocols.jabber import client, jid
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
from twisted.words.protocols.jabber.xmlstream import IQ
|
||||
from twisted.internet import reactor
|
||||
from sat.tools.xml_tools import dataForm2xml
|
||||
import pdb
|
||||
|
||||
from wokkel import data_form
|
||||
|
||||
NS_REG = 'jabber:iq:register'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "XEP 0077 Plugin",
|
||||
"import_name": "XEP_0077",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0077"],
|
||||
"dependencies": [],
|
||||
"main": "XEP_0077",
|
||||
"description": _("""Implementation of in-band registration""")
|
||||
}
|
||||
|
||||
class XEP_0077():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin XEP_0077 initialization"))
|
||||
self.host = host
|
||||
self.triggers = {} #used by other protocol (e.g. XEP-0100) to finish registration. key = target_jid
|
||||
host.bridge.addMethod("in_band_register", ".communication", in_sign='ss', out_sign='s', method=self.in_band_register)
|
||||
host.bridge.addMethod("in_band_submit", ".request", in_sign='sa(ss)', out_sign='s', method=self.in_band_submit)
|
||||
|
||||
def addTrigger(self, target, cb, profile):
|
||||
"""Add a callback which is called when registration to target is successful"""
|
||||
self.triggers[target] = (cb, profile)
|
||||
|
||||
def reg_ok(self, answer):
|
||||
"""Called after the first get IQ"""
|
||||
try:
|
||||
x_elem = filter (lambda x:x.name == "x", answer.firstChildElement().elements())[0] #We only want the "x" element (data form)
|
||||
except IndexError:
|
||||
info(_("No data form found"))
|
||||
#TODO: manage registration without data form
|
||||
answer_data={"reason": "unmanaged", "message":_("This gateway can't be managed by SàT, sorry :(")}
|
||||
answer_type = "ERROR"
|
||||
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
|
||||
return
|
||||
|
||||
form = data_form.Form.fromElement(x_elem)
|
||||
xml_data = dataForm2xml(form)
|
||||
self.host.bridge.actionResult("XMLUI", answer['id'], {"target":answer["from"], "type":"registration", "xml":xml_data})
|
||||
|
||||
def reg_err(self, failure):
|
||||
"""Called when something is wrong with registration"""
|
||||
info (_("Registration failure: %s") % str(failure.value))
|
||||
answer_data = {}
|
||||
answer_data['reason'] = 'unknown'
|
||||
answer_data={"message":"%s [code: %s]" % (failure.value.condition, unicode(failure.value))}
|
||||
answer_type = "ERROR"
|
||||
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
|
||||
|
||||
def unregistrationAnswer(self, answer):
|
||||
debug (_("registration answer: %s") % answer.toXml())
|
||||
answer_type = "SUCCESS"
|
||||
answer_data={"message":_("Your are now unregistred")}
|
||||
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
|
||||
|
||||
def unregistrationFailure(self, failure):
|
||||
info (_("Unregistration failure: %s") % str(failure.value))
|
||||
answer_type = "ERROR"
|
||||
answer_data = {}
|
||||
answer_data['reason'] = 'unknown'
|
||||
answer_data={"message":_("Unregistration failed: %s") % failure.value.condition}
|
||||
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
|
||||
|
||||
def registrationAnswer(self, answer):
|
||||
debug (_("registration answer: %s") % answer.toXml())
|
||||
answer_type = "SUCCESS"
|
||||
answer_data={"message":_("Registration successfull")}
|
||||
self.host.bridge.actionResult(answer_type, answer['id'], answer_data)
|
||||
if self.triggers.has_key(answer["from"]):
|
||||
callback,profile = self.triggers[answer["from"]]
|
||||
callback(answer["from"], profile)
|
||||
del self.triggers[answer["from"]]
|
||||
|
||||
def registrationFailure(self, failure):
|
||||
info (_("Registration failure: %s") % str(failure.value))
|
||||
print failure.value.stanza.toXml()
|
||||
answer_type = "ERROR"
|
||||
answer_data = {}
|
||||
if failure.value.condition == 'conflict':
|
||||
answer_data['reason'] = 'conflict'
|
||||
answer_data={"message":_("Username already exists, please choose an other one")}
|
||||
else:
|
||||
answer_data['reason'] = 'unknown'
|
||||
answer_data={"message":_("Registration failed")}
|
||||
self.host.bridge.actionResult(answer_type, failure.value.stanza['id'], answer_data)
|
||||
if self.triggers.has_key(answer["from"]):
|
||||
del self.triggers[answer["from"]]
|
||||
|
||||
def in_band_submit(self, action, target, fields, profile):
|
||||
"""Submit a form for registration, using data_form"""
|
||||
id, deferred = self.host.submitForm(action, target, fields, profile)
|
||||
if action == 'CANCEL':
|
||||
deferred.addCallbacks(self.unregistrationAnswer, self.unregistrationFailure)
|
||||
else:
|
||||
deferred.addCallbacks(self.registrationAnswer, self.registrationFailure)
|
||||
return id
|
||||
|
||||
def in_band_register(self, target, profile_key='@DEFAULT@'):
|
||||
"""register to a target JID"""
|
||||
current_jid, xmlstream = self.host.getJidNStream(profile_key)
|
||||
if not xmlstream:
|
||||
error (_('Asking for an non-existant or not connected profile'))
|
||||
return ""
|
||||
to_jid = jid.JID(target)
|
||||
debug(_("Asking registration for [%s]") % to_jid.full())
|
||||
reg_request=IQ(xmlstream,'get')
|
||||
reg_request["from"]=current_jid.full()
|
||||
reg_request["to"] = to_jid.full()
|
||||
query=reg_request.addElement('query', NS_REG)
|
||||
reg_request.send(to_jid.full()).addCallbacks(self.reg_ok, self.reg_err)
|
||||
return reg_request["id"]
|
||||
208
src/plugins/plugin_xep_0096.py
Executable file
208
src/plugins/plugin_xep_0096.py
Executable file
|
|
@ -0,0 +1,208 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing xep-0096
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, error
|
||||
from twisted.words.xish import domish
|
||||
from twisted.internet import protocol
|
||||
from twisted.words.protocols.jabber import client, jid
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
import os.path
|
||||
from twisted.internet import reactor #FIXME best way ???
|
||||
import pdb
|
||||
|
||||
from zope.interface import implements
|
||||
|
||||
try:
|
||||
from twisted.words.protocols.xmlstream import XMPPHandler
|
||||
except ImportError:
|
||||
from wokkel.subprotocols import XMPPHandler
|
||||
|
||||
from wokkel import disco, iwokkel
|
||||
|
||||
IQ_SET = '/iq[@type="set"]'
|
||||
NS_SI = 'http://jabber.org/protocol/si'
|
||||
SI_REQUEST = IQ_SET + '/si[@xmlns="' + NS_SI + '"]'
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "XEP 0096 Plugin",
|
||||
"import_name": "XEP_0096",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0096"],
|
||||
"dependencies": ["XEP_0065"],
|
||||
"main": "XEP_0096",
|
||||
"handler": "yes",
|
||||
"description": _("""Implementation of SI File Transfert""")
|
||||
}
|
||||
|
||||
class XEP_0096():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Plugin XEP_0096 initialization"))
|
||||
self.host = host
|
||||
self._waiting_for_approval = {}
|
||||
host.bridge.addMethod("sendFile", ".communication", in_sign='sss', out_sign='s', method=self.sendFile)
|
||||
|
||||
def getHandler(self, profile):
|
||||
return XEP_0096_handler(self)
|
||||
|
||||
def xep_96(self, IQ, profile):
|
||||
info (_("XEP-0096 management"))
|
||||
IQ.handled=True
|
||||
SI_elem = IQ.firstChildElement()
|
||||
debug(SI_elem.toXml())
|
||||
filename = ""
|
||||
file_size = ""
|
||||
for element in SI_elem.elements():
|
||||
if element.name == "file":
|
||||
info (_("File proposed: name=[%(name)s] size=%(size)s") % {'name':element['name'], 'size':element['size']})
|
||||
filename = element["name"]
|
||||
file_size = element["size"]
|
||||
elif element.name == "feature":
|
||||
from_jid = IQ["from"]
|
||||
self._waiting_for_approval[IQ["id"]] = (element, from_jid, file_size, profile)
|
||||
data={ "filename":filename, "from":from_jid, "size":file_size }
|
||||
self.host.askConfirmation(IQ["id"], "FILE_TRANSFERT", data, self.confirmationCB)
|
||||
|
||||
def confirmationCB(self, id, accepted, data):
|
||||
"""Called on confirmation answer"""
|
||||
if accepted:
|
||||
data['size'] = self._waiting_for_approval[id][2]
|
||||
self.host.plugins["XEP_0065"].setData(data, id)
|
||||
self.approved(id)
|
||||
else:
|
||||
debug (_("Transfert [%s] refused"), id)
|
||||
del(self._waiting_for_approval[id])
|
||||
|
||||
def approved(self, id):
|
||||
"""must be called when a file transfert has be accepted by client"""
|
||||
debug (_("Transfert [%s] accepted"), id)
|
||||
|
||||
if ( not self._waiting_for_approval.has_key(id) ):
|
||||
error (_("Approved unknow id !"))
|
||||
#TODO: manage this (maybe approved by several frontends)
|
||||
else:
|
||||
element, from_id, size, profile = self._waiting_for_approval[id]
|
||||
del(self._waiting_for_approval[id])
|
||||
self.negociate(element, id, from_id, profile)
|
||||
|
||||
def negociate(self, feat_elem, id, to_jid, profile):
|
||||
#TODO: put this in a plugin
|
||||
#FIXME: over ultra mega ugly, need to be generic
|
||||
client = self.host.getClient(profile)
|
||||
assert(client)
|
||||
info (_("Feature negociation"))
|
||||
data = feat_elem.firstChildElement()
|
||||
field = data.firstChildElement()
|
||||
#FIXME: several options ! Q&D code for test only
|
||||
option = field.firstChildElement()
|
||||
value = option.firstChildElement()
|
||||
if unicode(value) == "http://jabber.org/protocol/bytestreams":
|
||||
#ugly, as usual, need to be entirely rewritten (just for test !)
|
||||
result = domish.Element(('', 'iq'))
|
||||
result['type'] = 'result'
|
||||
result['id'] = id
|
||||
result['to'] = to_jid
|
||||
si = result.addElement('si', 'http://jabber.org/protocol/si')
|
||||
file = si.addElement('file', 'http://jabber.org/protocol/si/profile/file-transfer')
|
||||
feature = si.addElement('feature', 'http://jabber.org/protocol/feature-neg')
|
||||
x = feature.addElement('x', 'jabber:x:data')
|
||||
x['type'] = 'submit'
|
||||
field = x.addElement('field')
|
||||
field['var'] = 'stream-method'
|
||||
value = field.addElement('value')
|
||||
value.addContent('http://jabber.org/protocol/bytestreams')
|
||||
client.xmlstream.send(result)
|
||||
|
||||
def fileCB(self, answer, xmlstream, current_jid):
|
||||
if answer['type']=="result": #FIXME FIXME FIXME ugly ugly ugly ! and temp FIXME FIXME FIXME
|
||||
info("SENDING UGLY ANSWER")
|
||||
offer=client.IQ(xmlstream,'set')
|
||||
offer["from"]=current_jid.full()
|
||||
offer["to"]=answer['from']
|
||||
query=offer.addElement('query', 'http://jabber.org/protocol/bytestreams')
|
||||
query['mode']='tcp'
|
||||
streamhost=query.addElement('streamhost')
|
||||
streamhost['host']=self.host.memory.getParamA("IP", "File Transfert")
|
||||
streamhost['port']=self.host.memory.getParamA("Port", "File Transfert")
|
||||
streamhost['jid']=current_jid.full()
|
||||
offer.send()
|
||||
|
||||
def sendFile(self, to, filepath, profile_key='@DEFAULT@'):
|
||||
"""send a file using XEP-0096
|
||||
Return an unique id to identify the transfert
|
||||
"""
|
||||
current_jid, xmlstream = self.host.getJidNStream(profile_key)
|
||||
if not xmlstream:
|
||||
error (_('Asking for an non-existant or not connected profile'))
|
||||
return ""
|
||||
debug ("sendfile (%s) to %s", filepath, to )
|
||||
print type(filepath), type(to)
|
||||
|
||||
statinfo = os.stat(filepath)
|
||||
|
||||
offer=client.IQ(xmlstream,'set')
|
||||
debug ("Transfert ID: %s", offer["id"])
|
||||
|
||||
self.host.plugins["XEP_0065"].sendFile(offer["id"], filepath, str(statinfo.st_size))
|
||||
|
||||
offer["from"]=current_jid.full()
|
||||
offer["to"]=jid.JID(to).full()
|
||||
si=offer.addElement('si','http://jabber.org/protocol/si')
|
||||
si["mime-type"]='text/plain'
|
||||
si["profile"]='http://jabber.org/protocol/si/profile/file-transfer'
|
||||
file = si.addElement('file', 'http://jabber.org/protocol/si/profile/file-transfer')
|
||||
file['name']=os.path.basename(filepath)
|
||||
file['size']=str(statinfo.st_size)
|
||||
|
||||
###
|
||||
# FIXME: Ugly temporary hard coded implementation of XEP-0020 & XEP-0004,
|
||||
# Need to be recoded elsewhere in a more generic way
|
||||
###
|
||||
|
||||
feature=si.addElement('feature', "http://jabber.org/protocol/feature-neg")
|
||||
x=feature.addElement('x', "jabber:x:data")
|
||||
x['type']='form'
|
||||
field=x.addElement('field')
|
||||
field['type']='list-single'
|
||||
field['var']='stream-method'
|
||||
option = field.addElement('option')
|
||||
value = option.addElement('value', content='http://jabber.org/protocol/bytestreams')
|
||||
|
||||
offer.addCallback(self.fileCB, current_jid = current_jid, xmlstream = xmlstream)
|
||||
offer.send()
|
||||
return offer["id"] #XXX: using IQ id as file transfert id seems OK as IQ id are required
|
||||
|
||||
class XEP_0096_handler(XMPPHandler):
|
||||
implements(iwokkel.IDisco)
|
||||
|
||||
def __init__(self, plugin_parent):
|
||||
self.plugin_parent = plugin_parent
|
||||
self.host = plugin_parent.host
|
||||
|
||||
def connectionInitialized(self):
|
||||
self.xmlstream.addObserver(SI_REQUEST, self.plugin_parent.xep_96, profile = self.parent.profile)
|
||||
|
||||
def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
|
||||
return [disco.DiscoFeature(NS_SI)]
|
||||
|
||||
def getDiscoItems(self, requestor, target, nodeIdentifier=''):
|
||||
return []
|
||||
|
||||
127
src/plugins/plugin_xep_0100.py
Executable file
127
src/plugins/plugin_xep_0100.py
Executable file
|
|
@ -0,0 +1,127 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
SAT plugin for managing gateways (xep-0100)
|
||||
Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.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, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
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 logging import debug, info, error
|
||||
from twisted.internet import protocol
|
||||
from twisted.words.protocols.jabber import client, jid
|
||||
from twisted.words.protocols.jabber import error as jab_error
|
||||
import twisted.internet.error
|
||||
import pdb
|
||||
|
||||
from wokkel import disco, iwokkel
|
||||
|
||||
PLUGIN_INFO = {
|
||||
"name": "Gateways Plugin",
|
||||
"import_name": "XEP_0100",
|
||||
"type": "XEP",
|
||||
"protocols": ["XEP-0100"],
|
||||
"dependencies": ["XEP_0077"],
|
||||
"main": "XEP_0100",
|
||||
"description": _("""Implementation of Gateways protocol""")
|
||||
}
|
||||
|
||||
class XEP_0100():
|
||||
|
||||
def __init__(self, host):
|
||||
info(_("Gateways plugin initialization"))
|
||||
self.host = host
|
||||
self.__gateways = {} #dict used to construct the answer to findGateways. Key = target jid
|
||||
host.bridge.addMethod("findGateways", ".communication", in_sign='ss', out_sign='s', method=self.findGateways)
|
||||
host.bridge.addMethod("gatewayRegister", ".request", in_sign='ssa(ss)s', out_sign='s', method=self.gatewayRegister)
|
||||
|
||||
def __inc_handled_items(self, request_id):
|
||||
self.__gateways[request_id]['__handled_items']+=1
|
||||
|
||||
if self.__gateways[request_id]['__total_items'] == self.__gateways[request_id]['__handled_items']:
|
||||
debug (_("All items checked for id [%s]") % str(request_id))
|
||||
|
||||
del self.__gateways[request_id]['__total_items']
|
||||
del self.__gateways[request_id]['__handled_items']
|
||||
self.host.actionResultExt(request_id,"DICT_DICT",self.__gateways[request_id])
|
||||
|
||||
def discoInfo(self, disco, entity, request_id):
|
||||
"""Find disco infos about entity, to check if it is a gateway"""
|
||||
|
||||
for identity in disco.identities:
|
||||
if identity[0] == 'gateway':
|
||||
print (_("Found gateway (%(jid)s): %(identity)s") % {'jid':entity.full(), 'identity':disco.identities[identity]})
|
||||
self.__gateways[request_id][entity.full()] = {
|
||||
'name':disco.identities[identity],
|
||||
'type':identity[1]
|
||||
}
|
||||
|
||||
self.__inc_handled_items(request_id)
|
||||
|
||||
def discoInfoErr(self, failure, entity, request_id):
|
||||
"""Something is going wrong with disco"""
|
||||
failure.trap(jab_error.StanzaError,twisted.internet.error.ConnectionLost)
|
||||
error(_("Error when discovering [%(jid)s]: %(error)s") % {'jid':entity.full(), 'error':failure.getErrorMessage()})
|
||||
self.__inc_handled_items(request_id)
|
||||
|
||||
|
||||
def discoItems(self, disco, request_id, target, client):
|
||||
"""Look for items with disco protocol, and ask infos for each one"""
|
||||
#FIXME: target is used as we can't find the original iq node (parent is None)
|
||||
# an other way would avoid this useless parameter (is there a way with wokkel ?)
|
||||
if len(disco._items) == 0:
|
||||
debug (_("No gateway found"))
|
||||
self.host.actionResultExt(request_id,"DICT_DICT",{})
|
||||
return
|
||||
|
||||
self.__gateways[request_id] = {'__total_items':len(disco._items), '__handled_items':0, '__private__':{'target':target.full()}}
|
||||
for item in disco._items:
|
||||
#TODO: need to set a timeout for theses requests
|
||||
debug (_("item found: %s"), item.name)
|
||||
client.disco.requestInfo(item.entity).addCallback(self.discoInfo, entity=item.entity, request_id=request_id).addErrback(self.discoInfoErr, entity=item.entity, request_id=request_id)
|
||||
|
||||
def discoItemsErr(self, failure, request_id, target, client):
|
||||
"""Something is going wrong with disco"""
|
||||
error(_("Error when discovering [%(target)s]: %(condition)s") % {'target':target.full(), 'condition':unicode(failure.value)})
|
||||
message_data={"reason": "connection error", "message":_(u"Error while trying to discover %(target)s gateways: %(error_mess)s") % {'target':target.full(), 'error_mess':unicode(failure.value)}}
|
||||
self.host.bridge.actionResult("ERROR", request_id, message_data)
|
||||
|
||||
|
||||
def registrationSuccessful(self, target, profile):
|
||||
"""Called when in_band registration is ok, we must now follow the rest of procedure"""
|
||||
debug (_("Registration successful, doing the rest"))
|
||||
self.host.addContact(target, profile)
|
||||
self.host.setPresence(target, profile)
|
||||
|
||||
def gatewayRegister(self, action, target, fields, profile_key='@DEFAULT@'):
|
||||
"""Register gateway using in-band registration, then log-in to gateway"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
assert(profile) #FIXME: return an error here
|
||||
if action == 'SUBMIT':
|
||||
self.host.plugins["XEP_0077"].addTrigger(target, self.registrationSuccessful, profile)
|
||||
return self.host.plugins["XEP_0077"].in_band_submit(action, target, fields, profile)
|
||||
|
||||
def findGateways(self, target, profile_key='@DEFAULT@'):
|
||||
"""Find gateways in the target JID, using discovery protocol
|
||||
Return an id used for retrieving the list of gateways
|
||||
"""
|
||||
profile = self.host.memory.getProfileName(profile_key)
|
||||
client = self.host.getClient(profile_key)
|
||||
assert(client)
|
||||
to_jid = jid.JID(target)
|
||||
debug (_("find gateways (target = %(target)s, profile = %(profile)s)") % {'target':to_jid.full(), 'profile':profile})
|
||||
request_id = self.host.get_next_id()
|
||||
client.disco.requestItems(to_jid).addCallback(self.discoItems, request_id=request_id, target = to_jid, client = client).addErrback(self.discoItemsErr, request_id=request_id, target = to_jid, client = client)
|
||||
return request_id
|
||||
Loading…
Reference in a new issue