chain.js

import { api } from './util';

const processors = {
	extracts: data => {
		return { extract: data.extract };
	},
	links: data => {
		return { links: data.links.map(e => e.title) };
	},
	extlinks: data => {
		return { extlinks: data.extlinks.map(e => e['*']) };
	},
	langlinks: data => {
		return {
			langlinks: data.langlinks.map(link => {
				return {
					lang: link.lang,
					title: link['*'],
					url: link.url
				};
			})
		};
	},
	coordinates: data => {
		if (data.coordinates) {
			return { coordinates: data.coordinates[0] };
		} else {
			return {};
		}
	},
	categories: data => {
		return { categories: data.categories.map(e => e.title) };
	},
	pageimages: data => {
		return {
			image: {
				name: data.pageimage,
				thumbnail: data.thumbnail,
				original: data.original
			}
		};
	}
};

function process(props, rawPageData) {
	const data = { title: rawPageData.title };
	return props.reduce((memo, prop) => {
		if (processors[prop]) Object.assign(memo, processors[prop](rawPageData));
		return memo;
	}, data);
}

/**
 * Chain API requests together
 * @example
 * // Get page summary and images in same request
 * wiki.page('batman').then(page => page.chain().summary().image().request()).then(console.log);
 * @namespace QueryChain
 */
export default class QueryChain {
	constructor(apiOptions, id) {
		this.id = id;
		this.apiOptions = apiOptions;
		this._params = { pageids: id };
		this.props = new Set();
	}

	params() {
		const prop = [...this.props].join('|');
		return Object.assign({}, this._params, { prop });
	}

	direct(key, ...args) {
		return this[key](...args)
			.request()
			.then(res => res[key]);
	}

	// TODO: Add page searches for root calls - generators

	// TODO: Add pagination helper method

	/**
	 * Make combined API request
	 * @method QueryChain#request
	 * @returns {Object|Array} - Data object(s) depending on where the chain was created from
	 */
	request() {
		const props = [...this.props];
		return api(this.apiOptions, this.params())
			.then(res => {
				if (this.id) {
					return res.query.pages[this.id];
				} else {
					return Object.values(res.query.pages);
				}
			})
			.then(data => {
				if (Array.isArray(data)) {
					return data.map(e => process(props, e));
				} else {
					return process(props, data);
				}
			});
	}

	chain(prop, params = {}) {
		if (prop) {
			this.props.add(prop);
		}
		Object.assign(this._params, params);
		return this;
	}

	/**
	 * @summary Finds pages near a specific point
	 * @method QueryChain#geosearch
	 * @returns {QueryChain}
	 */
	geosearch(latitude, longitude, radius) {
		return this.chain(undefined, {
			generator: 'geosearch',
			ggsradius: radius,
			ggscoord: `${latitude}|${longitude}`
		});
	}

	search(query, limit = 50) {
		return this.chain(undefined, {
			list: 'search',
			srsearch: query,
			srlimit: limit
		});
	}

	/**
	 * @summary Useful for extracting structured section content
	 * @method QueryChain#content
	 * @returns {QueryChain}
	 */
	content() {
		return this.chain('extracts', {
			explaintext: '1'
		});
	}

	/**
	 * @summary Useful for extracting summary content
	 * @method QueryChain#summary
	 * @returns {QueryChain}
	 */
	summary() {
		return this.chain('extracts', {
			explaintext: '1',
			exintro: '1'
		});
	}

	/**
	 * @summary Extract image
	 * @method QueryChain#image
	 * @returns {QueryChain}
	 */
	image(types = { thumbnail: true, original: false, name: true }) {
		return this.chain('pageimages', {
			piprop: Object.keys(types)
				.filter(k => types[k])
				.join('|')
		});
	}

	/**
	 * @summary Extract external links
	 * @method QueryChain#extlinks
	 * @returns {QueryChain}
	 */
	extlinks() {
		return this.chain('extlinks', {
			ellimit: 'max'
		});
	}

	/**
	 * @summary Extract page links
	 * @method QueryChain#links
	 * @returns {QueryChain}
	 */
	links(limit = 100) {
		return this.chain('links', {
			plnamespace: 0,
			pllimit: limit
		});
	}

	/**
	 * @summary Extract categories
	 * @method QueryChain#categories
	 * @returns {QueryChain}
	 */
	categories(limit = 100) {
		return this.chain('categories', {
			pllimit: limit
		});
	}

	/**
	 * @summary Extract coordinates
	 * @method QueryChain#coordinates
	 * @returns {QueryChain}
	 */
	coordinates() {
		return this.chain('coordinates');
	}

	/**
	 * @summary Get list of links to different translations
	 * @method QueryChain#langlinks
	 * @returns {QueryChain}
	 */
	langlinks() {
		return this.chain('langlinks', {
			lllimit: 'max',
			llprop: 'url'
		});
	}
}