├── .gitignore ├── bin.js ├── build ├── __tests__ │ ├── bin.d.ts │ ├── bin.js │ ├── bin.js.map │ ├── index.d.ts │ ├── index.js │ └── index.js.map ├── index.d.ts ├── index.js └── index.js.map ├── changelog.md ├── doc └── schema.md ├── examples ├── hackernews.graphql └── source.graphql ├── package.json ├── readme.md ├── screenshot.png ├── src ├── __tests__ │ ├── __snapshots__ │ │ ├── bin.js.md │ │ └── bin.js.snap │ ├── bin.ts │ └── index.ts ├── index.ts └── types.graphql ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | -------------------------------------------------------------------------------- /bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const argv = require('minimist')(process.argv.slice(2)) 3 | 4 | const colors = require('chalk') 5 | const fs = require('fs') 6 | const getStdin = require('get-stdin') 7 | const GraphQL = require('graphql') 8 | const { graphql } = GraphQL 9 | const util = require('util') 10 | 11 | const schema = require('./build') 12 | 13 | const package = require('./package.json') 14 | const usage = `graphql-scraper v${package.version} 15 | 16 | Usage: graphql-scraper [query-file] [--json] [--=value] 17 | 18 | Reads a GraphQL query from query-file, and prints the result. 19 | 20 | If query-file is not given, reads a query from stdin. 21 | 22 | Options: 23 | \t--json\tSerialize the result as parseable JSON. 24 | 25 | Example: 26 | > graphql-scraper query.graphql --json --url="https://news.ycombinator.com/" --page=2 27 | ` 28 | 29 | function die() { 30 | console.log(usage) 31 | process.exit(1) 32 | } 33 | 34 | const formatters = { 35 | json: function(result) { 36 | console.log(JSON.stringify(result)) 37 | }, 38 | pretty: function(result) { 39 | if (result.data) { 40 | console.log(colors.green('Data:')) 41 | printObject(result.data) 42 | } 43 | if (Array.isArray(result.errors)) { 44 | console.log(colors.red('Errors:')) 45 | result.errors.forEach(error => { 46 | printObject(error) 47 | }) 48 | console.log(colors.red('Your query has errors, see above.')) 49 | } 50 | function printObject(obj) { 51 | console.log(util.inspect(obj, { depth: null, colors: true })) 52 | } 53 | }, 54 | } 55 | 56 | async function main() { 57 | if (argv.help) { 58 | console.log(usage) 59 | return 60 | } 61 | 62 | const formatter = argv.json ? 'json' : 'pretty' 63 | delete argv.json 64 | 65 | let query 66 | if (argv._.length === 0) { 67 | query = await getStdin() 68 | if (query === '') { 69 | die() 70 | } 71 | } else if (argv._.length === 1) { 72 | query = fs.readFileSync(argv._[0], { encoding: 'utf8' }) 73 | } else { 74 | die() 75 | } 76 | delete argv._ 77 | 78 | const result = await graphql(schema, query, {}, {}, argv) 79 | formatters[formatter](result) 80 | } 81 | main() 82 | -------------------------------------------------------------------------------- /build/__tests__/bin.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachenmayer/graphql-scraper/c66c0e15f79155e17679781ba9b3f9edef9c64d9/build/__tests__/bin.d.ts -------------------------------------------------------------------------------- /build/__tests__/bin.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const ava_1 = require("ava"); 12 | const http_1 = require("http"); 13 | const execa = require('execa'); 14 | ava_1.default('it runs a query from stdin', (t) => __awaiter(this, void 0, void 0, function* () { 15 | const server = http_1.createServer((req, res) => res.end(` 16 | 17 | 18 | some test site 19 | 20 | 21 |
looking good
22 | 23 | 24 | `)); 25 | server.listen(13338); 26 | const query = ` 27 | { 28 | page(url: "http://localhost:13338") { 29 | title: text(selector: "title") 30 | main: text(selector: "main") 31 | } 32 | } 33 | `; 34 | const output = yield execa.stdout('./bin.js', ['--json'], { input: query }); 35 | t.snapshot(output); 36 | server.close(); 37 | })); 38 | ava_1.default('it runs a query from a file', (t) => __awaiter(this, void 0, void 0, function* () { 39 | const output = yield execa.stdout('./bin.js', [ 40 | 'examples/source.graphql', 41 | '--json', 42 | ]); 43 | t.snapshot(output); 44 | })); 45 | ava_1.default('it formats output nicely', (t) => __awaiter(this, void 0, void 0, function* () { 46 | const output = yield execa.stdout('./bin.js', ['examples/source.graphql']); 47 | t.snapshot(output); 48 | })); 49 | ava_1.default('it formats errors nicely', (t) => __awaiter(this, void 0, void 0, function* () { 50 | const output = yield execa.stdout('./bin.js', { input: 'garbage' }); 51 | t.snapshot(output); 52 | })); 53 | //# sourceMappingURL=bin.js.map -------------------------------------------------------------------------------- /build/__tests__/bin.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/__tests__/bin.ts"],"names":[],"mappings":";;;;;;;;;;AAAA,6BAAsB;AACtB,+BAAmC;AACnC,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAE9B,aAAI,CAAC,4BAA4B,EAAE,CAAM,CAAC,EAAC,EAAE;IAC3C,MAAM,MAAM,GAAG,mBAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CACvC,GAAG,CAAC,GAAG,CAAC;;;;;;;;;CASX,CAAC,CACC,CAAA;IACD,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;IAEpB,MAAM,KAAK,GAAG;;;;;;;GAOb,CAAA;IAED,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAA;IAC3E,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;IAElB,MAAM,CAAC,KAAK,EAAE,CAAA;AAChB,CAAC,CAAA,CAAC,CAAA;AAEF,aAAI,CAAC,6BAA6B,EAAE,CAAM,CAAC,EAAC,EAAE;IAC5C,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE;QAC5C,yBAAyB;QACzB,QAAQ;KACT,CAAC,CAAA;IACF,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACpB,CAAC,CAAA,CAAC,CAAA;AAEF,aAAI,CAAC,0BAA0B,EAAE,CAAM,CAAC,EAAC,EAAE;IACzC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC,yBAAyB,CAAC,CAAC,CAAA;IAC1E,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACpB,CAAC,CAAA,CAAC,CAAA;AAEF,aAAI,CAAC,0BAA0B,EAAE,CAAM,CAAC,EAAC,EAAE;IACzC,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAA;IACnE,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAA;AACpB,CAAC,CAAA,CAAC,CAAA"} -------------------------------------------------------------------------------- /build/__tests__/index.d.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachenmayer/graphql-scraper/c66c0e15f79155e17679781ba9b3f9edef9c64d9/build/__tests__/index.d.ts -------------------------------------------------------------------------------- /build/__tests__/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | Object.defineProperty(exports, "__esModule", { value: true }); 11 | const ava_1 = require("ava"); 12 | const GraphQL = require("graphql"); 13 | const { graphql } = GraphQL; 14 | const http_1 = require("http"); 15 | const _1 = require("../"); 16 | const requiredSchema = require('../'); 17 | ava_1.default('es6 & commonjs imports work', t => { 18 | t.is(_1.default, requiredSchema); 19 | }); 20 | ava_1.default('no args throws errors', (t) => __awaiter(this, void 0, void 0, function* () { 21 | const query = `{ page { title } }`; 22 | const response = yield graphql(_1.default, query); 23 | t.is(response && response.errors && response.errors[0].message, 'You need to provide either a URL or a HTML source string.'); 24 | })); 25 | ava_1.default('title', (t) => __awaiter(this, void 0, void 0, function* () { 26 | const html = `some title`; 27 | const query = `{ page(source: "${html}") { title } }`; 28 | const response = yield graphql(_1.default, query); 29 | t.false('errors' in response); 30 | t.is(response.data && response.data.page.title, 'some title'); 31 | })); 32 | ava_1.default('from url', (t) => __awaiter(this, void 0, void 0, function* () { 33 | const server = http_1.createServer((req, res) => { 34 | res.end(`some title`); 35 | }); 36 | server.listen(13337); 37 | const query = `{ page(url: "http://localhost:13337/") { title } }`; 38 | const response = yield graphql(_1.default, query); 39 | t.false('errors' in response); 40 | t.is(response.data && response.data.page.title, 'some title'); 41 | server.close(); 42 | })); 43 | ava_1.default('content', (t) => __awaiter(this, void 0, void 0, function* () { 44 | const html = `some titlesome body`; 45 | const query = `{ page(source: "${html}") { content } }`; 46 | const response = yield graphql(_1.default, query); 47 | t.false('errors' in response); 48 | t.is(response.data && response.data.page.content, 'some titlesome body'); 49 | })); 50 | ava_1.default('content with selector', (t) => __awaiter(this, void 0, void 0, function* () { 51 | const html = `some title
bad
`; 52 | const query = `{ 53 | page(source: "${html}") { 54 | content(selector: ".selectme") 55 | } 56 | }`; 57 | const response = yield graphql(_1.default, query); 58 | t.false('errors' in response); 59 | t.is(response.data && response.data.page.content, 'bad'); 60 | })); 61 | ava_1.default('not existing selector', (t) => __awaiter(this, void 0, void 0, function* () { 62 | const html = `some title
bad
`; 63 | const query = `{ 64 | page(source: "${html}") { 65 | content(selector: ".selectmenot") 66 | } 67 | }`; 68 | const response = yield graphql(_1.default, query); 69 | t.false('errors' in response); 70 | t.is(response.data && response.data.page.content, null); 71 | })); 72 | ava_1.default('html', (t) => __awaiter(this, void 0, void 0, function* () { 73 | const html = `some titlesome body`; 74 | const query = `{ page(source: "${html}") { html } }`; 75 | const response = yield graphql(_1.default, query); 76 | t.false('errors' in response); 77 | t.is(response.data && response.data.page.html, html); 78 | })); 79 | ava_1.default('html with selector', (t) => __awaiter(this, void 0, void 0, function* () { 80 | const html = `some title
bad
`; 81 | const query = `{ 82 | page(source: "${html}") { 83 | html(selector: ".selectme") 84 | } 85 | }`; 86 | const response = yield graphql(_1.default, query); 87 | t.false('errors' in response); 88 | t.is(response.data && response.data.page.html, '
bad
'); 89 | })); 90 | ava_1.default('text', (t) => __awaiter(this, void 0, void 0, function* () { 91 | const html = `some title
bad
`; 92 | const query = `{ 93 | page(source: "${html}") { 94 | text 95 | } 96 | }`; 97 | const response = yield graphql(_1.default, query); 98 | t.false('errors' in response); 99 | t.is(response.data && response.data.page.text, 'some titlebad'); 100 | })); 101 | ava_1.default('text with selector', (t) => __awaiter(this, void 0, void 0, function* () { 102 | const html = `some title
bad
`; 103 | const query = `{ 104 | page(source: "${html}") { 105 | text(selector: ".selectme") 106 | } 107 | }`; 108 | const response = yield graphql(_1.default, query); 109 | t.false('errors' in response); 110 | t.is(response.data && response.data.page.text, 'bad'); 111 | })); 112 | ava_1.default('tag', (t) => __awaiter(this, void 0, void 0, function* () { 113 | const html = `some title
bad
`; 114 | const query = `{ 115 | page(source: "${html}") { 116 | tag 117 | } 118 | }`; 119 | const response = yield graphql(_1.default, query); 120 | t.false('errors' in response); 121 | t.is(response.data && response.data.page.tag, 'HTML'); 122 | })); 123 | ava_1.default('tag with selector', (t) => __awaiter(this, void 0, void 0, function* () { 124 | const html = `some title
bad
`; 125 | const query = `{ 126 | page(source: "${html}") { 127 | tag(selector: ".selectme") 128 | } 129 | }`; 130 | const response = yield graphql(_1.default, query); 131 | t.false('errors' in response); 132 | t.is(response.data && response.data.page.tag, 'DIV'); 133 | })); 134 | ava_1.default('attr', (t) => __awaiter(this, void 0, void 0, function* () { 135 | const html = `some title
bad
`; 136 | const query = `{ 137 | page(source: "${html}") { 138 | attr(name: "style") 139 | } 140 | }`; 141 | const response = yield graphql(_1.default, query); 142 | t.false('errors' in response); 143 | t.is(response.data && response.data.page.attr, 'background: red;'); 144 | })); 145 | ava_1.default('wacky attr', (t) => __awaiter(this, void 0, void 0, function* () { 146 | const html = `some title
bad
`; 147 | const query = `{ 148 | page(source: "${html}") { 149 | attr(name: "asdf") 150 | } 151 | }`; 152 | const response = yield graphql(_1.default, query); 153 | t.false('errors' in response); 154 | t.is(response.data && response.data.page.attr, null); 155 | })); 156 | ava_1.default('attr with selector', (t) => __awaiter(this, void 0, void 0, function* () { 157 | const html = `some title
bad
`; 158 | const query = `{ 159 | page(source: "${html}") { 160 | attr(selector: ".selectme", name: "class") 161 | } 162 | }`; 163 | const response = yield graphql(_1.default, query); 164 | t.false('errors' in response); 165 | t.is(response.data && response.data.page.attr, 'selectme'); 166 | })); 167 | ava_1.default('has', (t) => __awaiter(this, void 0, void 0, function* () { 168 | const html = `some title
one
two
`; 169 | const query = `{ 170 | page(source: "${html}") { 171 | firstDiv: query(selector: "div") { 172 | isStrong: has(selector: "strong") 173 | } 174 | } 175 | }`; 176 | const response = yield graphql(_1.default, query); 177 | t.false('errors' in response); 178 | t.true(response.data && response.data.page.firstDiv.isStrong); 179 | })); 180 | ava_1.default('has not', (t) => __awaiter(this, void 0, void 0, function* () { 181 | const html = `some title
one
two
`; 182 | const query = `{ 183 | page(source: "${html}") { 184 | firstDiv: query(selector: "div") { 185 | isWeak: has(selector: "weak") 186 | } 187 | } 188 | }`; 189 | const response = yield graphql(_1.default, query); 190 | t.false('errors' in response); 191 | t.true(response.data && !response.data.page.firstDiv.isWeak); 192 | })); 193 | ava_1.default('query', (t) => __awaiter(this, void 0, void 0, function* () { 194 | const html = `some title
one
two
`; 195 | const query = `{ 196 | page(source: "${html}") { 197 | firstDiv: query(selector: "div") { 198 | text 199 | } 200 | } 201 | }`; 202 | const response = yield graphql(_1.default, query); 203 | t.false('errors' in response); 204 | t.deepEqual(response.data && response.data.page.firstDiv, { text: 'one' }); 205 | })); 206 | ava_1.default('queryAll', (t) => __awaiter(this, void 0, void 0, function* () { 207 | const html = `some title
one
two
`; 208 | const query = `{ 209 | page(source: "${html}") { 210 | divs: queryAll(selector: "div") { 211 | text 212 | } 213 | } 214 | }`; 215 | const response = yield graphql(_1.default, query); 216 | t.false('errors' in response); 217 | t.deepEqual(response.data && response.data.page.divs, [ 218 | { text: 'one' }, 219 | { text: 'two' }, 220 | ]); 221 | })); 222 | ava_1.default('children', (t) => __awaiter(this, void 0, void 0, function* () { 223 | const html = `some title
onetwo
twothree
`; 224 | const query = `{ 225 | page(source: "${html}") { 226 | kids: queryAll(selector: "div") { 227 | children { 228 | text 229 | } 230 | } 231 | } 232 | }`; 233 | const response = yield graphql(_1.default, query); 234 | t.false('errors' in response); 235 | t.deepEqual(response.data && response.data.page.kids, [ 236 | { 237 | children: [{ text: 'one' }, { text: 'two' }], 238 | }, 239 | { 240 | children: [{ text: 'two' }, { text: 'three' }], 241 | }, 242 | ]); 243 | })); 244 | ava_1.default('childNodes', (t) => __awaiter(this, void 0, void 0, function* () { 245 | const html = `some title
onetwo
twoamazingthree
`; 246 | const query = `{ 247 | page(source: "${html}") { 248 | kids: queryAll(selector: "div") { 249 | childNodes { 250 | text 251 | } 252 | } 253 | } 254 | }`; 255 | const response = yield graphql(_1.default, query); 256 | t.false('errors' in response); 257 | t.deepEqual(response.data && response.data.page.kids, [ 258 | { 259 | childNodes: [{ text: 'one' }, { text: 'two' }], 260 | }, 261 | { 262 | childNodes: [{ text: 'two' }, { text: 'amazing' }, { text: 'three' }], 263 | }, 264 | ]); 265 | })); 266 | ava_1.default('parent', (t) => __awaiter(this, void 0, void 0, function* () { 267 | const html = `some title
bad
`; 268 | const query = `{ 269 | page(source: "${html}") { 270 | query(selector: "strong") { 271 | parent { 272 | attr(name: "class") 273 | } 274 | } 275 | } 276 | }`; 277 | const response = yield graphql(_1.default, query); 278 | t.false('errors' in response); 279 | t.is(response.data && response.data.page.query.parent.attr, 'selectme'); 280 | })); 281 | ava_1.default('siblings', (t) => __awaiter(this, void 0, void 0, function* () { 282 | const html = `some title
bad

