Merge lp:~cmulk/cardapio/duckduckgo-plugin into lp:cardapio

Proposed by cmulk
Status: Merged
Merge reported by: Thiago Teixeira
Merged at revision: not available
Proposed branch: lp:~cmulk/cardapio/duckduckgo-plugin
Merge into: lp:cardapio
Diff against target: 253 lines (+249/-0)
1 file modified
cardapio/src/plugins/duckduck.py (+249/-0)
To merge this branch: bzr merge lp:~cmulk/cardapio/duckduckgo-plugin
Reviewer Review Type Date Requested Status
Thiago Teixeira Approve
Review via email: mp+56062@code.launchpad.net

Description of the change

Added DuckDuckGo search plugin

To post a comment you must log in.
Revision history for this message
Thiago Teixeira (tvst) wrote :

Nice! I will review this and add it to the PPA tonight :)

Revision history for this message
Thiago Teixeira (tvst) wrote :

Looks good!

Regarding your comment about the result_limit variable: that variable can't be set in the constructor because sometimes it changes after the plugin has been loaded. For instance, when doing a keyword search, result_limit changes from 4 to 20 (those are default values).

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'cardapio/src/plugins/duckduck.py'
--- cardapio/src/plugins/duckduck.py 1970-01-01 00:00:00 +0000
+++ cardapio/src/plugins/duckduck.py 2011-04-03 00:40:56 +0000
@@ -0,0 +1,249 @@
1#
2# Copyright (C) 2010 Cardapio Team (tvst@hotmail.com)
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16#
17
18
19## Duck Duck Go search plugin by Clifton Mulkey
20## Adapted from the Google Search plugin
21class CardapioPlugin(CardapioPluginInterface):
22
23 author = _('Cardapio Team')
24 name = _('DuckDuckGo')
25 description = _('Perform quick DuckDuckGo searches')
26
27 url = ''
28 help_text = ''
29 version = '1.0'
30
31 plugin_api_version = 1.40
32
33 search_delay_type = 'remote'
34
35 default_keyword = 'duck'
36
37 category_name = _('DuckDuckGo Results')
38 category_icon = 'system-search'
39 icon = 'system-search'
40 category_tooltip = _('Results found with DuckDuckGo')
41 hide_from_sidebar = True
42
43
44 def __init__(self, cardapio_proxy, category):
45
46 self.c = cardapio_proxy
47
48 try:
49 from gio import File, Cancellable
50 from urllib2 import quote
51 from simplejson import loads
52 from locale import getdefaultlocale
53 from glib import GError
54
55 except Exception, exception:
56 self.c.write_to_log(self, 'Could not import certain modules', is_error = True)
57 self.c.write_to_log(self, exception, is_error = True)
58 self.loaded = False
59 return
60
61 self.File = File
62 self.Cancellable = Cancellable
63 self.quote = quote
64 self.loads = loads
65 self.getdefaultlocale = getdefaultlocale
66 self.GError = GError
67
68 self.query_url = r'http://www.duckduckgo.com/?q={0}&o=json'
69
70 self.search_controller = self.Cancellable()
71
72 self.action_command = "xdg-open 'http://duckduckgo.com/?q=%s'"
73 self.action = {
74 'name' : _('Show additional results'),
75 'tooltip' : _('Show additional search results in your web browser'),
76 'icon name' : 'system-search',
77 'type' : 'callback',
78 'command' : self.more_results_action,
79 'context menu' : None,
80 }
81
82
83
84 self.loaded = True
85
86
87 def search(self, text, result_limit):
88
89 # TODO: I'm sure this is not the best way of doing remote procedure
90 # calls, but I can't seem to find anything that is this easy to use and
91 # compatible with gtk. Argh :(
92
93 # TODO: we should really check if there's an internet connection before
94 # proceeding...
95
96 self.current_query = text
97 text = self.quote(text)
98
99 # Is there a way to get the result_limit in the init method
100 # so we don't have to assign it everytime search is called?
101 self.result_limit = result_limit
102
103 query = self.query_url.format(text)
104
105 self.stream = self.File(query)
106
107 self.search_controller.reset()
108 self.stream.load_contents_async(self.handle_search_result, cancellable = self.search_controller)
109
110
111 def cancel(self):
112
113 if not self.search_controller.is_cancelled():
114 self.search_controller.cancel()
115
116
117 def handle_search_result(self, gdaemonfile = None, response = None):
118 # This function parses the results from the query
119 # The results returned from DDG are a little convoluted
120 # so we have to check for many different types of results here
121
122 result_count = 0;
123
124 try:
125 response = self.stream.load_contents_finish(response)[0]
126
127 except self.GError, e:
128 # no need to worry if there's no response: maybe there's no internet
129 # connection...
130 self.c.handle_search_error(self, 'no response')
131 return
132
133 raw_results = self.loads(response)
134
135 # print raw_results
136 parsed_results = []
137
138 if 'Error' in raw_results:
139 self.c.handle_search_error(self, raw_results['Error'])
140 return
141
142 # check for an abstract section
143 try:
144 if raw_results['Abstract']:
145 item = {
146 'name' : raw_results['Heading'],
147 'tooltip' : '(%s) %s' % (raw_results['AbstractSource'], raw_results['AbstractText']),
148 'icon name' : 'text-html',
149 'type' : 'xdg',
150 'command' : raw_results['AbstractURL'],
151 'context menu' : None,
152 }
153 parsed_results.append(item)
154 result_count += 1
155 except KeyError:
156 pass
157
158 # check for a definition section
159 try:
160 if raw_results['Definition']:
161 item = {
162 'name' : '%s (Definition)' % raw_results['Heading'],
163 'tooltip' : '(%s) %s' % (raw_results['DefinitionSource'], raw_results['Definition']),
164 'icon name' : 'text-html',
165 'type' : 'xdg',
166 'command' : raw_results['DefinitionURL'],
167 'context menu' : None,
168 }
169 parsed_results.append(item)
170 result_count += 1
171 except KeyError:
172 pass
173
174 # check for a related topics section
175 try:
176 if raw_results['RelatedTopics']:
177 for raw_result in raw_results['RelatedTopics']:
178 if result_count >= self.result_limit: break
179
180 #some related topics have a 'Topics' sub list
181 try:
182 for result in raw_result['Topics']:
183 if result_count >= self.result_limit: break
184
185 item = {
186 'name' : result['Text'],
187 'tooltip' : result['FirstURL'],
188 'icon name' : 'text-html',
189 'type' : 'xdg',
190 'command' : result['FirstURL'],
191 'context menu' : None,
192 }
193 parsed_results.append(item)
194 result_count += 1
195 except KeyError:
196 #otherwise the RelatedTopic is a single entry
197 item = {
198 'name' : raw_result['Text'],
199 'tooltip' : raw_result['FirstURL'],
200 'icon name' : 'text-html',
201 'type' : 'xdg',
202 'command' : raw_result['FirstURL'],
203 'context menu' : None,
204 }
205 parsed_results.append(item)
206 result_count += 1
207 except KeyError:
208 pass
209
210 # check for external results section
211 try:
212 if raw_results['Results']:
213 for raw_result in raw_results['Results']:
214 if result_count >= self.result_limit: break
215
216 item = {
217 'name' : raw_result['Text'],
218 'tooltip' : raw_result['FirstURL'],
219 'icon name' : 'text-html',
220 'type' : 'xdg',
221 'command' : raw_result['FirstURL'],
222 'context menu' : None,
223 }
224 parsed_results.append(item)
225 result_count += 1
226
227 except KeyError:
228 pass
229
230
231
232 if parsed_results:
233 parsed_results.append(self.action)
234
235 self.c.handle_search_result(self, parsed_results, self.current_query)
236
237
238 def more_results_action(self, text):
239
240 text = text.replace("'", r"\'")
241 text = text.replace('"', r'\"')
242
243 try:
244 subprocess.Popen(self.action_command % text, shell = True)
245 except OSError, e:
246 self.c.write_to_log(self, 'Error launching plugin action.', is_error = True)
247 self.c.write_to_log(self, e, is_error = True)
248
249

Subscribers

People subscribed via source and target branches