├── .gitignore ├── .npmignore ├── .github ├── codecov.yml └── workflows │ └── ci.yml ├── package.json ├── CHANGELOG.md ├── LICENSE ├── test └── wolfram.test.js ├── README.md └── wolfram.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | wolfram-cov.js 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | examples 3 | .gitignore 4 | .travis.yml 5 | .npmignore 6 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | require_changes: no 3 | layout: "diff,files" 4 | coverage: 5 | status: 6 | project: 7 | project: off 8 | patch: off 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - uses: actions/setup-node@v3 12 | with: 13 | node-version: 'lts/*' 14 | - run: npm install 15 | - run: npm run lint 16 | - run: npm test 17 | coverage: 18 | runs-on: ubuntu-latest 19 | steps: 20 | - uses: actions/checkout@v3 21 | - uses: actions/setup-node@v3 22 | with: 23 | node-version: 'lts/*' 24 | - run: npm install 25 | - run: npm run coverage 26 | - uses: codecov/codecov-action@v3 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Eirik Albrigtsen ", 3 | "name": "wolfram-alpha", 4 | "description": "Wolfram Alpha API", 5 | "keywords": [ 6 | "wolfram", 7 | "alpha", 8 | "api", 9 | "wolframalpha" 10 | ], 11 | "version": "0.6.0", 12 | "repository": { 13 | "type": "git", 14 | "url": "clux/wolfram-alpha" 15 | }, 16 | "scripts": { 17 | "test": "bndg test/*.test.js", 18 | "precoverage": "istanbul cover bndg test/*.test.js", 19 | "coverage": "cat coverage/lcov.info && rm -rf coverage" 20 | }, 21 | "main": "wolfram.js", 22 | "dependencies": { 23 | "libxmljs": "^0.18.4", 24 | "request": "^2.81.0" 25 | }, 26 | "devDependencies": { 27 | "bandage": "^0.5.0", 28 | "istanbul": "^0.4.5" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 0.6.0 / 2017-04-16 2 | ================== 3 | * bump all dependencies to tip 4 | * *BREAKING* `client.query` now returns a promise 5 | * Old functionality temporarily available under `client.queryCb`. 6 | 7 | 0.5.0 / 2016-05-23 8 | ================== 9 | * upgrade libxmljs to 0.18.0 10 | 11 | 0.4.0 / 2016-04-20 12 | ================== 13 | * upgrade libxmljs to 0.17.1 14 | 15 | 0.3.0 / 2015-11-16 16 | ================== 17 | * Upgrade libxmljs to 0.15.0 18 | * Added `.npmignore` 19 | 20 | 0.2.1 / 2015-05-13 21 | ================== 22 | * Upgrade libxmljs to 0.14.1 to fix iojs 2.X build 23 | 24 | 0.2.0 / 2015-04-04 25 | ================== 26 | * Upgrade libxmljs to 0.13.0 27 | * Upgrade request to 2.54.0 28 | 29 | 0.1.1 / 2013-09-21 30 | ================== 31 | * typos and bumps 32 | 33 | 0.1.0 / 2013-06-08 34 | ================== 35 | * initial release 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Eirik Albrigtsen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/wolfram.test.js: -------------------------------------------------------------------------------- 1 | var test = require('bandage'); 2 | var wolfram = require('..'); 3 | 4 | test('maths', function *(t) { 5 | var client = wolfram.createClient(process.env.WOLFRAM_APPID); 6 | 7 | var result = yield client.query("integrate 2x"); 8 | t.ok(Array.isArray(result), "result is an array"); 9 | t.equal(result.length, 2, "with 2 result pods"); 10 | t.equal(result[0].primary, true, "first pod should be primary"); 11 | t.ok(Array.isArray(result[0].subpods), "subpods present"); 12 | t.ok(result[0].subpods[0].text.indexOf('x^2 + constant') >= 0, 'answer'); 13 | t.ok(result[0].subpods[0].image, "image answer"); 14 | }); 15 | 16 | test('simple', function *(t) { 17 | var client = wolfram.createClient(process.env.WOLFRAM_APPID); 18 | var result = yield client.query("1+1"); 19 | t.ok(Array.isArray(result), "result is an array"); 20 | t.equal(result.length, 6, "with 6 result pods"); 21 | t.ok(!result[0].primary, "first pod not primary"); 22 | t.equal(result[0].subpods[0].text, '1 + 1', 'interpretation in pod 1'); 23 | t.ok(result[1].primary, 'second pod primary'); 24 | t.equal(result[1].subpods[0].text, '2', 'answer in pod 2'); 25 | }); 26 | 27 | test('noanswer', function *(t) { 28 | var client = wolfram.createClient(process.env.WOLFRAM_APPID); 29 | 30 | var result = yield client.query("adh89u8n0eudhdah"); 31 | t.ok(Array.isArray(result), "result is an array"); 32 | t.equal(result.length, 0, 'but no pods found'); 33 | }); 34 | 35 | test('missingAppId', function *(t) { 36 | var client = wolfram.createClient(); 37 | 38 | try { 39 | yield client.query("integrate 2x"); 40 | t.ok(false, 'should not be here'); 41 | } 42 | catch (e) { 43 | t.ok(/key not set/.test(e), "error contains reason"); 44 | } 45 | }); 46 | 47 | test('badAppId', function *(t) { 48 | var client = wolfram.createClient("bogus-trololoo"); 49 | 50 | try { 51 | yield client.query("integrate 2x"); 52 | t.ok(false, 'should not be here'); 53 | } 54 | catch (e) { 55 | t.equal(typeof e, "string", "error should be a string describing the message"); 56 | } 57 | }); 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # wolfram-alpha 2 | 3 | ## **DEPRECATED AND ARCHIVED** 4 | This repo is archived and the package should be considered deprecated. 5 | It relies on old, vulnerable libxml, as well as request which is also outdated. 6 | No further development is provided. 7 | 8 | ## Info 9 | [![npm status](http://img.shields.io/npm/v/wolfram-alpha.svg)](https://www.npmjs.org/package/wolfram-alpha) 10 | This is a Wolfram|Alpha API wrapper for Node.js. 11 | 12 | All the query parameters are fully customizable as per the [API reference](http://products.wolframalpha.com/docs/WolframAlpha-API-Reference.pdf), and the normal `'plaintext,image,sound,mathml'` formats are all parsed properly. 13 | 14 | ## Usage 15 | Register for a Wolfram|Alpha application ID. The Wolfram|Alpha® API is available for free for non-commercial experimental use with a low monthly cap on queries. For more information, visit [http://products.wolframalpha.com/developers/](http://products.wolframalpha.com/developers/). Wolfram is a registered trademark of the Wolfram Group of Companies. 16 | 17 | Install the module with npm, and (possibly) install the libxml dependency in your OS first: 18 | 19 | ```sh 20 | $ sudo apt-get install libxml2-dev 21 | $ npm install wolfram-alpha 22 | ``` 23 | 24 | Example usage: 25 | 26 | ```js 27 | var wolfram = require('wolfram-alpha').createClient("APIKEY-HERE", opts); 28 | 29 | var results = yield wolfram.query("integrate 2x") 30 | console.log("Result: %j", results); 31 | ``` 32 | 33 | ## Output 34 | The results calls back with a result array of pods (each pod is typically one of the result blocks on the web interface). An empty results array corresponds to results found. 35 | 36 | When results exist, each pod will have the following format: 37 | 38 | ```js 39 | results[0]; 40 | { 41 | title: "some title", 42 | subpods: [ 43 | { 44 | text: "text found in the element", 45 | image: "link found in the <img src=> attribute", 46 | mathml: "mathml string found in the <mathml> element" 47 | }, 48 | // maybe more subpods (but often not) 49 | ], 50 | primary: Boolean("was the primary attribute set on the pod?"), 51 | sounds: [ 52 | "link found in the first <sound url=> attribute", 53 | // maybe more sounds 54 | ] 55 | } 56 | ``` 57 | 58 | Sounds and mathml will only exist when you request them in the createClient opts.format string, and they are not guaranteed to exist. Text and images can be removed from the opts.format string as well which will likely mean you get much fewer pods. 59 | 60 | Having a quick look at the [API reference](http://products.wolframalpha.com/docs/WolframAlpha-API-Reference.pdf) and the [short source](./wolfram.js) will prove useful for providing guarantees of what kind of data is available when. 61 | 62 | Note that `results[0]` seems to always be the "Input Interpretation", while `results[1]` is the most relevant answers (but it will not always have `results[1].primary === true` despite this). 63 | 64 | ## Running tests 65 | Set the API key as an environment variable, then run the library's test command: 66 | 67 | ```bash 68 | $ export WOLFRAM_APPID=APIKEY-HERE 69 | $ npm test 70 | ``` 71 | 72 | ## License 73 | MIT-Licensed. See LICENSE file for details. 74 | -------------------------------------------------------------------------------- /wolfram.js: -------------------------------------------------------------------------------- 1 | var xml = require('libxmljs') 2 | , qs = require('querystring') 3 | , request = require('request'); 4 | 5 | function Client(appKey, opts) { 6 | var qp = this.qp = {}; 7 | qp.units = 'metric'; 8 | // Not all formats are supported yet. Needs work in pod mappers below. 9 | // http://products.wolframalpha.com/docs/WolframAlpha-API-Reference.pdf 10 | qp.format = 'plaintext,image'; // same as not setting format parameter 11 | qp.primary = true; // set primary result bool per pod 12 | 13 | // merge in options 14 | Object.keys(opts || {}).forEach(function (key) { 15 | qp[key] = opts[key]; 16 | }); 17 | 18 | // ensure format is sanely set lest it breaks the parser's expectations 19 | if (!qp.format || 'string' !== typeof qp.format) { 20 | throw new Error("cannot unset/change format completely"); 21 | } 22 | 23 | // ensure appKey overrides opts 24 | qp.appid = appKey; 25 | } 26 | 27 | Client.prototype.query = function (input, debug) { 28 | return new Promise((resolve, reject) => { 29 | this.queryCb(input, function (err, res) { 30 | if (err) { 31 | reject(err); 32 | } else { 33 | resolve(res); 34 | } 35 | }, debug); 36 | }); 37 | } 38 | 39 | Client.prototype.queryCb = function (input, cb, debug) { 40 | var qp = this.qp; 41 | 42 | if(!qp.appid) { 43 | return cb(new Error("Application key not set"), null); 44 | } 45 | if (!input) { 46 | return cb(null, []); 47 | } 48 | 49 | // TODO: perhaps other attributes should be set on a per query basis as well 50 | // at the moment everything but input is global 51 | qp.input = input; 52 | var uri = 'http://api.wolframalpha.com/v2/query?' + qs.stringify(this.qp); 53 | 54 | if (debug) { 55 | request(uri).pipe(process.stdout); 56 | return; 57 | } 58 | 59 | var parseImg = qp.format.indexOf('image') >= 0 60 | , parseText = qp.format.indexOf('plaintext') >= 0 61 | , parseSound = qp.format.indexOf('sound') >= 0 62 | , parseMathml = qp.format.indexOf('mathml') >= 0 63 | ; 64 | 65 | request(uri, function (error, response, body) { 66 | if (error || response.statusCode !== 200) { 67 | return cb(error, null); 68 | } 69 | 70 | var doc = xml.parseXml(body); 71 | var root = doc.root(); 72 | 73 | if (root.attr('error').value() !== 'false') { 74 | var message = root.get('//error/msg').text(); 75 | return cb(message, null); 76 | } 77 | 78 | var pods = root.find('pod').map(function (pod) { 79 | var o = {}; 80 | if (qp.primary) { 81 | o.primary = (pod.attr('primary') && pod.attr('primary').value() === 'true'); 82 | } 83 | o.title = pod.attr('title').value(); 84 | 85 | // sound elements are inside sounds 86 | if (parseSound && pod.get('sounds')) { 87 | // we just return a flat list of urls 88 | o.sounds = pod.get('sounds').find('sound').map(function (s) { 89 | return s.attr('url').value(); 90 | }); 91 | } 92 | 93 | // image and plaintext are found in the subpods 94 | o.subpods = pod.find('subpod').map(function (node) { 95 | var i = {}; 96 | 97 | if (parseText) { 98 | i.text = node.get('plaintext').text(); 99 | } 100 | if (parseImg) { 101 | // often just a link to an image of the above text 102 | // but will be useful if !i.text (e.g. psy curve) 103 | i.image = node.get('img').attr('src').value(); 104 | } 105 | if (parseMathml && node.get('mathml')) { 106 | // even if we request mathml it is not always there 107 | // if it is, we parse through the raw mathml string 108 | i.mathml = node.get('mathml').toString(); 109 | } 110 | return i; 111 | }); 112 | 113 | return o; 114 | }); 115 | cb(null, pods); 116 | }); 117 | }; 118 | 119 | exports.createClient = function (appKey, opts) { 120 | return new Client(appKey, opts); 121 | }; 122 | --------------------------------------------------------------------------------