├── .gitignore ├── .prettierrc ├── cache.js ├── package.json ├── LICENSE ├── index.js ├── README.md └── scrapper.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | yarn.lock 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "printWidth": 120, 5 | "singleQuote": true, 6 | "trailingComma": "es5" 7 | } 8 | -------------------------------------------------------------------------------- /cache.js: -------------------------------------------------------------------------------- 1 | const responses = [] 2 | 3 | const cache = async (ms, func, req, res) => { 4 | const previous = responses[req.url] 5 | if (previous && (!previous.date || previous.date > new Date())) { 6 | return previous.data 7 | } 8 | 9 | const data = await func(req, res) 10 | responses[req.url] = { 11 | data, 12 | date: ms && new Date(new Date().getTime() + ms), 13 | } 14 | return data 15 | } 16 | 17 | module.exports = cache 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archillect-unoffcial-api", 3 | "author": "lndgalante ", 4 | "version": "1.0.0", 5 | "license": "MIT", 6 | "main": "index.js", 7 | "repository": "https://github.com/lndgalante/archillect-unoffcial-api.git", 8 | "dependencies": { 9 | "micro": "^9.3.4", 10 | "micro-cors": "^0.1.1", 11 | "microrouter": "^3.1.3", 12 | "ms": "^2.1.2", 13 | "scrape-it": "^5.2.0" 14 | }, 15 | "devDependencies": { 16 | "micro-dev": "^3.0.0" 17 | }, 18 | "scripts": { 19 | "dev": "micro-dev", 20 | "start": "micro" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Leonardo Galante 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { send } = require('micro') 2 | const cors = require('micro-cors')() 3 | const { router, get } = require('microrouter') 4 | const ms = require('ms') 5 | 6 | const scrapper = require('./scrapper') 7 | const cache = require('./cache') 8 | 9 | const visuals = async (req, res) => { 10 | const data = await cache( 11 | ms('10m'), 12 | async req => { 13 | try { 14 | return await scrapper.visualsRoute(req.query.per) 15 | } catch (error) { 16 | return null 17 | } 18 | }, 19 | req, 20 | res 21 | ) 22 | 23 | if (!data) { 24 | send(res, 500, 'Internal Server Error') 25 | } else { 26 | send(res, 200, data) 27 | } 28 | } 29 | 30 | const random = async (req, res) => { 31 | const data = await cache( 32 | ms('10m'), 33 | async () => { 34 | try { 35 | return await scrapper.randomRoute() 36 | } catch (error) { 37 | return null 38 | } 39 | }, 40 | req, 41 | res 42 | ) 43 | 44 | if (!data) { 45 | send(res, 500, 'Internal Server Error') 46 | } else { 47 | send(res, 200, data) 48 | } 49 | } 50 | 51 | const id = async (req, res) => { 52 | // Cache with 'null' ms -- means cache forever 53 | const data = await cache( 54 | null, 55 | async req => { 56 | try { 57 | return await scrapper.idRoute(req.params.id) 58 | } catch (error) { 59 | return null 60 | } 61 | }, 62 | req, 63 | res 64 | ) 65 | 66 | if (!data) { 67 | send(res, 500, 'Internal Server Error') 68 | } else { 69 | send(res, 200, data) 70 | } 71 | } 72 | 73 | const notFound = (req, res) => { 74 | send(res, 404, { 75 | error: 'Not found route', 76 | docs: 'https://github.com/lndgalante/archillect-api', 77 | }) 78 | } 79 | 80 | module.exports = router( 81 | get('/visuals', cors(visuals)), 82 | get('/visuals/:id', cors(id)), 83 | get('/random', cors(random)), 84 | get('/*', cors(notFound)) 85 | ) 86 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archillect Unofficial API 2 | 3 | ### Get a list of visuals 4 | 5 | It brings a list of the lastest 20 visuals by default 6 | 7 | > GET [https://archillect-api.now.sh/visuals](https://archillect-api.now.sh/visuals) 8 | 9 | Example response: 10 | 11 | ```json 12 | [ 13 | { 14 | "sourceLinks": [ 15 | "https://www.google.com/searchbyimage?safe=offℑurl=https://78.media.tumblr.com/49424df2098547088e82e50b7d20bcc2/tumblr_ort7nwa1EO1sr19s8o1_1280.jpg", 16 | "http://new-brutalism.tumblr.com/post/162015765471/preston-bus-station-2-keith-ingham-and-charles" 17 | ], 18 | "imageSource": "https://78.media.tumblr.com/49424df2098547088e82e50b7d20bcc2/tumblr_ort7nwa1EO1sr19s8o1_1280.jpg", 19 | "original": "http://archillect.com/184152", 20 | "id": 184152 21 | }, 22 | ... 23 | ] 24 | ``` 25 | 26 | ### Get a list of a number of visuals 27 | 28 | You could also ask for a number of the latest visuals through the `per` query. 29 | With 200 maximum. 30 | 31 | > GET [https://archillect-api.now.sh/visuals?per=120](https://archillect-api.now.sh/visuals?per=120) 32 | 33 | ### Get an id 34 | 35 | Brings one visual id specified 36 | 37 | > GET [https://archillect-api.now.sh/visuals/147836](https://archillect-api.now.sh/visuals/147836) 38 | 39 | Example esponse: 40 | 41 | ```json 42 | { 43 | "sourceLinks": [ 44 | "https://www.google.com/searchbyimage?safe=offℑurl=http://78.media.tumblr.com/a06af535eb801c32ff60c5dbb0031d13/tumblr_olhnlsSjnS1vczpxxo1_400.gif" 45 | ], 46 | "imageSource": "http://78.media.tumblr.com/a06af535eb801c32ff60c5dbb0031d13/tumblr_olhnlsSjnS1vczpxxo1_400.gif", 47 | "original": "http://archillect.com/147836", 48 | "id": 147836 49 | } 50 | ``` 51 | 52 | ### Get a random 53 | 54 | Brings one random visual 55 | 56 | > GET [https://archillect-api.now.sh/random](https://archillect-api.now.sh/random) 57 | 58 | Example esponse: 59 | 60 | ```json 61 | { 62 | "sourceLinks": [ 63 | "https://www.google.com/searchbyimage?safe=offℑurl=http://66.media.tumblr.com/937d59f3248ec51df8641332e9aa61d9/tumblr_ncb4fpPq0t1r94dw8o1_1280.jpg", 64 | "http://www.yellowtrace.com.au/fearon-hay-architects-harbour-edge-house/", 65 | "http://kazu721010.tumblr.com/post/98145993655/harbour-edge-house-fearon-hay-architects" 66 | ], 67 | "imageSource": "http://66.media.tumblr.com/937d59f3248ec51df8641332e9aa61d9/tumblr_ncb4fpPq0t1r94dw8o1_1280.jpg", 68 | "original": "http://archillect.com/74786", 69 | "id": 74786 70 | } 71 | ``` 72 | 73 | #### License 74 | 75 | MIT © **[`Leonardo Galante`](https://leonardogalante.com)** 76 | -------------------------------------------------------------------------------- /scrapper.js: -------------------------------------------------------------------------------- 1 | const scrapeIt = require('scrape-it') 2 | 3 | class ArchillectScrapper { 4 | constructor() { 5 | this.baseUrl = 'http://archillect.com' 6 | this.min = 1 7 | } 8 | 9 | async getMax() { 10 | const res = await scrapeIt(this.baseUrl, { 11 | max: { 12 | selector: 'div#container > a', 13 | attr: 'href', 14 | convert: x => x.replace('/', ''), 15 | }, 16 | }) 17 | 18 | return Number(res.max) 19 | } 20 | 21 | async getImageSources(id) { 22 | const res = await scrapeIt(`${this.baseUrl}/${id}`, { 23 | imageSource: { 24 | selector: 'img#ii', 25 | attr: 'src', 26 | }, 27 | sourceLinks: { 28 | listItem: '#sources a', 29 | data: { 30 | content: { 31 | attr: 'href', 32 | }, 33 | }, 34 | }, 35 | }) 36 | 37 | const resParsed = { 38 | ...res, 39 | sourceLinks: res.sourceLinks.map(sourceLink => sourceLink.content.replace(/ℑ/g, '&image_')), 40 | } 41 | 42 | return resParsed 43 | } 44 | 45 | getRandomNumber(max) { 46 | return Math.floor(Math.random() * max) + 1 47 | } 48 | 49 | async randomRoute() { 50 | const max = await this.getMax() 51 | const id = this.getRandomNumber(max) 52 | const { imageSource, sourceLinks } = await this.getImageSources(id) 53 | const original = `${this.baseUrl}/${id}` 54 | 55 | return { 56 | sourceLinks, 57 | imageSource, 58 | original, 59 | id, 60 | } 61 | } 62 | 63 | async idRoute(id) { 64 | id = Number(id) 65 | if (!Number.isInteger(id)) return { error: `The id should be an integer` } 66 | 67 | const max = await this.getMax() 68 | if (id < this.min || id > max) return { error: `The id ${id} should be between ${this.min} and ${max}` } 69 | 70 | const { imageSource, sourceLinks } = await this.getImageSources(id) 71 | const original = `${this.baseUrl}/${id}` 72 | 73 | return { 74 | sourceLinks, 75 | imageSource, 76 | original, 77 | id, 78 | } 79 | } 80 | 81 | async visualsRoute(per = 20) { 82 | per = Number(per) 83 | if (!Number.isInteger(per)) return { error: 'The per query should be an integer' } 84 | if (per < 1) return { error: 'The per query should be bigger than 0' } 85 | if (per > 200) return { error: 'You cannot get more than 200 visuals' } 86 | 87 | let max = await this.getMax() 88 | const ids = [] 89 | 90 | for (let i = 0; i < per; i++) { 91 | ids[i] = max 92 | max-- 93 | } 94 | 95 | const result = await Promise.all( 96 | ids.map(async id => { 97 | const { imageSource, sourceLinks } = await this.getImageSources(id) 98 | const original = `${this.baseUrl}/${id}` 99 | 100 | return { 101 | sourceLinks, 102 | imageSource, 103 | original, 104 | id, 105 | } 106 | }) 107 | ) 108 | 109 | return result 110 | } 111 | } 112 | 113 | module.exports = new ArchillectScrapper() 114 | --------------------------------------------------------------------------------