From a8ef4cb2539bd57081f173c0cf4fc76a792eb800 Mon Sep 17 00:00:00 2001 From: shawjia Date: Mon, 3 Sep 2018 22:33:56 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20v1.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 9 ++++ .eslintrc.js | 3 ++ .gitignore | 73 ++++++++++++++++++++++++++++++++ README.md | 57 ++++++++++++++++++++++++- index.js | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++ package.json | 26 ++++++++++++ test.js | 42 +++++++++++++++++++ 7 files changed, 322 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .eslintrc.js create mode 100644 .gitignore create mode 100644 index.js create mode 100644 package.json create mode 100644 test.js diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..c6c8b36 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..5d6070d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: "airbnb-base" +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a4c858 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless diff --git a/README.md b/README.md index e9258ff..19ad702 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,57 @@ # geektime -API client for time.geekbang.org + +> API client for time.geekbang.org + +## INSTALL +```bash +npm install geektime +# or +yarn add geektime +``` + +## EXAMPLE +```js +const Geektime = require('geektime'); +const client = new Geektime('phone', 'pass'); + +(async () => { + + try { + const products = await client.products(); + console.log(products); + } catch (error) { + console.error(error); + } + +})(); +``` + +## API + +### products() + +产品列表,返回 专栏/视频课/微课/其他 + +### intro(cid) + +返回专栏信息 + +### articles(cid, size = 1000) + +返回专栏文章列表 + +### article(id) + +返回单篇文章详情 + +### audios(cid, size = 1000) + +返回音频列表 + +### NOTE +- *cid:* 专栏 id +- *id:* 文章 id + +## License + +MIT © [shawjia](https://github.com/shawjia) diff --git a/index.js b/index.js new file mode 100644 index 0000000..dce4776 --- /dev/null +++ b/index.js @@ -0,0 +1,113 @@ +const got = require('got'); + +const host = 'https://time.geekbang.org/serv/v1'; +const links = { + login: 'https://account.geekbang.org/account/ticket/login', + products: `${host}/my/products/all`, + intro: `${host}/column/intro`, + articles: `${host}/column/articles`, + article: `${host}/article`, + audios: `${host}/column/audios`, +}; + +async function request(link, body = {}, cookie = '') { + const headers = { + Referer: 'https://servicewechat.com/wxc4f8a61ef62e6e35/20/page-frame.html', + Cookie: cookie, + }; + + try { + const isLoginLink = link === links.login; + const res = await got(link, { + json: true, headers, body, method: 'post', + }); + const loginCookie = isLoginLink + ? res.headers['set-cookie'].map(v => v.split(';')[0]).join('; ') + : ''; + + if (res.body.code === 0) { + return isLoginLink + ? { ...res.body.data, cookie: loginCookie } + : res.body.data; + } + + throw new Error(`Wrong Code: ${res.body.code}`); + } catch (err) { + throw err; + } +} + +class Api { + constructor(cellphone, password) { + if (typeof cellphone !== 'string' || typeof password !== 'string') { + throw new TypeError('cellphone/password should be string'); + } + + if (cellphone === '' || password === '') { + throw new Error('cellphone/password should not be empty'); + } + + this.cellphone = cellphone; + this.password = password; + this.cookie = null; + } + + // 产品列表,返回 专栏/视频课/微课/其他 + async products() { + const cookie = await this.getCookie(); + + return request(links.products, null, cookie); + } + + // 专栏介绍 + async intro(cid) { + const cookie = await this.getCookie(); + + return request(links.intro, { cid }, cookie); + } + + // 专栏文章列表 + async articles(cid, size = 1000) { + const cookie = await this.getCookie(); + + return request(links.articles, { cid, size }, cookie); + } + + // 单篇文章详情 + async article(id) { + const cookie = await this.getCookie(); + + return request(links.article, { id }, cookie); + } + + // 音频列表 + async audios(cid, size = 1000) { + const cookie = await this.getCookie(); + + return request(links.audios, { cid, size }, cookie); + } + + async getCookie() { + if (this.cookie) { + return this.cookie; + } + + const { cellphone, password } = this; + const body = { + cellphone, + password, + country: 86, + remember: 1, + captcha: '', + platform: 4, + appid: 1, + }; + + const { cookie } = await request(links.login, body); + this.cookie = cookie; + + return this.cookie; + } +} + +module.exports = Api; diff --git a/package.json b/package.json new file mode 100644 index 0000000..69db55a --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "geektime", + "version": "1.0.0", + "description": "API client for time.geekbang.org", + "main": "index.js", + "repository": "https://github.com/shawjia/geektime", + "author": "shawjia", + "license": "MIT", + "scripts": { + "test": "ava" + }, + "keywords": [ + "geektime", + "API", + "极客时间" + ], + "devDependencies": { + "ava": "^0.25.0", + "eslint": "^5.5.0", + "eslint-config-airbnb-base": "^13.1.0", + "eslint-plugin-import": "^2.14.0" + }, + "dependencies": { + "got": "^9.2.0" + } +} diff --git a/test.js b/test.js new file mode 100644 index 0000000..3d32a33 --- /dev/null +++ b/test.js @@ -0,0 +1,42 @@ +const test = require('ava'); +const Api = require('.'); + +const { GEEKTIME_PHONE, GEEKTIME_PASS } = process.env; +const client = new Api(GEEKTIME_PHONE, GEEKTIME_PASS); + +const cid = 48; // 左耳听风 +const articleId = 14271; // "article_title": "高效学习:端正学习态度", + +test('Api()', (t) => { + t.throws(() => new Api(), TypeError); +}); + +test('api.products()', async (t) => { + const res = await client.products(); + + t.true(res.length > 0); +}); + +test('api.intro(48)', async (t) => { + const res = await client.intro(cid); + + t.is(res.column_title, '左耳听风'); +}); + +test('api.articles(48)', async (t) => { + const res = await client.articles(cid); + + t.true(res.list.length > 0); +}); + +test('api.article(14271)', async (t) => { + const res = await client.article(articleId); + + t.true(res.article_title.startsWith('高效学习')); +}); + +test('api.audios(48)', async (t) => { + const res = await client.audios(cid); + + t.true(res.list.length > 0); +});