Browse Source

v2: added blessed node.js ddg search app

Tyler Hallada 7 years ago
parent
commit
51cf3bc1f8
5 changed files with 313 additions and 0 deletions
  1. 2 0
      .gitignore
  2. 41 0
      ddg.py
  3. 29 0
      package.json
  4. 10 0
      results.py
  5. 231 0
      search-pane2

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
1
+node_modules
2
+.py[co]

+ 41 - 0
ddg.py

@@ -0,0 +1,41 @@
1
+"""
2
+Taken and modified from https://github.com/thibauts/duckduckgo
3
+"""
4
+import requests
5
+from lxml import html
6
+import time
7
+
8
+def search(keywords, max_results=None):
9
+    url = 'https://duckduckgo.com/html/'
10
+    params = {
11
+        'q': keywords,
12
+        's': '0',
13
+    }
14
+    results = []
15
+
16
+    yielded = 0
17
+    while True:
18
+        res = requests.post(url, data=params)
19
+        doc = html.fromstring(res.text)
20
+
21
+        for link in doc.cssselect('#links .links_main'):
22
+            result = {}
23
+            a_elem = link.cssselect('a')[0]
24
+            desc = link.cssselect('.result__snippet')[0]
25
+            result['link'] = a_elem.get('href')
26
+            result['title'] = a_elem.text_content()
27
+            result['desc'] = desc.text_content()
28
+            results.append(result)
29
+
30
+        for result in results:
31
+            yield result
32
+            time.sleep(0.1)
33
+            yielded += 1
34
+            if max_results and yielded >= max_results:
35
+                return
36
+
37
+        try:
38
+            form = doc.cssselect('.results_links_more form')[-1]
39
+        except IndexError:
40
+            return
41
+        params = dict(form.fields)

+ 29 - 0
package.json

@@ -0,0 +1,29 @@
1
+{
2
+  "name": "search-pane",
3
+  "version": "1.0.0",
4
+  "description": "Search duckduckgo from the terminal",
5
+  "main": "seach-pane2",
6
+  "dependencies": {
7
+    "blessed": "^0.1.81",
8
+    "python-shell": "^0.4.0"
9
+  },
10
+  "devDependencies": {},
11
+  "scripts": {
12
+    "test": "echo \"Error: no test specified\" && exit 1"
13
+  },
14
+  "repository": {
15
+    "type": "git",
16
+    "url": "git@mule.hallada.net:search-pane.git"
17
+  },
18
+  "keywords": [
19
+    "blessed",
20
+    "ncurses",
21
+    "duckduckgo",
22
+    "ddg",
23
+    "search",
24
+    "terminal",
25
+    "console"
26
+  ],
27
+  "author": "Tyler Hallada",
28
+  "license": "MIT"
29
+}

+ 10 - 0
results.py

@@ -0,0 +1,10 @@
1
+import argparse
2
+import json
3
+
4
+from ddg import search
5
+
6
+parser = argparse.ArgumentParser(description='Search duckduckgo and return JSON results')
7
+parser.add_argument('query', metavar='Q', type=str,
8
+                   help='The query to search on duckduckgo')
9
+args = parser.parse_args()
10
+print(json.dumps(list(search(args.query, max_results=10))))

+ 231 - 0
search-pane2