boom

bap
`; 283 | const query = `{ 284 | page(source: "${html}") { 285 | query(selector: "strong") { 286 | siblings { 287 | text 288 | } 289 | } 290 | } 291 | }`; 292 | const response = yield graphql(_1.default, query); 293 | t.false('errors' in response); 294 | t.deepEqual(response.data && response.data.page.query.siblings, [ 295 | { text: 'bad' }, 296 | { text: 'boom' }, 297 | { text: 'bap' }, 298 | ]); 299 | })); 300 | ava_1.default('siblings of root is only html', (t) => __awaiter(this, void 0, void 0, function* () { 301 | const html = `nothing to see here`; 302 | const query = `{ 303 | page(source: "${html}") { 304 | siblings { 305 | tag 306 | } 307 | } 308 | }`; 309 | const response = yield graphql(_1.default, query); 310 | t.false('errors' in response); 311 | t.deepEqual(response.data && response.data.page.siblings, [{ tag: 'HTML' }]); 312 | })); 313 | ava_1.default('next', (t) => __awaiter(this, void 0, void 0, function* () { 314 | const html = `some title
bad

boom

bap
`; 315 | const query = `{ 316 | page(source: "${html}") { 317 | query(selector: "strong") { 318 | next { 319 | text 320 | } 321 | } 322 | } 323 | }`; 324 | const response = yield graphql(_1.default, query); 325 | t.false('errors' in response); 326 | t.is(response.data && response.data.page.query.next.text, 'boom'); 327 | })); 328 | ava_1.default('next - bare text', (t) => __awaiter(this, void 0, void 0, function* () { 329 | const html = `some title
badbare textbap
`; 330 | const query = `{ 331 | page(source: "${html}") { 332 | query(selector: "strong") { 333 | next { 334 | tag 335 | text 336 | } 337 | } 338 | } 339 | }`; 340 | const response = yield graphql(_1.default, query); 341 | t.false('errors' in response); 342 | t.is(response.data && response.data.page.query.next.tag, null); 343 | t.is(response.data && response.data.page.query.next.text, 'bare text'); 344 | })); 345 | ava_1.default('nextAll', (t) => __awaiter(this, void 0, void 0, function* () { 346 | const html = `some title
badbare textbap
`; 347 | const query = `{ 348 | page(source: "${html}") { 349 | query(selector: "strong") { 350 | nextAll { 351 | tag 352 | text 353 | } 354 | } 355 | } 356 | }`; 357 | const response = yield graphql(_1.default, query); 358 | t.false('errors' in response); 359 | t.deepEqual(response.data && response.data.page.query.nextAll, [ 360 | { tag: null, text: 'bare text' }, 361 | { tag: 'SPAN', text: 'bap' }, 362 | ]); 363 | })); 364 | ava_1.default('previous', (t) => __awaiter(this, void 0, void 0, function* () { 365 | const html = `some title
bad

