Mercurial > libervia-backend
comparison src/plugins/plugin_misc_cs.py @ 223:86d249b6d9b7
Files reorganisation
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 29 Dec 2010 01:06:29 +0100 |
parents | plugins/plugin_misc_cs.py@782319a64ac6 |
children | b1794cbb88e5 |
comparison
equal
deleted
inserted
replaced
222:3198bfd66daa | 223:86d249b6d9b7 |
---|---|
1 #!/usr/bin/python | |
2 # -*- coding: utf-8 -*- | |
3 | |
4 """ | |
5 SAT plugin for managing xep-0045 | |
6 Copyright (C) 2009, 2010 Jérôme Poisson (goffi@goffi.org) | |
7 | |
8 This program is free software: you can redistribute it and/or modify | |
9 it under the terms of the GNU General Public License as published by | |
10 the Free Software Foundation, either version 3 of the License, or | |
11 (at your option) any later version. | |
12 | |
13 This program is distributed in the hope that it will be useful, | |
14 but WITHOUT ANY WARRANTY; without even the implied warranty of | |
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
16 GNU General Public License for more details. | |
17 | |
18 You should have received a copy of the GNU General Public License | |
19 along with this program. If not, see <http://www.gnu.org/licenses/>. | |
20 """ | |
21 | |
22 from logging import debug, info, warning, error | |
23 from twisted.words.xish import domish | |
24 from twisted.internet import protocol, defer, threads, reactor | |
25 from twisted.words.protocols.jabber import client, jid, xmlstream | |
26 from twisted.words.protocols.jabber import error as jab_error | |
27 from twisted.words.protocols.jabber.xmlstream import IQ | |
28 from twisted.web.client import getPage | |
29 import os.path | |
30 import pdb | |
31 | |
32 from zope.interface import implements | |
33 | |
34 from wokkel import disco, iwokkel, data_form | |
35 from sat.tools.xml_tools import XMLUI | |
36 import urllib | |
37 import webbrowser | |
38 | |
39 from BeautifulSoup import BeautifulSoup | |
40 import re | |
41 | |
42 | |
43 PLUGIN_INFO = { | |
44 "name": "CouchSurfing plugin", | |
45 "import_name": "CS", | |
46 "type": "Misc", | |
47 "protocols": [], | |
48 "dependencies": [], | |
49 "main": "CS_Plugin", | |
50 "handler": "no", | |
51 "description": _(u"""This plugin allow to manage your CouchSurfing account throught your SàT frontend""") | |
52 } | |
53 | |
54 AGENT = 'Salut à Toi XMPP/CS Plugin' | |
55 | |
56 class CS_Plugin(): | |
57 | |
58 params = """ | |
59 <params> | |
60 <individual> | |
61 <category name="CouchSurfing"> | |
62 <param name="Login" type="string" /> | |
63 <param name="Password" type="password" /> | |
64 </category> | |
65 </individual> | |
66 </params> | |
67 """ | |
68 | |
69 def __init__(self, host): | |
70 info(_("Plugin CS initialization")) | |
71 self.host = host | |
72 #parameters | |
73 host.memory.importParams(CS_Plugin.params) | |
74 #menu | |
75 host.importMenu(_("Plugin"), "CouchSurfing", self.menuSelected, help_string = _("Launch CoushSurfing management interface")) | |
76 self.data=self.host.memory.getPrivate('plugin_cs_data') or {} #TODO: delete cookies/data after a while | |
77 self.host.registerGeneralCB("plugin_CS_sendMessage", self.sendMessage) | |
78 self.host.registerGeneralCB("plugin_CS_showUnreadMessages", self.showUnreadMessages) | |
79 | |
80 def erroCB(self, e, id): | |
81 """Called when something is going wrong when contacting CS website""" | |
82 #pdb.set_trace() | |
83 message_data={"reason": "connection error", "message":_(u"Impossible to contact CS website, please check your login/password, connection or try again later")} | |
84 self.host.bridge.actionResult("ERROR", id, message_data) | |
85 | |
86 def menuSelected(self, id, profile): | |
87 """Called when the couchsurfing menu item is selected""" | |
88 login = self.host.memory.getParamA("Login", "CouchSurfing", profile_key=profile) | |
89 password = self.host.memory.getParamA("Password", "CouchSurfing", profile_key=profile) | |
90 if not login or not password: | |
91 message_data={"reason": "uncomplete", "message":_(u"You have to fill your CouchSurfing login & password in parameters before using this interface")} | |
92 self.host.bridge.actionResult("ERROR", id, message_data) | |
93 return | |
94 | |
95 post_data = urllib.urlencode({'auth_login[un]':login,'auth_login[pw]':password,'auth_login[action]':'Login...'}) | |
96 | |
97 if not self.data.has_key(profile): | |
98 self.data[profile] = {'cookies':{}} | |
99 else: | |
100 self.data[profile]['cookies'] = {} | |
101 | |
102 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']) | |
103 d.addCallback(self.__connectionCB, id, profile) | |
104 d.addErrback(self.erroCB, id) | |
105 | |
106 #self.host.bridge.actionResult("SUPPRESS", id, {}) | |
107 | |
108 | |
109 #pages parsing callbacks | |
110 def savePage(self, name, html): | |
111 f = open ('/tmp/CS_'+name+'.html','w') | |
112 f.write(html) | |
113 f.close() | |
114 print "page [%s] sauvee" % name | |
115 #pdb.set_trace() | |
116 | |
117 def __connectionCB(self, html, id, profile): | |
118 print 'Response received' | |
119 #self.savePage('principale',html) | |
120 soup = BeautifulSoup(html) | |
121 self.data[profile]['user_nick'] = soup.find('a','item_link',href='/home.html').contents[0] | |
122 self.data[profile]['user_name'] = soup.html.head.title.string.split(' - ')[1] | |
123 #unread messages | |
124 try: | |
125 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)) | |
126 except: | |
127 self.data[profile]['unread_messages'] = 0 | |
128 #unread couchrequest messages | |
129 try: | |
130 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)) | |
131 except: | |
132 self.data[profile]['unread_CR_messages'] = 0 | |
133 | |
134 #if we have already the list of friend, no need to make new requests | |
135 if not self.data[profile].has_key('friends'): | |
136 self.data[profile]['friends'] = {} | |
137 d = getPage('http://www.couchsurfing.org/connections.html?type=myfriends&show=10000', agent=AGENT, cookies=self.data[profile]['cookies']) | |
138 d.addCallback(self.__friendsPageCB, id=id, profile=profile) | |
139 d.addErrback(self.erroCB, id) | |
140 else: | |
141 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])}) | |
142 | |
143 def __buildUI(self, data): | |
144 """Build the XML UI of the plugin | |
145 @param data: data store for the profile""" | |
146 user_nick = data['user_nick'] | |
147 user_name = data['user_name'] | |
148 unread_mess = data['unread_messages'] | |
149 unread_CR_mess = data['unread_CR_messages'] | |
150 friends_list = data['friends'].keys() | |
151 friends_list.sort() | |
152 interface = XMLUI('window','tabs', title='CouchSurfing management') | |
153 interface.addCategory(_("Messages"), "vertical") | |
154 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 ''}) | |
155 if unread_mess: | |
156 interface.addButton('plugin_CS_showUnreadMessages', 'showUnreadMessages', _('Show unread message%(plural)s in external web browser') % {'plural':'s' if unread_mess>1 else ''}) | |
157 interface.addList(friends_list, 'friends', style=['multi']) | |
158 interface.changeLayout('pairs') | |
159 interface.addLabel(_("Subject")) | |
160 interface.addString('subject') | |
161 interface.changeLayout('vertical') | |
162 interface.addLabel(_("Message")) | |
163 interface.addText("(use %name% for contact name and %firstname% for guessed first name)") | |
164 interface.addTextBox('message') | |
165 interface.addButton('plugin_CS_sendMessage', 'sendMessage', _('send'), fields_back=['friends','subject','message']) | |
166 #interface.addCategory(_("Events"), "vertical") #TODO: coming soon, hopefuly :) | |
167 #interface.addCategory(_("Couch search"), "vertical") | |
168 return interface.toXml() | |
169 | |
170 def __meetingPageCB(self, html): | |
171 """Called when the meeting page has been received""" | |
172 | |
173 def __friendsPageCB(self, html, id, profile): | |
174 """Called when the friends list page has been received""" | |
175 self.savePage('friends',html) | |
176 soup = BeautifulSoup(html.replace('"formtable width="400','"formtable" width="400"')) #CS html fix #TODO: report the bug to CS dev team | |
177 friends = self.data[profile]['friends'] | |
178 for _tr in soup.findAll('tr', {'class':re.compile("^msgRow*")}): #we parse the row with friends infos | |
179 _nobr = _tr.find('nobr') #contain the friend name | |
180 friend_name = unicode(_nobr.string) | |
181 friend_link = u'http://www.couchsurfing.org'+_nobr.parent['href'] | |
182 regex_href = re.compile(r'/connections\.html\?id=([^&]+)') | |
183 a_tag = _tr.find('a',href=regex_href) | |
184 friend_id = regex_href.search(unicode(a_tag)).groups()[0] | |
185 | |
186 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}) | |
187 friends[friend_name] = {'link':friend_link,'id':friend_id} | |
188 a = soup.find('td','barmiddle next').a #is there several pages ? | |
189 if a: | |
190 #yes, we parse the next page | |
191 d = getPage('http://www.couchsurfing.org/'+str(a['href']), agent=AGENT, cookies=self.data[profile]['cookies']) | |
192 d.addCallback(self.__friendsPageCB, id=id, profile=profile) | |
193 d.addErrback(self.erroCB, id) | |
194 else: | |
195 #no, we show the result | |
196 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])}) | |
197 #and save the data | |
198 self.host.memory.setPrivate('plugin_cs_data', self.data) | |
199 | |
200 def __sendMessage(self, answer, subject, message, data, recipient_list, id, profile): | |
201 """Send actually the message | |
202 @param subject: subject of the message | |
203 @param message: body of the message | |
204 @param data: data of the profile | |
205 @param recipient_list: list of friends names, names are removed once message is sent | |
206 @param id: id of the action | |
207 @param profile: profile who launched the action | |
208 """ | |
209 if answer: | |
210 if not 'Here is a copy of the email that was sent' in answer: | |
211 error(_("INTERNAL ERROR: no confirmation of message sent by CS, maybe the site has been modified ?")) | |
212 #TODO: throw a warning to the frontend, saying that maybe the message has not been sent and to contact dev of this plugin | |
213 #debug(_('HTML answer: %s') % answer) | |
214 if recipient_list: | |
215 recipient = recipient_list.pop() | |
216 try: | |
217 friend_id = data['friends'][recipient]['id'] | |
218 except KeyError: | |
219 error('INTERNAL ERROR: unknown friend') | |
220 return #send an error to the frontend | |
221 mess = message.replace('%name%',recipient).replace('%firstname%',recipient.split(' ')[0]) | |
222 info(_('Sending message to %s') % recipient) | |
223 debug(_("\nsubject: %(subject)s\nmessage: \n---\n%(message)s\n---\n\n") % {'subject':subject,'message':mess}) | |
224 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]':''}) | |
225 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']) | |
226 d.addCallback(self.__sendMessage, subject, message, data, recipient_list, id, profile) | |
227 d.addErrback(self.erroCB, id) | |
228 else: | |
229 interface = XMLUI('window', title=_('Message sent')) #TODO: create particular actionResult for alerts ? | |
230 interface.addText(_('The message has been sent to every recipients')) | |
231 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":interface.toXml()}) | |
232 | |
233 def sendMessage(self, id, data, profile): | |
234 """Called to send a message to a friend | |
235 @param data: dict with the following keys: | |
236 friend: name of the recipient | |
237 subject: subject of the message | |
238 message: body of the message, with the following special keywords: | |
239 - %name%: name of the friend | |
240 - %firstname%: guessed first name of the friend (currently the first part of the name) | |
241 """ | |
242 if not data['friends']: | |
243 message_data={"reason": "bad data", "message":_(u"There is not recipient selected for this message !")} | |
244 self.host.bridge.actionResult("ERROR", id, message_data) | |
245 return | |
246 friends = data['friends'].split('\t') | |
247 subject = data['subject'] | |
248 message = data['message'] | |
249 print "send message \o/ :) :) :)" | |
250 info(_("sending message to %(friends)s with subject [%(subject)s]" % {'friends':friends, 'subject':subject})) | |
251 self.__sendMessage(None, subject, message, self.data[profile], friends, id, profile) | |
252 | |
253 def __showUnreadMessages2(self, html, id, profile): | |
254 """Called when the inbox page has been received""" | |
255 #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. | |
256 soup = BeautifulSoup(html) | |
257 for tag in soup.findAll(lambda tag: tag.name=='strong' and tag.a and tag.a['href'].startswith('messages.html?message_status=inbox')): | |
258 link = "http://www.couchsurfing.org/"+str(tag.a['href']) | |
259 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 | |
260 | |
261 def showUnreadMessages(self, id, data, profile): | |
262 """Called when user want to see all unread messages in the external browser""" | |
263 d = getPage("http://www.couchsurfing.org/messages.html?message_status=inbox&show=10000", agent=AGENT, cookies=self.data[profile]['cookies']) | |
264 d.addCallback(self.__showUnreadMessages2, id, profile) | |
265 d.addErrback(self.erroCB, id) | |
266 |