Mercurial > libervia-backend
annotate plugins/plugin_misc_cs.py @ 110:cb904fa7de3c
jp: profile management (new option: --profile)
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 29 Jun 2010 15:45:11 +0800 |
parents | 18b0cf49a6f1 |
children | 5c68a65548c3 |
rev | line source |
---|---|
101 | 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 import random | |
32 | |
33 from zope.interface import implements | |
34 | |
35 from wokkel import disco, iwokkel, data_form | |
106 | 36 from tools.xml_tools import XMLUI |
101 | 37 import urllib |
108
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
38 import webbrowser |
101 | 39 |
102 | 40 from BeautifulSoup import BeautifulSoup |
106 | 41 import re |
101 | 42 |
43 | |
44 PLUGIN_INFO = { | |
45 "name": "CouchSurfing plugin", | |
46 "import_name": "CS", | |
47 "type": "Misc", | |
48 "protocols": [], | |
49 "dependencies": [], | |
50 "main": "CS_Plugin", | |
51 "handler": "no", | |
52 "description": _(u"""This plugin allow to manage your CouchSurfing account throught your SàT frontend""") | |
53 } | |
54 | |
55 AGENT = 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.3) Gecko/20100423 Ubuntu/10.04 (lucid) Firefox/3.6.3' | |
56 | |
57 class CS_Plugin(): | |
58 | |
59 params = """ | |
60 <params> | |
61 <individual> | |
62 <category name="CouchSurfing"> | |
63 <param name="Login" type="string" /> | |
64 <param name="Password" type="password" /> | |
65 </category> | |
66 </individual> | |
67 </params> | |
68 """ | |
69 | |
70 def __init__(self, host): | |
71 info(_("Plugin CS initialization")) | |
72 self.host = host | |
73 #parameters | |
74 host.memory.importParams(CS_Plugin.params) | |
75 #menu | |
76 host.importMenu(_("Plugin"), "CouchSurfing", self.menuSelected, help_string = _("Launch CoushSurfing mangement interface")) | |
106 | 77 self.data=self.host.memory.getPrivate('plugin_cs_data') or {} #TODO: delete cookies/data after a while |
107 | 78 self.host.registerGeneralCB("plugin_CS_sendMessage", self.sendMessage) |
108
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
79 self.host.registerGeneralCB("plugin_CS_showUnreadMessages", self.showUnreadMessages) |
102 | 80 |
81 def erroCB(self, e, id): | |
82 """Called when something is going wrong when contacting CS website""" | |
107 | 83 #pdb.set_trace() |
102 | 84 message_data={"reason": "connection error", "message":_(u"Impossible to contact CS website, please check your login/password, connection or try again later")} |
85 self.host.bridge.actionResult("ERROR", id, message_data) | |
86 | |
101 | 87 def menuSelected(self, id, profile): |
88 """Called when the couchsurfing menu item is selected""" | |
89 login = self.host.memory.getParamA("Login", "CouchSurfing", profile_key=profile) | |
90 password = self.host.memory.getParamA("Password", "CouchSurfing", profile_key=profile) | |
91 if not login or not password: | |
92 message_data={"reason": "uncomplete", "message":_(u"You have to fill your CouchSurfing login & password in parameters before using this interface")} | |
93 self.host.bridge.actionResult("ERROR", id, message_data) | |
94 return | |
95 | |
96 post_data = urllib.urlencode({'auth_login[un]':login,'auth_login[pw]':password,'auth_login[action]':'Login...'}) | |
97 | |
106 | 98 if not self.data.has_key(profile): |
99 self.data[profile] = {'cookies':{}} | |
100 else: | |
101 self.data[profile]['cookies'] = {} | |
101 | 102 |
103 | |
104 | |
105 | |
106 | 106 |
102 | 107 #tmp |
107 | 108 """f = open('/home/goffi/tmp/CS_principale.html','r') |
102 | 109 html = f.read() |
107 | 110 self.__connectionCB(html, id, profile)""" |
102 | 111 |
107 | 112 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']) |
106 | 113 d.addCallback(self.__connectionCB, id, profile) |
107 | 114 d.addErrback(self.erroCB, id) |
102 | 115 |
116 | |
106 | 117 #self.host.bridge.actionResult("SUPPRESS", id, {}) |
118 | |
119 | |
120 #pages parsing callbacks | |
121 def savePage(self, name, html): | |
122 f = open ('/home/goffi/tmp/CS_'+name+'.html','w') | |
123 f.write(html) | |
124 f.close() | |
125 print "page [%s] sauvee" % name | |
126 #pdb.set_trace() | |
127 | |
128 def __connectionCB(self, html, id, profile): | |
129 print 'Response received' | |
130 self.savePage('principale',html) | |
131 soup = BeautifulSoup(html) | |
132 self.data[profile]['user_nick'] = soup.find('a','item_link',href='/home.html').contents[0] | |
133 self.data[profile]['user_name'] = soup.html.head.title.string.split(' - ')[1] | |
134 #unread messages | |
135 try: | |
136 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)) | |
137 except: | |
138 self.data[profile]['unread_messages'] = 0 | |
139 #unread couchrequest messages | |
140 try: | |
141 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)) | |
142 except: | |
143 self.data[profile]['unread_CR_messages'] = 0 | |
144 | |
145 #if we have already the list of friend, no need to make new requests | |
146 if not self.data[profile].has_key('friends'): | |
147 self.data[profile]['friends'] = {} | |
148 """f = open('/home/goffi/tmp/CS_friends.html','r') | |
149 html = f.read() | |
150 self.__friendsPageCB(html, id, profile)""" | |
151 d = getPage('http://www.couchsurfing.org/connections.html?type=myfriends&show=10000', agent=AGENT, cookies=self.data[profile]['cookies']) | |
152 d.addCallback(self.__friendsPageCB, id=id, profile=profile) | |
153 d.addErrback(self.erroCB, id) | |
154 else: | |
155 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])}) | |
156 | |
157 def __buildUI(self, data): | |
158 """Build the XML UI of the plugin | |
159 @param data: data store for the profile""" | |
160 user_nick = data['user_nick'] | |
161 user_name = data['user_name'] | |
162 unread_mess = data['unread_messages'] | |
163 unread_CR_mess = data['unread_CR_messages'] | |
164 friends_list = data['friends'].keys() | |
165 friends_list.sort() | |
107 | 166 interface = XMLUI('window','tabs', title='CouchSurfing management') |
106 | 167 interface.addCategory(_("Messages"), "vertical") |
107 | 168 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 ''}) |
108
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
169 if unread_mess: |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
170 interface.addButton('plugin_CS_showUnreadMessages', 'showUnreadMessages', _('Show unread message%(plural)s in external web browser') % {'plural':'s' if unread_mess>1 else ''}) |
107 | 171 interface.addList(friends_list, 'friends', style=['multi']) |
172 interface.changeLayout('pairs') | |
173 interface.addLabel(_("Subject")) | |
174 interface.addString('subject') | |
175 interface.changeLayout('vertical') | |
176 interface.addLabel(_("Message")) | |
177 interface.addText("(use %name% for contact name and %firstname% for guessed first name)") | |
178 interface.addTextBox('message') | |
179 interface.addButton('plugin_CS_sendMessage', 'sendMessage', _('send'), fields_back=['friends','subject','message']) | |
109
18b0cf49a6f1
Plugin CS minor changes (Events & Couch Search tabs temporarly removed, open_new_tab user to open link in browser)
Goffi <goffi@goffi.org>
parents:
108
diff
changeset
|
180 #interface.addCategory(_("Events"), "vertical") #TODO: coming soon, hopefuly :) |
18b0cf49a6f1
Plugin CS minor changes (Events & Couch Search tabs temporarly removed, open_new_tab user to open link in browser)
Goffi <goffi@goffi.org>
parents:
108
diff
changeset
|
181 #interface.addCategory(_("Couch search"), "vertical") |
106 | 182 return interface.toXml() |
183 | |
184 def __meetingPageCB(self, html): | |
185 """Called when the meeting page has been received""" | |
186 | |
187 def __friendsPageCB(self, html, id, profile): | |
188 """Called when the friends list page has been received""" | |
189 self.savePage('friends',html) | |
107 | 190 soup = BeautifulSoup(html.replace('"formtable width="400','"formtable" width="400"')) #CS html fix #TODO: report the bug to CS dev team |
106 | 191 friends = self.data[profile]['friends'] |
192 for _tr in soup.findAll('tr', {'class':re.compile("^msgRow*")}): #we parse the row with friends infos | |
193 _nobr = _tr.find('nobr') #contain the friend name | |
194 friend_name = unicode(_nobr.string) | |
195 friend_link = u'http://www.couchsurfing.org'+_nobr.parent['href'] | |
196 regex_href = re.compile(r'/connections\.html\?id=([^&]+)') | |
197 a_tag = _tr.find('a',href=regex_href) | |
198 friend_id = regex_href.search(unicode(a_tag)).groups()[0] | |
199 | |
200 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}) | |
201 friends[friend_name] = {'link':friend_link,'id':friend_id} | |
202 a = soup.find('td','barmiddle next').a #is there several pages ? | |
203 if a: | |
204 #yes, we parse the next page | |
205 d = getPage('http://www.couchsurfing.org/'+str(a['href']), agent=AGENT, cookies=self.data[profile]['cookies']) | |
206 d.addCallback(self.__friendsPageCB, id=id, profile=profile) | |
207 d.addErrback(self.erroCB, id) | |
208 else: | |
209 #no, we show the result | |
210 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":self.__buildUI(self.data[profile])}) | |
211 #and save the data | |
212 self.host.memory.setPrivate('plugin_cs_data', self.data) | |
107 | 213 |
214 def __sendMessage(self, answer, subject, message, data, recipient_list, id, profile): | |
215 """Send actually the message | |
216 @param subject: subject of the message | |
217 @param message: body of the message | |
218 @param data: data of the profile | |
219 @param recipient_list: list of friends names, names are removed once message is sent | |
220 @param id: id of the action | |
221 @param profile: profile who launched the action | |
222 """ | |
223 if answer: | |
224 if not 'Here is a copy of the email that was sent' in answer: | |
225 error(_("INTERNAL ERROR: no confirmation of message sent by CS, maybe the site has been modified ?")) | |
226 #TODO: throw a warning to the frontend, saying that maybe the message has not been sent and to contact dev of this plugin | |
227 #debug(_('HTML answer: %s') % answer) | |
228 if recipient_list: | |
229 recipient = recipient_list.pop() | |
230 try: | |
231 friend_id = data['friends'][recipient]['id'] | |
232 except KeyError: | |
233 error('INTERNAL ERROR: unknown friend') | |
234 return #send an error to the frontend | |
235 mess = message.replace('%name%',recipient).replace('%firstname%',recipient.split(' ')[0]) | |
236 info(_('Sending message to %s') % recipient) | |
237 debug(_("\nsubject: %(subject)s\nmessage: \n---\n%(message)s\n---\n\n") % {'subject':subject,'message':mess}) | |
238 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]':''}) | |
239 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']) | |
240 d.addCallback(self.__sendMessage, subject, message, data, recipient_list, id, profile) | |
241 d.addErrback(self.erroCB, id) | |
242 else: | |
243 interface = XMLUI('window', title=_('Message sent')) #TODO: create particular actionResult for alerts ? | |
244 interface.addText(_('The message has been sent to every recipients')) | |
245 self.host.bridge.actionResult("XMLUI", id, {"type":"window", "xml":interface.toXml()}) | |
102 | 246 |
107 | 247 def sendMessage(self, id, data, profile): |
248 """Called to send a message to a friend | |
249 @param data: dict with the following keys: | |
250 friend: name of the recipient | |
251 subject: subject of the message | |
252 message: body of the message, with the following special keywords: | |
253 - %name%: name of the friend | |
254 - %firstname%: guessed first name of the friend (currently the first part of the name) | |
255 """ | |
256 if not data['friends']: | |
257 message_data={"reason": "bad data", "message":_(u"There is not recipient selected for this message !")} | |
258 self.host.bridge.actionResult("ERROR", id, message_data) | |
259 return | |
260 friends = data['friends'].split('\t') | |
261 subject = data['subject'] | |
262 message = data['message'] | |
263 print "send message \o/ :) :) :)" | |
264 info(_("sending message to %(friends)s with subject [%(subject)s]" % {'friends':friends, 'subject':subject})) | |
265 self.__sendMessage(None, subject, message, self.data[profile], friends, id, profile) | |
108
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
266 |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
267 def __showUnreadMessages2(self, html, id, profile): |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
268 """Called when the inbox page has been received""" |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
269 #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. |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
270 soup = BeautifulSoup(html) |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
271 for tag in soup.findAll(lambda tag: tag.name=='strong' and tag.a and tag.a['href'].startswith('messages.html?message_status=inbox')): |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
272 link = "http://www.couchsurfing.org/"+str(tag.a['href']) |
109
18b0cf49a6f1
Plugin CS minor changes (Events & Couch Search tabs temporarly removed, open_new_tab user to open link in browser)
Goffi <goffi@goffi.org>
parents:
108
diff
changeset
|
273 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 |
108
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
274 |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
275 def showUnreadMessages(self, id, data, profile): |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
276 """Called when user want to see all unread messages in the external browser""" |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
277 d = getPage("http://www.couchsurfing.org/messages.html?message_status=inbox&show=10000", agent=AGENT, cookies=self.data[profile]['cookies']) |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
278 d.addCallback(self.__showUnreadMessages2, id, profile) |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
279 d.addErrback(self.erroCB, id) |
e24e080e6b16
CS plugin: unread messages can now be openned in external web browser
Goffi <goffi@goffi.org>
parents:
107
diff
changeset
|
280 |