boom

bap
`; 366 | const query = `{ 367 | page(source: "${html}") { 368 | query(selector: "span") { 369 | previous { 370 | text 371 | } 372 | } 373 | } 374 | }`; 375 | const response = yield graphql(_1.default, query); 376 | t.false('errors' in response); 377 | t.is(response.data && response.data.page.query.previous.text, 'boom'); 378 | })); 379 | ava_1.default('previousAll', (t) => __awaiter(this, void 0, void 0, function* () { 380 | const html = `some title
badbare textbap
`; 381 | const query = `{ 382 | page(source: "${html}") { 383 | query(selector: "span") { 384 | previousAll { 385 | tag 386 | text 387 | } 388 | } 389 | } 390 | }`; 391 | const response = yield graphql(_1.default, query); 392 | t.false('errors' in response); 393 | t.deepEqual(response.data && response.data.page.query.previousAll, [ 394 | { tag: 'STRONG', text: 'bad' }, 395 | { tag: null, text: 'bare text' }, 396 | ]); 397 | })); 398 | ava_1.default('visit', (t) => __awaiter(this, void 0, void 0, function* () { 399 | const server = http_1.createServer((req, res) => { 400 | if (req.url === '/link') { 401 | res.end(`we managed to visit the link!`); 402 | } 403 | else { 404 | res.end(`come on in`); 405 | } 406 | }); 407 | server.listen(13339); 408 | const query = `{ 409 | page(url: "http://localhost:13339/") { 410 | link: query(selector: "a") { 411 | visit { 412 | text(selector: "strong") 413 | } 414 | } 415 | } 416 | }`; 417 | const response = yield graphql(_1.default, query); 418 | t.false('errors' in response); 419 | t.is(response.data && response.data.page.link.visit.text, 'we managed to visit the link!'); 420 | server.close(); 421 | })); 422 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /build/__tests__/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/__tests__/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /build/index.d.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchema } from 'graphql'; 2 | declare const schema: GraphQLSchema; 3 | export = schema; 4 | -------------------------------------------------------------------------------- /build/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | return new (P || (P = Promise))(function (resolve, reject) { 4 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 5 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 6 | function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } 7 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 8 | }); 9 | }; 10 | const graphql_1 = require("graphql"); 11 | const jsdom_1 = require("jsdom"); 12 | const url_1 = require("url"); 13 | function sharedFields() { 14 | const selector = { 15 | type: graphql_1.GraphQLString, 16 | description: 'A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors).', 17 | }; 18 | return { 19 | content: { 20 | type: graphql_1.GraphQLString, 21 | description: 'The HTML content of the subnodes', 22 | args: { selector }, 23 | resolve(element, { selector }) { 24 | element = selector ? element.querySelector(selector) : element; 25 | return element && element.innerHTML; 26 | }, 27 | }, 28 | html: { 29 | type: graphql_1.GraphQLString, 30 | description: 'The HTML content of the selected DOM node', 31 | args: { selector }, 32 | resolve(element, { selector }) { 33 | element = selector ? element.querySelector(selector) : element; 34 | return element && element.outerHTML; 35 | }, 36 | }, 37 | text: { 38 | type: graphql_1.GraphQLString, 39 | description: 'The text content of the selected DOM node', 40 | args: { selector }, 41 | resolve(element, { selector }) { 42 | element = selector ? element.querySelector(selector) : element; 43 | return element && element.textContent; 44 | }, 45 | }, 46 | tag: { 47 | type: graphql_1.GraphQLString, 48 | description: 'The tag name of the selected DOM node', 49 | args: { selector }, 50 | resolve(element, { selector }) { 51 | element = selector ? element.querySelector(selector) : element; 52 | return element && element.tagName; 53 | }, 54 | }, 55 | attr: { 56 | type: graphql_1.GraphQLString, 57 | description: 'An attribute of the selected node (eg. `href`, `src`, etc.).', 58 | args: { 59 | selector, 60 | name: { 61 | type: new graphql_1.GraphQLNonNull(graphql_1.GraphQLString), 62 | description: 'The name of the attribute', 63 | }, 64 | }, 65 | resolve(element, { selector, name }) { 66 | element = selector ? element.querySelector(selector) : element; 67 | if (element == null) 68 | return null; 69 | const attribute = element.attributes[name]; 70 | if (attribute == null) 71 | return null; 72 | return attribute.value; 73 | }, 74 | }, 75 | has: { 76 | type: graphql_1.GraphQLBoolean, 77 | description: 'Returns true if an element with the given selector exists.', 78 | args: { selector }, 79 | resolve(element, { selector }) { 80 | return !!element.querySelector(selector); 81 | }, 82 | }, 83 | query: { 84 | type: ElementType, 85 | description: 'Equivalent to [Element.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). The selectors of any nested queries will be scoped to the resulting element.', 86 | args: { selector }, 87 | resolve(element, { selector }) { 88 | return element.querySelector(selector); 89 | }, 90 | }, 91 | queryAll: { 92 | type: new graphql_1.GraphQLList(ElementType), 93 | description: 'Equivalent to [Element.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). The selectors of any nested queries will be scoped to the resulting elements.', 94 | args: { selector }, 95 | resolve(element, { selector }) { 96 | return Array.from(element.querySelectorAll(selector)); 97 | }, 98 | }, 99 | children: { 100 | type: new graphql_1.GraphQLList(ElementType), 101 | description: "An element's child elements.", 102 | resolve(element) { 103 | return Array.from(element.children); 104 | }, 105 | }, 106 | childNodes: { 107 | type: new graphql_1.GraphQLList(ElementType), 108 | description: "An element's child nodes. Includes text nodes.", 109 | resolve(element) { 110 | return Array.from(element.childNodes); 111 | } 112 | }, 113 | parent: { 114 | type: ElementType, 115 | description: "An element's parent element.", 116 | resolve(element) { 117 | return element.parentElement; 118 | }, 119 | }, 120 | siblings: { 121 | type: new graphql_1.GraphQLList(ElementType), 122 | description: "All elements which are at the same level in the tree as the current element, ie. the children of the current element's parent. Includes the current element.", 123 | resolve(element) { 124 | const parent = element.parentElement; 125 | if (parent == null) 126 | return [element]; 127 | return Array.from(parent.children); 128 | }, 129 | }, 130 | next: { 131 | type: ElementType, 132 | description: "The current element's next sibling. Includes text nodes. Equivalent to [Node.nextSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling).", 133 | resolve(element) { 134 | return element.nextSibling; 135 | }, 136 | }, 137 | nextAll: { 138 | type: new graphql_1.GraphQLList(ElementType), 139 | description: "All of the current element's next siblings", 140 | resolve(element, { selector }) { 141 | const siblings = []; 142 | for (let next = element.nextSibling; next != null; next = next.nextSibling) { 143 | siblings.push(next); 144 | } 145 | return siblings; 146 | }, 147 | }, 148 | previous: { 149 | type: ElementType, 150 | description: "The current element's previous sibling. Includes text nodes. Equivalent to [Node.previousSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling).", 151 | resolve(element) { 152 | return element.previousSibling; 153 | }, 154 | }, 155 | previousAll: { 156 | type: new graphql_1.GraphQLList(ElementType), 157 | description: "All of the current element's previous siblings", 158 | resolve(element, { selector }) { 159 | const siblings = []; 160 | for (let previous = element.previousSibling; previous != null; previous = previous.previousSibling) { 161 | siblings.push(previous); 162 | } 163 | siblings.reverse(); 164 | return siblings; 165 | }, 166 | }, 167 | }; 168 | } 169 | const NodeType = new graphql_1.GraphQLInterfaceType({ 170 | name: 'Node', 171 | description: 'A DOM node (either an Element or a Document).', 172 | fields: sharedFields, 173 | }); 174 | const DocumentType = new graphql_1.GraphQLObjectType({ 175 | name: 'Document', 176 | description: 'A DOM document.', 177 | interfaces: [NodeType], 178 | fields: () => (Object.assign({}, sharedFields(), { title: { 179 | type: graphql_1.GraphQLString, 180 | description: 'The page title', 181 | resolve(element) { 182 | return element.ownerDocument.title; 183 | }, 184 | } })), 185 | }); 186 | const ElementType = new graphql_1.GraphQLObjectType({ 187 | name: 'Element', 188 | description: 'A DOM element.', 189 | interfaces: [NodeType], 190 | fields: () => (Object.assign({}, sharedFields(), { visit: { 191 | type: DocumentType, 192 | description: 'If the element is a link, visit the page linked to in the href attribute.', 193 | resolve(element) { 194 | return __awaiter(this, void 0, void 0, function* () { 195 | const href = element.attributes['href']; 196 | if (href == null) { 197 | return null; 198 | } 199 | const url = url_1.resolve(element.ownerDocument.location.href, href.value); // handle relative links. 200 | const dom = yield jsdom_1.JSDOM.fromURL(url); 201 | return dom.window.document.documentElement; 202 | }); 203 | }, 204 | } })), 205 | }); 206 | const schema = new graphql_1.GraphQLSchema({ 207 | query: new graphql_1.GraphQLObjectType({ 208 | name: 'Query', 209 | fields: () => ({ 210 | page: { 211 | type: DocumentType, 212 | args: { 213 | url: { 214 | type: graphql_1.GraphQLString, 215 | description: 'A URL to fetch the HTML source from.', 216 | }, 217 | source: { 218 | type: graphql_1.GraphQLString, 219 | description: 'A string containing HTML to be used as the source document.', 220 | }, 221 | }, 222 | resolve(_, { url, source }) { 223 | return __awaiter(this, void 0, void 0, function* () { 224 | if (url == null && source == null) { 225 | throw new Error('You need to provide either a URL or a HTML source string.'); 226 | } 227 | const dom = url != null ? yield jsdom_1.JSDOM.fromURL(url) : new jsdom_1.JSDOM(source); 228 | return dom.window.document.documentElement; 229 | }); 230 | }, 231 | }, 232 | }), 233 | }), 234 | }); 235 | // Make this importable with ES6 236 | schema['default'] = schema; 237 | module.exports = schema; 238 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /build/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 1.2.1 4 | 5 | - `graphql` was incorrectly listed as a dev dependency. 6 | 7 | ## 1.2.0 8 | 9 | - Fixed CLI. 10 | 11 | ## 1.1.0 12 | 13 | - Added `childNodes` field 14 | - Added TypeScript declaration file 15 | 16 | ## 1.0.0 17 | 18 | Initial release -------------------------------------------------------------------------------- /doc/schema.md: -------------------------------------------------------------------------------- 1 | # Schema Types 2 | 3 |
4 | Table of Contents 5 | 6 | * [Query](#query) 7 | * [Objects](#objects) 8 | * [Document](#document) 9 | * [Element](#element) 10 | * [Scalars](#scalars) 11 | * [Boolean](#boolean) 12 | * [String](#string) 13 | * [Interfaces](#interfaces) 14 | * [Node](#node) 15 | 16 |
17 | 18 | ## Query 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | 45 | 46 | 51 | 52 | 53 |
FieldArgumentTypeDescription
pageDocument
urlString 38 | 39 | A URL to fetch the HTML source from. 40 | 41 |
sourceString 47 | 48 | A string containing HTML to be used as the source document. 49 | 50 |
54 | 55 | ## Objects 56 | 57 | ### Document 58 | 59 | A DOM document. 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 79 | 80 | 81 | 82 | 83 | 88 | 89 | 90 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | 106 | 107 | 108 | 109 | 110 | 115 | 116 | 117 | 118 | 119 | 124 | 125 | 126 | 127 | 128 | 133 | 134 | 135 | 136 | 137 | 142 | 143 | 144 | 145 | 146 | 151 | 152 | 153 | 154 | 155 | 160 | 161 | 162 | 163 | 164 | 169 | 170 | 171 | 172 | 173 | 178 | 179 | 180 | 181 | 182 | 187 | 188 | 189 | 190 | 191 | 196 | 197 | 198 | 199 | 200 | 205 | 206 | 207 | 208 | 209 | 214 | 215 | 216 | 217 | 218 | 223 | 224 | 225 | 226 | 227 | 232 | 233 | 234 | 235 | 236 | 241 | 242 | 243 | 244 | 245 | 250 | 251 | 252 | 253 | 254 | 259 | 260 | 261 | 262 | 263 | 268 | 269 | 270 | 271 | 272 | 277 | 278 | 279 | 280 | 281 | 286 | 287 | 288 | 289 | 290 | 295 | 296 | 297 |
FieldArgumentTypeDescription
contentString 75 | 76 | The HTML content of the subnodes 77 | 78 |
selectorString 84 | 85 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 86 | 87 |
htmlString 93 | 94 | The HTML content of the selected DOM node 95 | 96 |
selectorString 102 | 103 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 104 | 105 |
textString 111 | 112 | The text content of the selected DOM node 113 | 114 |
selectorString 120 | 121 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 122 | 123 |
tagString 129 | 130 | The tag name of the selected DOM node 131 | 132 |
selectorString 138 | 139 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 140 | 141 |
attrString 147 | 148 | An attribute of the selected node (eg. `href`, `src`, etc.). 149 | 150 |
selectorString 156 | 157 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 158 | 159 |
nameString! 165 | 166 | The name of the attribute 167 | 168 |
hasBoolean 174 | 175 | Returns true if an element with the given selector exists. 176 | 177 |
selectorString 183 | 184 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 185 | 186 |
queryElement 192 | 193 | Equivalent to [Element.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). The selectors of any nested queries will be scoped to the resulting element. 194 | 195 |
selectorString 201 | 202 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 203 | 204 |
queryAll[Element] 210 | 211 | Equivalent to [Element.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). The selectors of any nested queries will be scoped to the resulting elements. 212 | 213 |
selectorString 219 | 220 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 221 | 222 |
children[Element] 228 | 229 | An element's child elements. 230 | 231 |
parentElement 237 | 238 | An element's parent element. 239 | 240 |
siblings[Element] 246 | 247 | All elements which are at the same level in the tree as the current element, ie. the children of the current element's parent. Includes the current element. 248 | 249 |
nextElement 255 | 256 | The current element's next sibling. Includes text nodes. Equivalent to [Node.nextSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 257 | 258 |
nextAll[Element] 264 | 265 | All of the current element's next siblings 266 | 267 |
previousElement 273 | 274 | The current element's previous sibling. Includes text nodes. Equivalent to [Node.previousSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 275 | 276 |
previousAll[Element] 282 | 283 | All of the current element's previous siblings 284 | 285 |
titleString 291 | 292 | The page title 293 | 294 |
298 | 299 | ### Element 300 | 301 | A DOM element. 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 321 | 322 | 323 | 324 | 325 | 330 | 331 | 332 | 333 | 334 | 339 | 340 | 341 | 342 | 343 | 348 | 349 | 350 | 351 | 352 | 357 | 358 | 359 | 360 | 361 | 366 | 367 | 368 | 369 | 370 | 375 | 376 | 377 | 378 | 379 | 384 | 385 | 386 | 387 | 388 | 393 | 394 | 395 | 396 | 397 | 402 | 403 | 404 | 405 | 406 | 411 | 412 | 413 | 414 | 415 | 420 | 421 | 422 | 423 | 424 | 429 | 430 | 431 | 432 | 433 | 438 | 439 | 440 | 441 | 442 | 447 | 448 | 449 | 450 | 451 | 456 | 457 | 458 | 459 | 460 | 465 | 466 | 467 | 468 | 469 | 474 | 475 | 476 | 477 | 478 | 483 | 484 | 485 | 486 | 487 | 492 | 493 | 494 | 495 | 496 | 501 | 502 | 503 | 504 | 505 | 510 | 511 | 512 | 513 | 514 | 519 | 520 | 521 | 522 | 523 | 528 | 529 | 530 | 531 | 532 | 537 | 538 | 539 |
FieldArgumentTypeDescription
contentString 317 | 318 | The HTML content of the subnodes 319 | 320 |
selectorString 326 | 327 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 328 | 329 |
htmlString 335 | 336 | The HTML content of the selected DOM node 337 | 338 |
selectorString 344 | 345 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 346 | 347 |
textString 353 | 354 | The text content of the selected DOM node 355 | 356 |
selectorString 362 | 363 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 364 | 365 |
tagString 371 | 372 | The tag name of the selected DOM node 373 | 374 |
selectorString 380 | 381 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 382 | 383 |
attrString 389 | 390 | An attribute of the selected node (eg. `href`, `src`, etc.). 391 | 392 |
selectorString 398 | 399 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 400 | 401 |
nameString! 407 | 408 | The name of the attribute 409 | 410 |
hasBoolean 416 | 417 | Returns true if an element with the given selector exists. 418 | 419 |
selectorString 425 | 426 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 427 | 428 |
queryElement 434 | 435 | Equivalent to [Element.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). The selectors of any nested queries will be scoped to the resulting element. 436 | 437 |
selectorString 443 | 444 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 445 | 446 |
queryAll[Element] 452 | 453 | Equivalent to [Element.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). The selectors of any nested queries will be scoped to the resulting elements. 454 | 455 |
selectorString 461 | 462 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 463 | 464 |
children[Element] 470 | 471 | An element's child elements. 472 | 473 |
parentElement 479 | 480 | An element's parent element. 481 | 482 |
siblings[Element] 488 | 489 | All elements which are at the same level in the tree as the current element, ie. the children of the current element's parent. Includes the current element. 490 | 491 |
nextElement 497 | 498 | The current element's next sibling. Includes text nodes. Equivalent to [Node.nextSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 499 | 500 |
nextAll[Element] 506 | 507 | All of the current element's next siblings 508 | 509 |
previousElement 515 | 516 | The current element's previous sibling. Includes text nodes. Equivalent to [Node.previousSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 517 | 518 |
previousAll[Element] 524 | 525 | All of the current element's previous siblings 526 | 527 |
visitDocument 533 | 534 | If the element is a link, visit the page linked to in the href attribute. 535 | 536 |
540 | 541 | ## Scalars 542 | 543 | ### Boolean 544 | 545 | The `Boolean` scalar type represents `true` or `false`. 546 | 547 | ### String 548 | 549 | The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text. 550 | 551 | 552 | ## Interfaces 553 | 554 | 555 | ### Node 556 | 557 | A DOM node (either an Element or a Document). 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 577 | 578 | 579 | 580 | 581 | 586 | 587 | 588 | 589 | 590 | 595 | 596 | 597 | 598 | 599 | 604 | 605 | 606 | 607 | 608 | 613 | 614 | 615 | 616 | 617 | 622 | 623 | 624 | 625 | 626 | 631 | 632 | 633 | 634 | 635 | 640 | 641 | 642 | 643 | 644 | 649 | 650 | 651 | 652 | 653 | 658 | 659 | 660 | 661 | 662 | 667 | 668 | 669 | 670 | 671 | 676 | 677 | 678 | 679 | 680 | 685 | 686 | 687 | 688 | 689 | 694 | 695 | 696 | 697 | 698 | 703 | 704 | 705 | 706 | 707 | 712 | 713 | 714 | 715 | 716 | 721 | 722 | 723 | 724 | 725 | 730 | 731 | 732 | 733 | 734 | 739 | 740 | 741 | 742 | 743 | 748 | 749 | 750 | 751 | 752 | 757 | 758 | 759 | 760 | 761 | 766 | 767 | 768 | 769 | 770 | 775 | 776 | 777 | 778 | 779 | 784 | 785 | 786 |
FieldArgumentTypeDescription
contentString 573 | 574 | The HTML content of the subnodes 575 | 576 |
selectorString 582 | 583 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 584 | 585 |
htmlString 591 | 592 | The HTML content of the selected DOM node 593 | 594 |
selectorString 600 | 601 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 602 | 603 |
textString 609 | 610 | The text content of the selected DOM node 611 | 612 |
selectorString 618 | 619 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 620 | 621 |
tagString 627 | 628 | The tag name of the selected DOM node 629 | 630 |
selectorString 636 | 637 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 638 | 639 |
attrString 645 | 646 | An attribute of the selected node (eg. `href`, `src`, etc.). 647 | 648 |
selectorString 654 | 655 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 656 | 657 |
nameString! 663 | 664 | The name of the attribute 665 | 666 |
hasBoolean 672 | 673 | Returns true if an element with the given selector exists. 674 | 675 |
selectorString 681 | 682 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 683 | 684 |
queryElement 690 | 691 | Equivalent to [Element.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). The selectors of any nested queries will be scoped to the resulting element. 692 | 693 |
selectorString 699 | 700 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 701 | 702 |
queryAll[Element] 708 | 709 | Equivalent to [Element.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). The selectors of any nested queries will be scoped to the resulting elements. 710 | 711 |
selectorString 717 | 718 | A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors). 719 | 720 |
children[Element] 726 | 727 | An element's child elements. 728 | 729 |
parentElement 735 | 736 | An element's parent element. 737 | 738 |
siblings[Element] 744 | 745 | All elements which are at the same level in the tree as the current element, ie. the children of the current element's parent. Includes the current element. 746 | 747 |
nextElement 753 | 754 | The current element's next sibling. Includes text nodes. Equivalent to [Node.nextSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 755 | 756 |
nextAll[Element] 762 | 763 | All of the current element's next siblings 764 | 765 |
previousElement 771 | 772 | The current element's previous sibling. Includes text nodes. Equivalent to [Node.previousSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling). 773 | 774 |
previousAll[Element] 780 | 781 | All of the current element's previous siblings 782 | 783 |
787 | -------------------------------------------------------------------------------- /examples/hackernews.graphql: -------------------------------------------------------------------------------- 1 | { 2 | page(url: "http://news.ycombinator.com") { 3 | items: queryAll(selector: "tr.athing") { 4 | rank: text(selector: "td span.rank") 5 | title: text(selector: "td.title a") 6 | sitebit: text(selector: "span.comhead a") 7 | url: attr(selector: "td.title a", name: "href") 8 | attrs: next { 9 | score: text(selector: "span.score") 10 | user: text(selector: "a:first-of-type") 11 | comments: text(selector: "a:nth-of-type(3)") 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /examples/source.graphql: -------------------------------------------------------------------------------- 1 | { 2 | page(source: "

you can pass in HTML!

") { 3 | text(selector: "p") 4 | } 5 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "graphql-scraper", 3 | "version": "1.2.1", 4 | "description": "Extract structured data from the web using GraphQL.", 5 | "author": "harry lachenmayer ", 6 | "license": "MIT", 7 | "main": "build/index.js", 8 | "bin": "bin.js", 9 | "scripts": { 10 | "build": "tsc", 11 | "dev": "tsc -w", 12 | "test": "tsc; ava build/__tests__/", 13 | "generate-docs": "graphql-markdown ./build/index.js > doc/schema.md" 14 | }, 15 | "dependencies": { 16 | "chalk": "^2.3.0", 17 | "execa": "^0.9.0", 18 | "get-stdin": "^5.0.1", 19 | "graphql": "^0.12.3", 20 | "isomorphic-fetch": "^2.2.1", 21 | "jsdom": "^11.5.1", 22 | "minimist": "^1.2.0" 23 | }, 24 | "devDependencies": { 25 | "@types/graphql": "^0.12.0", 26 | "@types/jsdom": "^11.0.4", 27 | "ava": "^0.24.0", 28 | "graphql-markdown": "^3.2.0", 29 | "typescript": "^2.6.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # graphql-scraper 2 | 3 | ![](screenshot.png) 4 | 5 | [GraphQL](http://graphql.org/) lets us query all sorts of graph-shaped data - so why not use it to query the world's most useful graph, **the web**? 6 | 7 | `graphql-scraper` is a command-line tool and reusable GraphQL schema which lets you easily extract data from HTML. 8 | 9 | [**Check out a live demo here.**](https://graphqlbin.com/W6nkfX) You can easily spin up your own by using [`graphql-scraper-server`](https://github.com/lachenmayer/graphql-scraper-server). 10 | 11 | ## The command-line tool 12 | 13 | ``` 14 | npx graphql-scraper 15 | ``` 16 | 17 | or 18 | 19 | ``` 20 | npm install -g graphql-scraper 21 | graphql-scraper 22 | ``` 23 | 24 | Reads a GraphQL query from the path `query-file`, and prints the result. 25 | 26 | If `query-file` is not given, reads the query from stdin. 27 | 28 | ### Command-line options 29 | 30 | - `--json` Returns the result in JSON format, for use in other tools. 31 | - `--help` Prints a help string. 32 | 33 | ### Variables 34 | 35 | Any other named options you pass to the CLI will be used as a [query variable](http://graphql.org/learn/queries/#variables). 36 | 37 | For example, if you want to reuse the same query on several pages, you could write the following query file (`query.graphql`): 38 | 39 | ```graphql 40 | query ExampleQueryWithVariable($page: String) { 41 | page(url: $page) { 42 | items: queryAll(selector: "tr.athing") { 43 | rank: text(selector: "td span.rank") 44 | title: text(selector: "td.title a") 45 | sitebit: text(selector: "span.comhead a") 46 | url: attr(selector: "td.title a", name: "href") 47 | attrs: next { 48 | score: text(selector: "span.score") 49 | user: text(selector: "a:first-of-type") 50 | comments: text(selector: "a:nth-of-type(3)") 51 | } 52 | } 53 | } 54 | } 55 | ``` 56 | 57 | ...and execute the query like this: 58 | 59 | ```bash 60 | graphql-scraper query.graphql --page="https://news.ycombinator.com/" 61 | ``` 62 | 63 | ## The schema 64 | 65 | You can check out an [auto-generated schema description here](doc/schema.md), but I recommend trying out the [graphql-scraper-server](https://github.com/lachenmayer/graphql-scraper-server) example and exploring the types interactively. You can also play around with the schema in the [live demo](https://graphqlbin.com/W6nkfX). 66 | 67 | ## Re-using the schema in your own projects 68 | 69 | The npm package exports the GraphQL schema which is used by the command-line tool. This an instance of graphql-js [`GraphQLSchema`](http://graphql.org/graphql-js/type/#graphqlschema), which you can use anywhere that expects a schema, for example [`apollo-server`](https://www.apollographql.com/docs/apollo-server/) or [`graphql-yoga`](https://github.com/graphcool/graphql-yoga). 70 | 71 | Use `npm install graphql-scraper` or `yarn add graphql-scraper` to add the schema to your project. 72 | 73 | ### Basic example with `graphql` 74 | 75 | ```js 76 | import { graphql } from 'graphql' 77 | import schema from 'graphql-scraper' 78 | // You can also import it as follows: 79 | // const schema = require('graphql-scraper') 80 | 81 | 82 | const query = ` 83 | { 84 | page(url: "http://news.ycombinator.com") { 85 | items: queryAll(selector: "tr.athing") { 86 | rank: text(selector: "td span.rank") 87 | title: text(selector: "td.title a") 88 | sitebit: text(selector: "span.comhead a") 89 | url: attr(selector: "td.title a", name: "href") 90 | attrs: next { 91 | score: text(selector: "span.score") 92 | user: text(selector: "a:first-of-type") 93 | comments: text(selector: "a:nth-of-type(3)") 94 | } 95 | } 96 | } 97 | } 98 | ` 99 | 100 | graphql(schema, query).then(response => { 101 | console.log(response) 102 | }) 103 | ``` 104 | 105 | ## Background 106 | 107 | This project was inspired by [`gdom`](https://github.com/syrusakbary/gdom), which is written in Python and uses the [Graphene](http://graphene-python.org/) GraphQL library. 108 | 109 | If you want to switch over from `gdom`, please note some schema changes: 110 | 111 | - `query(selector: String!)` now only returns a single `Element`, rather than a list (like `document.querySelector`). Added a new `queryAll(selector: String!): [Element]` field, which behaves like `document.querySelectorAll`. 112 | - `is(selector: String!)` is renamed to `has(selector: String!)`. 113 | - `children`, `parent`, `siblings`, `next` etc. no longer have a `selector` argument. If you need to select children with a specific selector, use [child selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/Child_selectors) (`.foo > .bar`). 114 | - `parents` is removed. 115 | - `prev[All]` is renamed to `previous[All]`. 116 | 117 | ## Maintainers 118 | 119 | [@lachenmayer](https://github.com/lachenmayer) 120 | 121 | ## Contribute 122 | 123 | PRs accepted. 124 | 125 | ## License 126 | 127 | MIT © 2018 harry lachenmayer 128 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachenmayer/graphql-scraper/c66c0e15f79155e17679781ba9b3f9edef9c64d9/screenshot.png -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/bin.js.md: -------------------------------------------------------------------------------- 1 | # Snapshot report for `build/__tests__/bin.js` 2 | 3 | The actual snapshot is saved in `bin.js.snap`. 4 | 5 | Generated by [AVA](https://ava.li). 6 | 7 | ## it formats errors nicely 8 | 9 | > Snapshot 1 10 | 11 | `Errors:␊ 12 | { GraphQLError: Syntax Error: Unexpected Name "garbage"␊ 13 | at syntaxError (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/error/syntaxError.js:24:10)␊ 14 | at unexpected (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/language/parser.js:1312:33)␊ 15 | at parseDefinition (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/language/parser.js:152:9)␊ 16 | at parseDocument (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/language/parser.js:110:22)␊ 17 | at parse (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/language/parser.js:38:10)␊ 18 | at graphqlImpl (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/graphql.js:100:34)␊ 19 | at /Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/graphql.js:66:223␊ 20 | at new Promise ()␊ 21 | at graphql (/Users/harry/Documents/Projects/graphql-scraper/node_modules/graphql/graphql.js:63:10)␊ 22 | at main (/Users/harry/Documents/Projects/graphql-scraper/bin.js:78:24)␊ 23 | message: 'Syntax Error: Unexpected Name "garbage"',␊ 24 | locations: [ { line: 1, column: 1 } ],␊ 25 | path: undefined }␊ 26 | Your query has errors, see above.` 27 | 28 | ## it formats output nicely 29 | 30 | > Snapshot 1 31 | 32 | `Data:␊ 33 | { page: { text: 'you can pass in HTML!' } }` 34 | 35 | ## it runs a query from a file 36 | 37 | > Snapshot 1 38 | 39 | '{"data":{"page":{"text":"you can pass in HTML!"}}}' 40 | 41 | ## it runs a query from stdin 42 | 43 | > Snapshot 1 44 | 45 | '{"data":{"page":{"title":"some test site","main":"looking good"}}}' 46 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/bin.js.snap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lachenmayer/graphql-scraper/c66c0e15f79155e17679781ba9b3f9edef9c64d9/src/__tests__/__snapshots__/bin.js.snap -------------------------------------------------------------------------------- /src/__tests__/bin.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import { createServer } from 'http' 3 | const execa = require('execa') 4 | 5 | test('it runs a query from stdin', async t => { 6 | const server = createServer((req, res) => 7 | res.end(` 8 | 9 | 10 | some test site 11 | 12 | 13 |
looking good
14 | 15 | 16 | `) 17 | ) 18 | server.listen(13338) 19 | 20 | const query = ` 21 | { 22 | page(url: "http://localhost:13338") { 23 | title: text(selector: "title") 24 | main: text(selector: "main") 25 | } 26 | } 27 | ` 28 | 29 | const output = await execa.stdout('./bin.js', ['--json'], { input: query }) 30 | t.snapshot(output) 31 | 32 | server.close() 33 | }) 34 | 35 | test('it runs a query from a file', async t => { 36 | const output = await execa.stdout('./bin.js', [ 37 | 'examples/source.graphql', 38 | '--json', 39 | ]) 40 | t.snapshot(output) 41 | }) 42 | 43 | test('it formats output nicely', async t => { 44 | const output = await execa.stdout('./bin.js', ['examples/source.graphql']) 45 | t.snapshot(output) 46 | }) 47 | 48 | test('it formats errors nicely', async t => { 49 | const output = await execa.stdout('./bin.js', { input: 'garbage' }) 50 | t.snapshot(output) 51 | }) 52 | -------------------------------------------------------------------------------- /src/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import test from 'ava' 2 | import * as GraphQL from 'graphql' 3 | const { graphql } = GraphQL 4 | import { createServer } from 'http' 5 | 6 | import schema from '../' 7 | const requiredSchema = require('../') 8 | 9 | test('es6 & commonjs imports work', t => { 10 | t.is(schema, requiredSchema) 11 | }) 12 | 13 | test('no args throws errors', async t => { 14 | const query = `{ page { title } }` 15 | const response = await graphql(schema, query) 16 | t.is( 17 | response && response.errors && response.errors[0].message, 18 | 'You need to provide either a URL or a HTML source string.' 19 | ) 20 | }) 21 | 22 | test('title', async t => { 23 | const html = `some title` 24 | const query = `{ page(source: "${html}") { title } }` 25 | const response = await graphql(schema, query) 26 | t.false('errors' in response) 27 | t.is(response.data && response.data.page.title, 'some title') 28 | }) 29 | 30 | test('from url', async t => { 31 | const server = createServer((req, res) => { 32 | res.end(`some title`) 33 | }) 34 | server.listen(13337) 35 | const query = `{ page(url: "http://localhost:13337/") { title } }` 36 | const response = await graphql(schema, query) 37 | t.false('errors' in response) 38 | t.is(response.data && response.data.page.title, 'some title') 39 | server.close() 40 | }) 41 | 42 | test('content', async t => { 43 | const html = `some titlesome body` 44 | const query = `{ page(source: "${html}") { content } }` 45 | const response = await graphql(schema, query) 46 | t.false('errors' in response) 47 | t.is( 48 | response.data && response.data.page.content, 49 | 'some titlesome body' 50 | ) 51 | }) 52 | 53 | test('content with selector', async t => { 54 | const html = `some title
bad
` 55 | const query = `{ 56 | page(source: "${html}") { 57 | content(selector: ".selectme") 58 | } 59 | }` 60 | const response = await graphql(schema, query) 61 | t.false('errors' in response) 62 | t.is(response.data && response.data.page.content, 'bad') 63 | }) 64 | 65 | test('not existing selector', async t => { 66 | const html = `some title
bad
` 67 | const query = `{ 68 | page(source: "${html}") { 69 | content(selector: ".selectmenot") 70 | } 71 | }` 72 | const response = await graphql(schema, query) 73 | t.false('errors' in response) 74 | t.is(response.data && response.data.page.content, null) 75 | }) 76 | 77 | test('html', async t => { 78 | const html = `some titlesome body` 79 | const query = `{ page(source: "${html}") { html } }` 80 | const response = await graphql(schema, query) 81 | t.false('errors' in response) 82 | t.is(response.data && response.data.page.html, html) 83 | }) 84 | 85 | test('html with selector', async t => { 86 | const html = `some title
bad
` 87 | const query = `{ 88 | page(source: "${html}") { 89 | html(selector: ".selectme") 90 | } 91 | }` 92 | const response = await graphql(schema, query) 93 | t.false('errors' in response) 94 | t.is( 95 | response.data && response.data.page.html, 96 | '
bad
' 97 | ) 98 | }) 99 | 100 | test('text', async t => { 101 | const html = `some title
bad
` 102 | const query = `{ 103 | page(source: "${html}") { 104 | text 105 | } 106 | }` 107 | const response = await graphql(schema, query) 108 | t.false('errors' in response) 109 | t.is(response.data && response.data.page.text, 'some titlebad') 110 | }) 111 | 112 | test('text with selector', async t => { 113 | const html = `some title
bad
` 114 | const query = `{ 115 | page(source: "${html}") { 116 | text(selector: ".selectme") 117 | } 118 | }` 119 | const response = await graphql(schema, query) 120 | t.false('errors' in response) 121 | t.is(response.data && response.data.page.text, 'bad') 122 | }) 123 | 124 | test('tag', async t => { 125 | const html = `some title
bad
` 126 | const query = `{ 127 | page(source: "${html}") { 128 | tag 129 | } 130 | }` 131 | const response = await graphql(schema, query) 132 | t.false('errors' in response) 133 | t.is(response.data && response.data.page.tag, 'HTML') 134 | }) 135 | 136 | test('tag with selector', async t => { 137 | const html = `some title
bad
` 138 | const query = `{ 139 | page(source: "${html}") { 140 | tag(selector: ".selectme") 141 | } 142 | }` 143 | const response = await graphql(schema, query) 144 | t.false('errors' in response) 145 | t.is(response.data && response.data.page.tag, 'DIV') 146 | }) 147 | 148 | test('attr', async t => { 149 | const html = `some title
bad
` 150 | const query = `{ 151 | page(source: "${html}") { 152 | attr(name: "style") 153 | } 154 | }` 155 | const response = await graphql(schema, query) 156 | t.false('errors' in response) 157 | t.is(response.data && response.data.page.attr, 'background: red;') 158 | }) 159 | 160 | test('wacky attr', async t => { 161 | const html = `some title
bad
` 162 | const query = `{ 163 | page(source: "${html}") { 164 | attr(name: "asdf") 165 | } 166 | }` 167 | const response = await graphql(schema, query) 168 | t.false('errors' in response) 169 | t.is(response.data && response.data.page.attr, null) 170 | }) 171 | 172 | test('attr with selector', async t => { 173 | const html = `some title
bad
` 174 | const query = `{ 175 | page(source: "${html}") { 176 | attr(selector: ".selectme", name: "class") 177 | } 178 | }` 179 | const response = await graphql(schema, query) 180 | t.false('errors' in response) 181 | t.is(response.data && response.data.page.attr, 'selectme') 182 | }) 183 | 184 | test('has', async t => { 185 | const html = `some title
one
two
` 186 | const query = `{ 187 | page(source: "${html}") { 188 | firstDiv: query(selector: "div") { 189 | isStrong: has(selector: "strong") 190 | } 191 | } 192 | }` 193 | const response = await graphql(schema, query) 194 | t.false('errors' in response) 195 | t.true(response.data && response.data.page.firstDiv.isStrong) 196 | }) 197 | 198 | test('has not', async t => { 199 | const html = `some title
one
two
` 200 | const query = `{ 201 | page(source: "${html}") { 202 | firstDiv: query(selector: "div") { 203 | isWeak: has(selector: "weak") 204 | } 205 | } 206 | }` 207 | const response = await graphql(schema, query) 208 | t.false('errors' in response) 209 | t.true(response.data && !response.data.page.firstDiv.isWeak) 210 | }) 211 | 212 | test('query', async t => { 213 | const html = `some title
one
two
` 214 | const query = `{ 215 | page(source: "${html}") { 216 | firstDiv: query(selector: "div") { 217 | text 218 | } 219 | } 220 | }` 221 | const response = await graphql(schema, query) 222 | t.false('errors' in response) 223 | t.deepEqual(response.data && response.data.page.firstDiv, { text: 'one' }) 224 | }) 225 | 226 | test('queryAll', async t => { 227 | const html = `some title
one
two
` 228 | const query = `{ 229 | page(source: "${html}") { 230 | divs: queryAll(selector: "div") { 231 | text 232 | } 233 | } 234 | }` 235 | const response = await graphql(schema, query) 236 | t.false('errors' in response) 237 | t.deepEqual(response.data && response.data.page.divs, [ 238 | { text: 'one' }, 239 | { text: 'two' }, 240 | ]) 241 | }) 242 | 243 | test('children', async t => { 244 | const html = `some title
onetwo
twothree
` 245 | const query = `{ 246 | page(source: "${html}") { 247 | kids: queryAll(selector: "div") { 248 | children { 249 | text 250 | } 251 | } 252 | } 253 | }` 254 | const response = await graphql(schema, query) 255 | t.false('errors' in response) 256 | t.deepEqual(response.data && response.data.page.kids, [ 257 | { 258 | children: [{ text: 'one' }, { text: 'two' }], 259 | }, 260 | { 261 | children: [{ text: 'two' }, { text: 'three' }], 262 | }, 263 | ]) 264 | }) 265 | 266 | test('childNodes', async t => { 267 | const html = `some title
onetwo
twoamazingthree
` 268 | const query = `{ 269 | page(source: "${html}") { 270 | kids: queryAll(selector: "div") { 271 | childNodes { 272 | text 273 | } 274 | } 275 | } 276 | }` 277 | const response = await graphql(schema, query) 278 | t.false('errors' in response) 279 | t.deepEqual(response.data && response.data.page.kids, [ 280 | { 281 | childNodes: [{ text: 'one' }, { text: 'two' }], 282 | }, 283 | { 284 | childNodes: [{ text: 'two' }, {text: 'amazing'}, { text: 'three' }], 285 | }, 286 | ]) 287 | }) 288 | 289 | test('parent', async t => { 290 | const html = `some title
bad
` 291 | const query = `{ 292 | page(source: "${html}") { 293 | query(selector: "strong") { 294 | parent { 295 | attr(name: "class") 296 | } 297 | } 298 | } 299 | }` 300 | const response = await graphql(schema, query) 301 | t.false('errors' in response) 302 | t.is(response.data && response.data.page.query.parent.attr, 'selectme') 303 | }) 304 | 305 | test('siblings', async t => { 306 | const html = `some title
bad

