├── .gitignore ├── LICENSE ├── README.md ├── icon.png ├── info.plist ├── package.json ├── query.js └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2022 Jongwook Choi 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Alfred arXiv Workflow 2 | ===================== 3 | 4 | Alfred 3-5 workflow to search via [arXiv search API](https://arxiv.org/help/api/index). 5 | 6 |

7 | arXiv workflow 8 |

9 | 10 | Usage: `arxiv ` 11 | 12 | 13 | Install 14 | ------- 15 | 16 | From v2.0, the workflow is written in the [alfy](https://github.com/sindresorhus/alfy/) framework, so you can install via `npm`. 17 | 18 | ``` 19 | npm install --global alfred-arxiv 20 | ``` 21 | 22 | Requires Node.js >= 14.0 (ESM). 23 | 24 | 25 | Authors 26 | ------- 27 | 28 | Jongwook Choi (@wookayin) 29 | 30 | 31 | License 32 | ------- 33 | 34 | [The MIT License](LICENSE) 35 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wookayin/alfred-arxiv-workflow/4da9ff46b3f2426edf954c1cfb468bfaea1f818c/icon.png -------------------------------------------------------------------------------- /info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | bundleid 6 | kr.wook.alfred-arxiv-workflow 7 | connections 8 | 9 | 04FAE5E3-4E3A-4C45-99DA-5A0A93AF188F 10 | 11 | 12 | destinationuid 13 | ABF44A93-47F8-4995-A8FB-8F972096FFAE 14 | modifiers 15 | 0 16 | modifiersubtext 17 | 18 | vitoclose 19 | 20 | 21 | 22 | destinationuid 23 | F0044CEE-2197-48CE-B31B-244C668438E9 24 | modifiers 25 | 0 26 | modifiersubtext 27 | 28 | vitoclose 29 | 30 | 31 | 32 | 684A5219-C08B-478D-9D1B-91771318AE81 33 | 34 | 35 | destinationuid 36 | 8BCEAD2E-4EC1-463D-A2AC-A2724E500902 37 | modifiers 38 | 0 39 | modifiersubtext 40 | 41 | vitoclose 42 | 43 | 44 | 45 | destinationuid 46 | B513E510-DDDB-4D68-A4DA-4AD0BED36DDD 47 | modifiers 48 | 524288 49 | modifiersubtext 50 | 51 | vitoclose 52 | 53 | 54 | 55 | destinationuid 56 | 04FAE5E3-4E3A-4C45-99DA-5A0A93AF188F 57 | modifiers 58 | 1048576 59 | modifiersubtext 60 | 61 | vitoclose 62 | 63 | 64 | 65 | destinationuid 66 | 79BC9E3D-DE43-457F-8DEA-8CCBE9993BC0 67 | modifiers 68 | 131072 69 | modifiersubtext 70 | 71 | vitoclose 72 | 73 | 74 | 75 | destinationuid 76 | CB0E287E-52F8-4B90-9F00-4DAA9175BCE9 77 | modifiers 78 | 262144 79 | modifiersubtext 80 | 81 | vitoclose 82 | 83 | 84 | 85 | 79BC9E3D-DE43-457F-8DEA-8CCBE9993BC0 86 | 87 | 88 | destinationuid 89 | ABF44A93-47F8-4995-A8FB-8F972096FFAE 90 | modifiers 91 | 0 92 | modifiersubtext 93 | 94 | vitoclose 95 | 96 | 97 | 98 | destinationuid 99 | F0044CEE-2197-48CE-B31B-244C668438E9 100 | modifiers 101 | 0 102 | modifiersubtext 103 | 104 | vitoclose 105 | 106 | 107 | 108 | CB0E287E-52F8-4B90-9F00-4DAA9175BCE9 109 | 110 | 111 | destinationuid 112 | ABF44A93-47F8-4995-A8FB-8F972096FFAE 113 | modifiers 114 | 0 115 | modifiersubtext 116 | 117 | vitoclose 118 | 119 | 120 | 121 | destinationuid 122 | 332C70B0-95E7-4908-93CA-F96D15A0E740 123 | modifiers 124 | 0 125 | modifiersubtext 126 | 127 | vitoclose 128 | 129 | 130 | 131 | 132 | createdby 133 | Jongwook Choi 134 | description 135 | Search arxiv.org 136 | disabled 137 | 138 | name 139 | arXiv Search 140 | objects 141 | 142 | 143 | config 144 | 145 | browser 146 | 147 | skipqueryencode 148 | 149 | skipvarencode 150 | 151 | spaces 152 | 153 | url 154 | 155 | 156 | type 157 | alfred.workflow.action.openurl 158 | uid 159 | 8BCEAD2E-4EC1-463D-A2AC-A2724E500902 160 | version 161 | 1 162 | 163 | 164 | config 165 | 166 | browser 167 | 168 | skipqueryencode 169 | 170 | skipvarencode 171 | 172 | spaces 173 | 174 | url 175 | 176 | 177 | type 178 | alfred.workflow.action.openurl 179 | uid 180 | B513E510-DDDB-4D68-A4DA-4AD0BED36DDD 181 | version 182 | 1 183 | 184 | 185 | config 186 | 187 | alfredfiltersresults 188 | 189 | alfredfiltersresultsmatchmode 190 | 0 191 | argumenttreatemptyqueryasnil 192 | 193 | argumenttrimmode 194 | 0 195 | argumenttype 196 | 0 197 | escaping 198 | 68 199 | keyword 200 | arxiv 201 | queuedelaycustom 202 | 3 203 | queuedelayimmediatelyinitially 204 | 205 | queuedelaymode 206 | 0 207 | queuemode 208 | 2 209 | runningsubtext 210 | Searching... 211 | script 212 | ./node_modules/.bin/run-node query.js "$1" 213 | scriptargtype 214 | 1 215 | scriptfile 216 | 217 | subtext 218 | Keyword: arxiv 219 | title 220 | Search 221 | type 222 | 0 223 | withspace 224 | 225 | 226 | type 227 | alfred.workflow.input.scriptfilter 228 | uid 229 | 684A5219-C08B-478D-9D1B-91771318AE81 230 | version 231 | 3 232 | 233 | 234 | config 235 | 236 | concurrently 237 | 238 | escaping 239 | 102 240 | script 241 | # Pass through the {query} 242 | 243 | query=$1 244 | echo -n $query 245 | scriptargtype 246 | 1 247 | scriptfile 248 | 249 | type 250 | 0 251 | 252 | type 253 | alfred.workflow.action.script 254 | uid 255 | 04FAE5E3-4E3A-4C45-99DA-5A0A93AF188F 256 | version 257 | 2 258 | 259 | 260 | config 261 | 262 | lastpathcomponent 263 | 264 | onlyshowifquerypopulated 265 | 266 | removeextension 267 | 268 | text 269 | {query} 270 | title 271 | Copied into the clipboard 272 | 273 | type 274 | alfred.workflow.output.notification 275 | uid 276 | ABF44A93-47F8-4995-A8FB-8F972096FFAE 277 | version 278 | 1 279 | 280 | 281 | config 282 | 283 | autopaste 284 | 285 | clipboardtext 286 | {query} 287 | ignoredynamicplaceholders 288 | 289 | transient 290 | 291 | 292 | type 293 | alfred.workflow.output.clipboard 294 | uid 295 | F0044CEE-2197-48CE-B31B-244C668438E9 296 | version 297 | 3 298 | 299 | 300 | config 301 | 302 | concurrently 303 | 304 | escaping 305 | 102 306 | script 307 | # Pass through the {query} 308 | 309 | query=$1 310 | echo -n $query 311 | scriptargtype 312 | 1 313 | scriptfile 314 | 315 | type 316 | 0 317 | 318 | type 319 | alfred.workflow.action.script 320 | uid 321 | 79BC9E3D-DE43-457F-8DEA-8CCBE9993BC0 322 | version 323 | 2 324 | 325 | 326 | config 327 | 328 | concurrently 329 | 330 | escaping 331 | 102 332 | script 333 | # Pass through the {query} 334 | 335 | echo -n "$1" 336 | scriptargtype 337 | 1 338 | scriptfile 339 | 340 | type 341 | 0 342 | 343 | type 344 | alfred.workflow.action.script 345 | uid 346 | CB0E287E-52F8-4B90-9F00-4DAA9175BCE9 347 | version 348 | 2 349 | 350 | 351 | config 352 | 353 | autopaste 354 | 355 | clipboardtext 356 | {query} 357 | ignoredynamicplaceholders 358 | 359 | transient 360 | 361 | 362 | type 363 | alfred.workflow.output.clipboard 364 | uid 365 | 332C70B0-95E7-4908-93CA-F96D15A0E740 366 | version 367 | 3 368 | 369 | 370 | readme 371 | 372 | uidata 373 | 374 | 04FAE5E3-4E3A-4C45-99DA-5A0A93AF188F 375 | 376 | note 377 | Copy arXiv abs URL 378 | xpos 379 | 400 380 | ypos 381 | 280 382 | 383 | 332C70B0-95E7-4908-93CA-F96D15A0E740 384 | 385 | xpos 386 | 645 387 | ypos 388 | 575 389 | 390 | 684A5219-C08B-478D-9D1B-91771318AE81 391 | 392 | xpos 393 | 120 394 | ypos 395 | 200 396 | 397 | 79BC9E3D-DE43-457F-8DEA-8CCBE9993BC0 398 | 399 | note 400 | Copy arXiv identifier 401 | xpos 402 | 400 403 | ypos 404 | 430 405 | 406 | 8BCEAD2E-4EC1-463D-A2AC-A2724E500902 407 | 408 | note 409 | Open abs page 410 | xpos 411 | 400 412 | ypos 413 | 15 414 | 415 | ABF44A93-47F8-4995-A8FB-8F972096FFAE 416 | 417 | xpos 418 | 640 419 | ypos 420 | 320 421 | 422 | B513E510-DDDB-4D68-A4DA-4AD0BED36DDD 423 | 424 | note 425 | Open PDF directly 426 | xpos 427 | 400 428 | ypos 429 | 145 430 | 431 | CB0E287E-52F8-4B90-9F00-4DAA9175BCE9 432 | 433 | note 434 | Copy BibTeX 435 | xpos 436 | 400 437 | ypos 438 | 570 439 | 440 | F0044CEE-2197-48CE-B31B-244C668438E9 441 | 442 | xpos 443 | 640 444 | ypos 445 | 430 446 | 447 | 448 | userconfigurationconfig 449 | 450 | version 451 | 2.1 452 | webaddress 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "alfred-arxiv", 3 | "version": "2.1.0", 4 | "description": "Alfred workflow to search arXiv.org", 5 | "author": { 6 | "name": "Jongwook Choi", 7 | "url": "https://github.com/wookayin", 8 | "email": "wookayin@gmail.com" 9 | }, 10 | "homepage": "https://github.com/wookayin/alfred-arxiv-workflow", 11 | "main": "index.js", 12 | "type": "module", 13 | "license": "MIT", 14 | "dependencies": { 15 | "alfred-npms": "^2.1.0", 16 | "alfy": "^0.12.1", 17 | "arxiv-api": "^1.1.1" 18 | }, 19 | "scripts": { 20 | "postinstall": "alfy-init", 21 | "preuninstall": "alfy-cleanup" 22 | }, 23 | "keywords": [ 24 | "Alfred", 25 | "Alfred workflow", 26 | "alfy", 27 | "arxiv" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /query.js: -------------------------------------------------------------------------------- 1 | 2 | import alfy from 'alfy'; 3 | import arxiv from 'arxiv-api'; 4 | 5 | const M = {}; 6 | 7 | M.query_arxiv = async function(input) { 8 | // query arxiv API. 9 | input = input.replace('-', ' '); 10 | var papers = await arxiv.search({ 11 | searchQueryParams: [ 12 | { 13 | include: [{name: input}], 14 | }, 15 | ], 16 | start: 0, 17 | maxResults: 25, 18 | }); 19 | 20 | // Clean up the JSON entry. 21 | papers = papers.map(function(p) { 22 | p.title = p.title.replace(/\s*\n\s*/, ' '); 23 | p.authors = p.authors.map(x => x.flat()[0]); 24 | p.url = p.id; // "https://arxiv.org/abs/1234.56789v2" 25 | 26 | var match = p.url.match(/^https?:\/\/arxiv.org\/(pdf|abs)\/([\w.\-\/]+)(v\d+)(\.pdf)?$/); 27 | var id = (match || [])[2]; 28 | var ver = (match || [])[3]; 29 | 30 | p.id = id; // 1234.56789 31 | p.ver = ver; // v2 32 | p.canonical_url = `https://arxiv.org/abs/${id}`; 33 | p.year = p.published.substring(0, 4); 34 | return p; 35 | }); 36 | 37 | // TODO: employ better ranking. 38 | return papers; 39 | }; 40 | 41 | M.generate_bibtex = function(p) { 42 | // Use google scholar style bibitem identifier. 43 | // e.g., @article{lastname2021firstword ... } 44 | // TODO: Make it configurable. 45 | var repr_author = p.authors[0].split(' '); 46 | repr_author = repr_author[repr_author.length - 1].toLowerCase(); // last name 47 | var title_word = p.title.replace('-', ' ').split(' ')[0].toLowerCase(); 48 | 49 | return `@article{${repr_author}${p.year}${title_word}, 50 | title = {{${p.title}}}, 51 | author = {${p.authors.join(' and ')}}, 52 | journal = {arXiv preprint arXiv:${p.id}}, 53 | year = {${p.year}}, 54 | } 55 | `; 56 | }; 57 | 58 | M.to_alfred_items = function(papers) { 59 | var paper_to_entry = function(p) { 60 | // p: arXiv paper entry. 61 | return { 62 | //title: p.title, 63 | title: p.id ? `[${p.id}] ${p.title}` : p.title, 64 | subtitle: p.authors.join(', '), 65 | arg: p.canonical_url, 66 | url: p.url, 67 | text: { 68 | // text to copy, cmd+C 69 | copy: `[${p.title}](${p.canonical_url})`, 70 | // largetype display text, cmd+L 71 | largetype: p.title + "\n\n" + p.authors.join(', ') + "\n" + p.canonical_url, 72 | }, 73 | mods: { 74 | // alt(option): Open the PDF instead of abstract page. 75 | alt: {subtitle: `Open http://arxiv.org/pdf/${p.id}`, 76 | arg: `http://arxiv.org/pdf/${p.id}`}, 77 | // cmd: Copy the url of the abstract page. 78 | cmd: {subtitle: `http://arxiv.org/abs/${p.id}`}, 79 | // shift: Copy to the clipboard the identifier. 80 | shift: {subtitle: `Copy the identifier to the clipboard: ${p.id}`, 81 | arg: `${p.id}`}, 82 | // ctrl: Copy to the clipboard a BibTex entry. 83 | ctrl: {subtitle: `Copy BibTex entry of ${p.id} to the clipboard`, 84 | arg: M.generate_bibtex(p) 85 | }, 86 | } 87 | } 88 | }; 89 | 90 | return papers.map(paper_to_entry); 91 | }; 92 | 93 | M.query = async function(input) { 94 | var papers = await M.query_arxiv(input); 95 | return M.to_alfred_items(papers); 96 | }; 97 | 98 | // Execute when running from Alfred. 99 | const argv = process.argv.slice(2); 100 | if (process.env.alfred_workflow_name) { 101 | alfy.output(await M.query(alfy.input)); 102 | } 103 | else if (argv.length) { 104 | var result = await M.query(argv.join(' ')); 105 | //var result = await M.query_arxiv(argv.join(' ')); 106 | console.log(result); 107 | } 108 | 109 | const query = M; 110 | export default query; 111 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wookayin/alfred-arxiv-workflow/4da9ff46b3f2426edf954c1cfb468bfaea1f818c/screenshot.png --------------------------------------------------------------------------------