@@ -0,0 +1,231 @@
1
+#!/usr/bin/env node
2
+var blessed = require('blessed'),
3
+    PythonShell = require('python-shell'),
4
+	screen = blessed.screen({
5
+        smartCSR: true,
6
+    }),
7
+    searchResults;
8
+
9
+screen._listenedMouse = true; // HACK HACK HACK
10
+
11
+var cleanExit = function () {
12
+    // program.clear();
13
+    // program.disableMouse();
14
+    // program.showCursor();
15
+    // program.normalBuffer();
16
+    process.exit(0);
17
+};
18
+
19
+var form = blessed.form({
20
+	parent: screen,
21
+	keys: true,
22
+	top: 'center',
23
+	left: 'center',
24
+    padding: {
25
+        left: 1,
26
+        right: 1,
27
+        top: 1,
28
+        bottom: 1
29
+    },
30
+    border: {
31
+        type: 'line',
32
+        fg: 'white',
33
+        bg: 'grey'
34
+    },
35
+	width: 'shrink',
36
+	height: 5,
37
+	bg: 'grey',
38
+	content: 'Search: '
39
+});
40
+
41
+var input = blessed.textbox({
42
+    parent: form,
43
+    name: 'input',
44
+    keys: true,
45
+    inputOnFocus: true,
46
+    height: 1,
47
+    padding: {
48
+        left: 1,
49
+        right: 1
50
+    },
51
+    top: 0,
52
+    left: 10,
53
+	style: {
54
+		bg: 'darkblue',
55
+		focus: {
56
+			bg: 'darkblue'
57
+		},
58
+		hover: {
59
+			bg: 'darkblue'
60
+		}
61
+	}
62
+});
63
+
64
+var submit = blessed.button({
65
+	parent: form,
66
+	keys: true,
67
+	shrink: true,
68
+	padding: {
69
+		left: 1,
70
+		right: 1
71
+	},
72
+	left: 10,
73
+	top: 2,
74
+	name: 'submit',
75
+	content: 'submit',
76
+	style: {
77
+		bg: 'blue',
78
+		focus: {
79
+			bg: 'white',
80
+            fg: 'blue'
81
+		},
82
+		hover: {
83
+			bg: 'white',
84
+            fg: 'blue'
85
+		}
86
+	}
87
+});
88
+
89
+var cancel = blessed.button({
90
+	parent: form,
91
+	keys: true,
92
+	shrink: true,
93
+	padding: {
94
+		left: 1,
95
+		right: 1
96
+	},
97
+	left: 20,
98
+	top: 2,
99
+	name: 'cancel',
100
+	content: 'cancel',
101
+	style: {
102
+		bg: 'red',
103
+		focus: {
104
+			bg: 'white',
105
+            fg: 'red'
106
+		},
107
+		hover: {
108
+			bg: 'white',
109
+            fg: 'red'
110
+		}
111
+	}
112
+});
113
+
114
+var loading = blessed.loading({
115
+    parent: screen,
116
+    visible: false,
117
+    hidden: true,
118
+    top: 'center',
119
+    left: 'center',
120
+    padding: {
121
+        left: 1,
122
+        right: 1,
123
+        top: 1,
124
+        bottom: 1
125
+    },
126
+    border: {
127
+        type: 'line',
128
+        fg: 'white',
129
+        bg: 'grey'
130
+    },
131
+    width: 32,
132
+    height: 7,
133
+    bg: 'grey',
134
+});
135
+
136
+var results = blessed.listtable({
137
+    parent: screen,
138
+    hidden: true,
139
+    top: 'center',
140
+    left: 'center',
141
+    padding: {
142
+        left: 1,
143
+        right: 1,
144
+        top: 1,
145
+        bottom: 1
146
+    },
147
+    border: {
148
+        type: 'line',
149
+        fg: 'white',
150
+        bg: 'grey'
151
+    },
152
+    bg: 'grey',
153
+    height: 'shrink'
154
+});
155
+
156
+var resultsList = blessed.list({
157
+    parent: screen,
158
+    hidden: true,
159
+    keys: true,
160
+    top: 'center',
161
+    left: 'center',
162
+    padding: {
163
+        left: 1,
164
+        right: 1,
165
+        top: 1,
166
+        bottom: 1
167
+    },
168
+    border: {
169
+        type: 'line',
170
+        fg: 'white',
171
+        bg: 'grey'
172
+    },
173
+    selectedBg: 'darkred',
174
+    selectedFg: 'yellow',
175
+    bg: 'grey',
176
+    scollbar: true
177
+});
178
+
179
+submit.on('press', function() {
180
+	form.submit();
181
+});
182
+
183
+cancel.on('press', function() {
184
+	form.reset();
185
+});
186
+
187
+input.on('submit', function(data) {
188
+    form.submit();
189
+});
190
+
191
+form.on('submit', function(data) {
192
+    form.hide();
193
+    PythonShell.run('results.py', {args: [data.input], mode: 'json'},
194
+                    function (err, results) {
195
+        loading.stop();
196
+        if (err) throw err;
197
+        searchResults = results[0];
198
+        var list = results[0].map(function(r) {
199
+            return r.title;
200
+        });
201
+        resultsList.setItems(list);
202
+        resultsList.show();
203
+        resultsList.focus();
204
+        screen.render();
205
+    });
206
+    loading.load('Searching...');
207
+    loading._.icon.style = {bg: 'grey'}; // Hacky, loading icon doesn't support styling via options
208
+});
209
+
210
+form.on('reset', function(data) {
211
+	form.setContent('Canceled.');
212
+    cleanExit();
213
+});
214
+
215
+resultsList.on('select', function(data) {
216
+    var i = resultsList.getItemIndex(data);
217
+    resultsList.hide();
218
+    var browser = screen.spawn('w3m', [searchResults[i].link], {stdio: 'inherit'});
219
+    browser.on('exit', function() {
220
+        resultsList.show();
221
+        resultsList.focus();
222
+        screen.render();
223
+    });
224
+});
225
+
226
+screen.key('q', function() {
227
+    cleanExit();
228
+});
229
+
230
+screen.render();
231
+input.focus();