boom

bap
` 307 | const query = `{ 308 | page(source: "${html}") { 309 | query(selector: "strong") { 310 | siblings { 311 | text 312 | } 313 | } 314 | } 315 | }` 316 | const response = await graphql(schema, query) 317 | t.false('errors' in response) 318 | t.deepEqual(response.data && response.data.page.query.siblings, [ 319 | { text: 'bad' }, 320 | { text: 'boom' }, 321 | { text: 'bap' }, 322 | ]) 323 | }) 324 | 325 | test('siblings of root is only html', async t => { 326 | const html = `nothing to see here` 327 | const query = `{ 328 | page(source: "${html}") { 329 | siblings { 330 | tag 331 | } 332 | } 333 | }` 334 | const response = await graphql(schema, query) 335 | t.false('errors' in response) 336 | t.deepEqual(response.data && response.data.page.siblings, [{ tag: 'HTML' }]) 337 | }) 338 | 339 | test('next', async t => { 340 | const html = `some title
bad

boom

bap
` 341 | const query = `{ 342 | page(source: "${html}") { 343 | query(selector: "strong") { 344 | next { 345 | text 346 | } 347 | } 348 | } 349 | }` 350 | const response = await graphql(schema, query) 351 | t.false('errors' in response) 352 | t.is(response.data && response.data.page.query.next.text, 'boom') 353 | }) 354 | 355 | test('next - bare text', async t => { 356 | const html = `some title
badbare textbap
` 357 | const query = `{ 358 | page(source: "${html}") { 359 | query(selector: "strong") { 360 | next { 361 | tag 362 | text 363 | } 364 | } 365 | } 366 | }` 367 | const response = await graphql(schema, query) 368 | t.false('errors' in response) 369 | t.is(response.data && response.data.page.query.next.tag, null) 370 | t.is(response.data && response.data.page.query.next.text, 'bare text') 371 | }) 372 | 373 | test('nextAll', async t => { 374 | const html = `some title
badbare textbap
` 375 | const query = `{ 376 | page(source: "${html}") { 377 | query(selector: "strong") { 378 | nextAll { 379 | tag 380 | text 381 | } 382 | } 383 | } 384 | }` 385 | const response = await graphql(schema, query) 386 | t.false('errors' in response) 387 | t.deepEqual(response.data && response.data.page.query.nextAll, [ 388 | { tag: null, text: 'bare text' }, 389 | { tag: 'SPAN', text: 'bap' }, 390 | ]) 391 | }) 392 | 393 | test('previous', async t => { 394 | const html = `some title
bad

boom

bap
` 395 | const query = `{ 396 | page(source: "${html}") { 397 | query(selector: "span") { 398 | previous { 399 | text 400 | } 401 | } 402 | } 403 | }` 404 | const response = await graphql(schema, query) 405 | t.false('errors' in response) 406 | t.is(response.data && response.data.page.query.previous.text, 'boom') 407 | }) 408 | 409 | test('previousAll', async t => { 410 | const html = `some title
badbare textbap
` 411 | const query = `{ 412 | page(source: "${html}") { 413 | query(selector: "span") { 414 | previousAll { 415 | tag 416 | text 417 | } 418 | } 419 | } 420 | }` 421 | const response = await graphql(schema, query) 422 | t.false('errors' in response) 423 | t.deepEqual(response.data && response.data.page.query.previousAll, [ 424 | { tag: 'STRONG', text: 'bad' }, 425 | { tag: null, text: 'bare text' }, 426 | ]) 427 | }) 428 | 429 | test('visit', async t => { 430 | const server = createServer((req, res) => { 431 | if (req.url === '/link') { 432 | res.end( 433 | `we managed to visit the link!` 434 | ) 435 | } else { 436 | res.end(`come on in`) 437 | } 438 | }) 439 | server.listen(13339) 440 | 441 | const query = `{ 442 | page(url: "http://localhost:13339/") { 443 | link: query(selector: "a") { 444 | visit { 445 | text(selector: "strong") 446 | } 447 | } 448 | } 449 | }` 450 | const response = await graphql(schema, query) 451 | t.false('errors' in response) 452 | t.is( 453 | response.data && response.data.page.link.visit.text, 454 | 'we managed to visit the link!' 455 | ) 456 | server.close() 457 | }) 458 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphQLSchema, 3 | GraphQLString, 4 | GraphQLBoolean, 5 | GraphQLObjectType, 6 | GraphQLNonNull, 7 | GraphQLInterfaceType, 8 | GraphQLList, 9 | GraphQLFieldConfigMap, 10 | GraphQLObjectTypeConfig, 11 | GraphQLInterfaceTypeConfig, 12 | } from 'graphql' 13 | import { JSDOM } from 'jsdom' 14 | import { resolve } from 'url' 15 | 16 | function sharedFields(): GraphQLFieldConfigMap { 17 | const selector = { 18 | type: GraphQLString, 19 | description: 20 | 'A [CSS selector](https://developer.mozilla.org/en-US/docs/Learn/CSS/Introduction_to_CSS/Selectors).', 21 | } 22 | return { 23 | content: { 24 | type: GraphQLString, 25 | description: 'The HTML content of the subnodes', 26 | args: { selector }, 27 | resolve(element, { selector }) { 28 | element = selector ? element.querySelector(selector) : element 29 | return element && element.innerHTML 30 | }, 31 | }, 32 | html: { 33 | type: GraphQLString, 34 | description: 'The HTML content of the selected DOM node', 35 | args: { selector }, 36 | resolve(element, { selector }) { 37 | element = selector ? element.querySelector(selector) : element 38 | return element && element.outerHTML 39 | }, 40 | }, 41 | text: { 42 | type: GraphQLString, 43 | description: 'The text content of the selected DOM node', 44 | args: { selector }, 45 | resolve(element, { selector }) { 46 | element = selector ? element.querySelector(selector) : element 47 | return element && element.textContent 48 | }, 49 | }, 50 | tag: { 51 | type: GraphQLString, 52 | description: 'The tag name of the selected DOM node', 53 | args: { selector }, 54 | resolve(element, { selector }) { 55 | element = selector ? element.querySelector(selector) : element 56 | return element && element.tagName 57 | }, 58 | }, 59 | attr: { 60 | type: GraphQLString, 61 | description: 62 | 'An attribute of the selected node (eg. `href`, `src`, etc.).', 63 | args: { 64 | selector, 65 | name: { 66 | type: new GraphQLNonNull(GraphQLString), 67 | description: 'The name of the attribute', 68 | }, 69 | }, 70 | resolve(element, { selector, name }) { 71 | element = selector ? element.querySelector(selector) : element 72 | if (element == null) return null 73 | const attribute = element.attributes[name] 74 | if (attribute == null) return null 75 | return attribute.value 76 | }, 77 | }, 78 | has: { 79 | type: GraphQLBoolean, 80 | description: 'Returns true if an element with the given selector exists.', 81 | args: { selector }, 82 | resolve(element, { selector }) { 83 | return !!element.querySelector(selector) 84 | }, 85 | }, 86 | query: { 87 | type: ElementType, 88 | description: 89 | 'Equivalent to [Element.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelector). The selectors of any nested queries will be scoped to the resulting element.', 90 | args: { selector }, 91 | resolve(element, { selector }) { 92 | return element.querySelector(selector) 93 | }, 94 | }, 95 | queryAll: { 96 | type: new GraphQLList(ElementType), 97 | description: 98 | 'Equivalent to [Element.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Element/querySelectorAll). The selectors of any nested queries will be scoped to the resulting elements.', 99 | args: { selector }, 100 | resolve(element, { selector }) { 101 | return Array.from(element.querySelectorAll(selector)) 102 | }, 103 | }, 104 | children: { 105 | type: new GraphQLList(ElementType), 106 | description: "An element's child elements.", 107 | resolve(element) { 108 | return Array.from(element.children) 109 | }, 110 | }, 111 | childNodes: { 112 | type: new GraphQLList(ElementType), 113 | description: "An element's child nodes. Includes text nodes.", 114 | resolve(element) { 115 | return Array.from(element.childNodes) 116 | } 117 | }, 118 | parent: { 119 | type: ElementType, 120 | description: "An element's parent element.", 121 | resolve(element) { 122 | return element.parentElement 123 | }, 124 | }, 125 | siblings: { 126 | type: new GraphQLList(ElementType), 127 | description: 128 | "All elements which are at the same level in the tree as the current element, ie. the children of the current element's parent. Includes the current element.", 129 | resolve(element) { 130 | const parent = element.parentElement 131 | if (parent == null) return [element] 132 | return Array.from(parent.children) 133 | }, 134 | }, 135 | next: { 136 | type: ElementType, 137 | description: 138 | "The current element's next sibling. Includes text nodes. Equivalent to [Node.nextSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling).", 139 | resolve(element) { 140 | return element.nextSibling 141 | }, 142 | }, 143 | nextAll: { 144 | type: new GraphQLList(ElementType), 145 | description: "All of the current element's next siblings", 146 | resolve(element, { selector }) { 147 | const siblings = [] 148 | for ( 149 | let next = element.nextSibling; 150 | next != null; 151 | next = next.nextSibling 152 | ) { 153 | siblings.push(next) 154 | } 155 | return siblings 156 | }, 157 | }, 158 | previous: { 159 | type: ElementType, 160 | description: 161 | "The current element's previous sibling. Includes text nodes. Equivalent to [Node.previousSibling](https://developer.mozilla.org/en-US/docs/Web/API/Node/nextSibling).", 162 | resolve(element) { 163 | return element.previousSibling 164 | }, 165 | }, 166 | previousAll: { 167 | type: new GraphQLList(ElementType), 168 | description: "All of the current element's previous siblings", 169 | resolve(element, { selector }) { 170 | const siblings = [] 171 | for ( 172 | let previous = element.previousSibling; 173 | previous != null; 174 | previous = previous.previousSibling 175 | ) { 176 | siblings.push(previous) 177 | } 178 | siblings.reverse() 179 | return siblings 180 | }, 181 | }, 182 | } 183 | } 184 | 185 | const NodeType = new GraphQLInterfaceType(>{ 189 | name: 'Node', 190 | description: 'A DOM node (either an Element or a Document).', 191 | fields: sharedFields, 192 | }) 193 | 194 | const DocumentType = new GraphQLObjectType(>{ 198 | name: 'Document', 199 | description: 'A DOM document.', 200 | interfaces: [NodeType], 201 | fields: () => ({ 202 | ...sharedFields(), 203 | title: { 204 | type: GraphQLString, 205 | description: 'The page title', 206 | resolve(element) { 207 | return element.ownerDocument.title 208 | }, 209 | }, 210 | }), 211 | }) 212 | 213 | const ElementType = new GraphQLObjectType(>{ 217 | name: 'Element', 218 | description: 'A DOM element.', 219 | interfaces: [NodeType], 220 | fields: () => ({ 221 | ...sharedFields(), 222 | visit: { 223 | type: DocumentType, 224 | description: 225 | 'If the element is a link, visit the page linked to in the href attribute.', 226 | async resolve(element) { 227 | const href = element.attributes['href'] 228 | if (href == null) { 229 | return null 230 | } 231 | const url = resolve(element.ownerDocument.location.href, href.value) // handle relative links. 232 | const dom = await JSDOM.fromURL(url) 233 | return dom.window.document.documentElement 234 | }, 235 | }, 236 | }), 237 | }) 238 | 239 | const schema = new GraphQLSchema({ 240 | query: new GraphQLObjectType(>{ 241 | name: 'Query', 242 | fields: () => ({ 243 | page: { 244 | type: DocumentType, 245 | args: { 246 | url: { 247 | type: GraphQLString, 248 | description: 'A URL to fetch the HTML source from.', 249 | }, 250 | source: { 251 | type: GraphQLString, 252 | description: 253 | 'A string containing HTML to be used as the source document.', 254 | }, 255 | }, 256 | async resolve(_, { url, source }) { 257 | if (url == null && source == null) { 258 | throw new Error( 259 | 'You need to provide either a URL or a HTML source string.' 260 | ) 261 | } 262 | const dom = url != null ? await JSDOM.fromURL(url) : new JSDOM(source) 263 | return dom.window.document.documentElement 264 | }, 265 | }, 266 | }), 267 | }), 268 | }) 269 | 270 | // Make this importable with ES6 271 | schema['default'] = schema 272 | export = schema 273 | -------------------------------------------------------------------------------- /src/types.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | page(url: String, html: String): Document 3 | } 4 | 5 | # A DOM Node 6 | interface Node { 7 | # The HTML representation of the subnodes 8 | content(selector: String): String 9 | # The HTML representation of the selected DOM node 10 | html(selector: String): String 11 | # The text content of the selected DOM node 12 | text(selector: String): String 13 | # The tag name of the selected DOM node 14 | tag(selector: String): String 15 | # The attribute with the given name of the selected DOM node 16 | attr(selector: String, name: String!): String 17 | # Returns true if the DOM matches the selector 18 | has(selector: String!): Boolean 19 | 20 | query(selector: String!): [Element] 21 | children: [Element] 22 | parent: Element 23 | siblings: [Element] 24 | # The immediately following sibling 25 | next(selector: String): Element 26 | # All following siblings 27 | nextAll(selector: String): [Element] 28 | # The immediately previous sibling 29 | previous(selector: String): Element 30 | # All previous siblings 31 | previousAll(selector: String): [Element] 32 | } 33 | 34 | type Document implements Node { 35 | title: String 36 | } 37 | 38 | type Element implements Node { 39 | # If the element is a link, visit the page linked to in the href attribute. 40 | visit: Document 41 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "noImplicitAny": true, 8 | "noUnusedLocals": true, 9 | "outDir": "./build", 10 | "preserveConstEnums": true, 11 | "removeComments": false, 12 | "sourceMap": true, 13 | "strictNullChecks": true, 14 | "suppressImplicitAnyIndexErrors": true, 15 | "target": "es2015" 16 | }, 17 | "include": ["src/**/*"], 18 | "exclude": ["node_modules"] 19 | } 20 | --------------------------------------------------------------------------------