├── test.js
├── app
├── js
│ ├── book.js
│ ├── category.js
│ ├── index.js
│ ├── jquery.base64.js
│ ├── scripts
│ │ ├── jquery.base64.js
│ │ ├── touch.js
│ │ └── jquery.jsonp.js
│ └── jquery.jsonp.js
├── views
│ ├── male.html
│ ├── rank.html
│ ├── female.html
│ ├── search.html
│ ├── include
│ │ ├── book-header.html
│ │ ├── index-bottom.html
│ │ ├── header.html
│ │ ├── index-free.html
│ │ ├── index-self.html
│ │ ├── index-top.html
│ │ ├── index-hot.html
│ │ ├── index-female.html
│ │ ├── index-male.html
│ │ └── index-recommend.html
│ ├── category.html
│ ├── book.html
│ ├── index.html
│ └── catalog.html
├── css
│ ├── test.styl
│ ├── test.css
│ ├── reset.css
│ ├── all.styl
│ ├── copycode.txt
│ └── all.css
├── img
│ ├── back.png
│ ├── female.png
│ ├── male.png
│ ├── rank.png
│ └── category.png
├── data
│ ├── data1.json
│ ├── data2.json
│ ├── data3.json
│ └── data4.json
└── 新建文本文档.txt
├── .gitignore
├── Models
├── Beauty.js
└── Movie.js
├── data
├── data1.json
├── data2.json
├── data3.json
└── data4.json
├── mock
├── reader
│ └── data
│ │ ├── data1.json
│ │ ├── data2.json
│ │ ├── data3.json
│ │ └── data4.json
├── book
│ └── 18218.json
├── bookbacket.json
└── rank.json
├── Schemas
├── Beauty.js
└── Movie.js
├── README.md
├── gulpfile.js
├── package.json
├── css
├── reset.css
└── copycode.txt
├── service
└── webAppService.js
├── util.js
├── server.js
├── js
├── jquery.base64.js
└── jquery.jsonp.js
└── lib
└── zepto.min.js
/test.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/js/book.js:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/male.html:
--------------------------------------------------------------------------------
1 | male.html
--------------------------------------------------------------------------------
/app/views/rank.html:
--------------------------------------------------------------------------------
1 | rank.html
--------------------------------------------------------------------------------
/app/views/female.html:
--------------------------------------------------------------------------------
1 | female.html
--------------------------------------------------------------------------------
/app/views/search.html:
--------------------------------------------------------------------------------
1 | search.html
--------------------------------------------------------------------------------
/app/css/test.styl:
--------------------------------------------------------------------------------
1 | .div
2 | display flex
3 | flex-flow row nowrap
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | app/static/*
2 |
3 | app/index.html
4 |
5 | /node_modules/*
6 |
--------------------------------------------------------------------------------
/app/css/test.css:
--------------------------------------------------------------------------------
1 | .div {
2 | display: flex;
3 | flex-flow: row nowrap;
4 | }
5 |
--------------------------------------------------------------------------------
/app/img/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/back.png
--------------------------------------------------------------------------------
/app/img/female.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/female.png
--------------------------------------------------------------------------------
/app/img/male.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/male.png
--------------------------------------------------------------------------------
/app/img/rank.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/rank.png
--------------------------------------------------------------------------------
/app/img/category.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/lastIndexOf/bookstore/HEAD/app/img/category.png
--------------------------------------------------------------------------------
/Models/Beauty.js:
--------------------------------------------------------------------------------
1 | const BeautySchema = require('../Schemas/Beauty')
2 | , mongoose = require('mongoose')
3 |
4 | const Beauty = mongoose.model('beauties', BeautySchema)
5 |
6 | module.exports = Beauty
7 |
--------------------------------------------------------------------------------
/app/views/include/book-header.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/views/include/index-bottom.html:
--------------------------------------------------------------------------------
1 |
16 | <% include include/book-header.html %>
17 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/app/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
<%= title %>
9 |
10 |
11 |
42 |
43 |
44 |
45 | <% include include/header.html %>
46 |
47 |
48 | <% include include/index-top.html %>
49 | <% include include/index-hot.html %>
50 | <% include include/index-recommend.html %>
51 | <% include include/index-male.html %>
52 | <% include include/index-female.html %>
53 | <% include include/index-free.html %>
54 | <% include include/index-bottom.html %>
55 |
56 |
57 |
58 | <% include include/index-self.html %>
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/app/views/catalog.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
<%= title %>
9 |
10 |
11 |
32 |
33 |
34 |
59 |
60 |
61 |
62 |
63 |
69 |
--------------------------------------------------------------------------------
/service/webAppService.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | exports.get_chapter_data = function() {
4 | var content = fs.readFileSync('./mock/reader/chapter.json', 'utf-8');
5 | return content;
6 | }
7 |
8 | exports.get_chapter_content_data = function(id) {
9 | if (!id) {
10 | id = "1";
11 | }
12 | var content = fs.readFileSync('./mock/reader/data/data' + id + '.json', 'utf-8');
13 | return content;
14 | }
15 |
16 | exports.get_index_data = function() {
17 | var content = fs.readFileSync('./mock/home.json', 'utf-8');
18 | return content;
19 | }
20 |
21 | exports.get_book_data = function(id) {
22 | if (!id) {
23 | id = "18218";
24 | }
25 | if(fs.existsSync('./mock/book/' + id + '.json')){
26 | return fs.readFileSync('./mock/book/' + id + '.json', 'utf-8');
27 | }else{
28 | return fs.readFileSync('./mock/book/18218.json', 'utf-8');
29 | }
30 | }
31 |
32 | exports.get_rank_data = function(channelId) {
33 | var content = fs.readFileSync('./mock/rank.json', 'utf-8');
34 | return content;
35 | }
36 |
37 | exports.get_category_data = function(channelId) {
38 | var content = fs.readFileSync('./mock/category.json', 'utf-8');
39 | return content;
40 | }
41 |
42 | exports.get_male_data = function(channelId) {
43 | var content = fs.readFileSync('./mock/channel/male.json', 'utf-8');
44 | return content;
45 | }
46 |
47 | exports.get_female_data = function(channelId) {
48 | var content = fs.readFileSync('./mock/channel/male.json', 'utf-8');
49 | return content;
50 | }
51 |
52 |
53 | exports.get_search_data = function(start, end, keyword) {
54 | return function(cb) {
55 | var http = require('http');
56 | var qs = require('querystring');
57 | var data = {
58 | s: keyword,
59 | start: start,
60 | end: end
61 | }
62 | var content = qs.stringify(data);
63 | var http_request = {
64 | hostname: 'dushu.xiaomi.com',
65 | port: 80,
66 | path: '/store/v0/lib/query/onebox?' + content,
67 | method: 'GET'
68 | };
69 |
70 | req_obj = http.request(http_request, function(_res) {
71 | var callback_content = '';
72 | var _this = this;
73 | var content='';
74 | _res.setEncoding('utf8');
75 |
76 | _res.on('data', function(chunk) {
77 | content += chunk;
78 | });
79 |
80 | _res.on('end', function(e) {
81 | cb(null,content);
82 | });
83 |
84 | });
85 |
86 | req_obj.on('error', function(e) {
87 |
88 | });
89 |
90 | req_obj.end();
91 | }
92 | }
--------------------------------------------------------------------------------
/util.js:
--------------------------------------------------------------------------------
1 | const cheerio = require('cheerio')
2 | , Books = require('./Models/Movie')
3 | , Book = Books.Book
4 | , baseUrl = 'http://www.quanshu.net/book'
5 |
6 | exports.html2data = function*(html, request) {
7 | const $ = cheerio.load(html)
8 | , src = $('#navList .seeWell.cf li:first-child > a').attr('href')
9 |
10 | if (src) {
11 | const book = yield getBook(src, request)
12 | , jQuery = cheerio.load(book)
13 | let _book = {}
14 |
15 | _book.authors = jQuery('.author .bookDetail .bookso').find('dd a').text()
16 | _book.cover = jQuery('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src')
17 | _book.src = jQuery('#container .mainnav .main.b-detail .detail .b-info .b-oper > a:first-child').attr('href').split(baseUrl)[1]
18 | _book.summary = jQuery('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text()
19 | _book._is = 1
20 |
21 | return _book
22 | }
23 | else if ($('.author .bookDetail .bookso').find('dd a').text()) {
24 | let _book = {}
25 |
26 | _book.authors = $('.author .bookDetail .bookso').find('dd a').text()
27 | _book.cover = $('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src')
28 | _book.summary = $('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text()
29 | _book.src = $('#container .mainnav .main.b-detail .detail .b-info .b-oper > a:first-child').attr('href').split(baseUrl)[1]
30 | _book._is = 1
31 |
32 | return _book
33 | }
34 | else
35 | return { _is: 0, cover: 'http://www.quanshu.net/modules/article/images/nocover.jpg', authors: '没有找到这本书%>_<%', summary: '没有爬到数据...' }
36 | }
37 |
38 | exports.getCurPage = function*(request, src, page) {
39 | let curPage = {}
40 | const URL = baseUrl + src
41 | , pages = yield getBook(URL, request)
42 | , $ = cheerio.load(pages)
43 | , pageslist = $('#chapter .chapterSo .chapterNum ul .clearfix.dirconone li')
44 | , pageA = $(pageslist[`${ page }`]).find('a')
45 | , contentPage = yield getBook(URL + '/' + pageA.attr('href'), request)
46 |
47 | curPage.title = pageA.text()
48 | curPage.content = cheerio.load(contentPage)('#content').text()
49 |
50 | return curPage
51 | }
52 |
53 | exports.getPageslist = function*(src, request, title) {
54 | const html = yield getBook(baseUrl + src, request)
55 | , $ = cheerio.load(html)
56 |
57 | let arcs = []
58 | $('#chapter .chapterSo .chapterNum ul .clearfix.dirconone li')
59 | .each((index, item) => {
60 | arcs.push({ _id: '', title: $(item).find('a').text(), page: index })
61 | })
62 |
63 | return { arcs, title, src }
64 | }
65 |
66 | function getBook(url, request) {
67 | return new Promise((resolve, reject) => {
68 | let key = false
69 | , get = false
70 | setTimeout(() => {
71 | if (!key) {
72 | get = true
73 | console.log('请求超时...')
74 | resolve('timeout')
75 | }
76 | },20000)
77 | console.log(`正在爬取${ url }...`)
78 | request.get(url)
79 | .charset('gbk')
80 | .end((err, res) => {
81 | if (get)
82 | return
83 | if (err)
84 | reject(err)
85 | key = true
86 | console.log('爬取成功,正在分析...')
87 | // console.log(res)
88 | resolve(res.text)
89 | }).on('error', (err) => {
90 | reject(err)
91 | })
92 | })
93 | }
94 |
95 | // function addBook(html) {
96 | // return new Promise((resolve, reject) => {
97 | // const $ = cheerio.load(html)
98 |
99 | // book.name = $('#container .mainnav .main.b-detail').find('.detail .b-info > h1').text()
100 | // book.autor = $('.author .bookDetail .bookso').find('dd a').text()
101 | // book.avaiter = $('#container .mainnav .main.b-detail').find('.l.mr11 > img').attr('src')
102 | // book.intrudoce = $('#container .mainnav .main.b-detail').find('.detail .b-info #waa').text()
103 | // book.arcs =[]
104 |
105 | // book = new Movie(book)
106 |
107 | // book.save((err) => {
108 | // if (err)
109 | // reject(err)
110 |
111 | // resolve()
112 | // })
113 | // })
114 | // }
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const koa = require('koa')
2 | , router = require('koa-route')
3 | , static = require('koa-static')
4 | , bodyParser = require('koa-bodyparser')
5 | , mongoose = require('mongoose')
6 | , superagent = require('superagent')
7 | , install = require('superagent-charset')
8 | , views = require('co-views')
9 | , iconv = require('iconv-lite')
10 | , querystring = require('querystring')
11 | , Books = require('./Models/Movie')
12 | , util = require('./util')
13 | , service = require('./service/webAppService')
14 | , appData = require('./data.json')
15 | , seller = appData.seller
16 | , goods = appData.goods
17 | , ratings = appData.ratings
18 | , app = koa()
19 | , request = install(superagent)
20 |
21 | const dbUrl = 'mongodb://localhost:27017/avMovies'
22 | , Book = Books.Movie
23 | , render = views('app/views', {
24 | map: { html: 'ejs' }
25 | })
26 | // 连接数据库
27 | mongoose.connect(dbUrl)
28 |
29 |
30 | app.use(static('app', { index: 'none' }))
31 | app.use(bodyParser())
32 |
33 | app.use(router.get('/', function*() {
34 | this.body = yield render('index', {
35 | title: '首页'
36 | })
37 | }))
38 | app.use(router.get('/book', function*() {
39 | const query = this.request.query
40 | , _id = query.id
41 | , title = query.title
42 |
43 | if (_id && _id !== 'undefined') {
44 | const book = yield Book.findOne({_id: _id}, ['title', 'cover', 'summary', 'authors'])
45 | this.body = yield render('book', { book })
46 | } else {
47 | const _title = escape(iconv.encode(title, 'gb2312').toString('binary'))
48 | let result = yield request.get('http://www.quanshu.net/modules/article/search.php')
49 | .query(`searchkey=${ _title }`)
50 | .query('searchtype=articlename&searchbuttom.x=0&searchbuttom.y=0')
51 | .charset('gbk')
52 |
53 | result = yield util.html2data(result.text, request)
54 | result.title = title
55 |
56 | if (result._is)
57 | this.body = yield render('book', { book: result })
58 | else {
59 | this.body = yield render('book', { book: result })
60 | }
61 | }
62 |
63 | }))
64 |
65 | app.use(router.get('/category', function*() {
66 | this.body = yield render('category', { title: '爬自全书网...' })
67 | }))
68 |
69 | app.use(router.get('/api/seller', function* () {
70 | this.body = {
71 | errno: 0,
72 | data: seller
73 | }
74 | }))
75 | app.use(router.get('/api/goods', function* () {
76 | this.body = {
77 | errno: 0,
78 | data: goods
79 | }
80 | }))
81 | app.use(router.get('/api/ratings', function* () {
82 | this.body = {
83 | errno: 0,
84 | data: ratings
85 | }
86 | }))
87 | app.use(router.get('/catalog', function*() {
88 | const query = this.request.query
89 | , _id = query.id
90 | , _title = query.title
91 | , _src = query.src
92 | , page = query.page - 0
93 |
94 | let data = []
95 |
96 | if (_id)
97 | data = yield Book.findOne({_id: _id}, ['title', 'arcs'])
98 | else
99 | data = yield util.getPageslist(_src, request, _title)
100 |
101 | this.body = yield render('catalog', { title: _title, page, data: data })
102 | }))
103 |
104 | // app.use(router.get('/male', function*() {
105 | // this.body = yield render('male', {})
106 | // }))
107 | // app.use(router.get('/female', function*() {
108 | // this.body = yield render('female', {})
109 | // }))
110 | // app.use(router.get('/search', function*() {
111 | // this.body = yield render('search', {})
112 | // }))
113 | // app.use(router.get('/rank', function*() {
114 | // this.body = yield render('rank', {})
115 | // }))
116 |
117 | app.use(router.get('/ajax/index', function*(){
118 | this.body = service.get_index_data()
119 | }))
120 |
121 | app.use(router.get('/ajax/book', function*(){
122 | var params = querystring.parse(this.req._parsedUrl.query)
123 | var id = params.id
124 | if(!id){
125 | id = ""
126 | }
127 | this.body = service.get_book_data(id)
128 | }))
129 |
130 | app.use(router.get('/ajax/page', function*() {
131 | const query = this.request.query
132 | , _id = query.id
133 | , page = query.page - 0
134 |
135 | if (_id) {
136 | const pageDate = yield Book.findOne({_id: _id}, ['arcs'])
137 | this.body = { ERROR: 1, data: pageDate.arcs[`${ page }`] }
138 | } else {
139 | const _title = query.title
140 | , _src = query.src
141 | , pageDate = yield util.getCurPage(request, _src, page)
142 |
143 | this.body = { ERROR: 1, data: pageDate }
144 | }
145 | }))
146 |
147 | app.use(router.get('/ajax/category', function*(){
148 | const books = yield Book.find({}, ['title', 'authors', 'cover', 'summary']).limit(9)
149 |
150 | if (books)
151 | this.body = { ERROR: 0, data: books }
152 | else
153 | this.body = { ERROR: 1 }
154 | }))
155 |
156 | // app.use(router.get('/ajax/chapter_data', function*(){
157 | // var params = querystring.parse(this.req._parsedUrl.query)
158 | // var id = params.id
159 | // if(!id){
160 | // id = ""
161 | // }
162 | // this.body = service.get_chapter_content_data(id)
163 | // }))
164 |
165 | // app.use(router.get('/ajax/rank', function*(){
166 | // this.body = service.get_rank_data()
167 | // }))
168 |
169 | // app.use(router.get('/ajax/male', function*(){
170 | // this.body = service.get_male_data()
171 | // }))
172 |
173 | // app.use(router.get('/ajax/female', function*(){
174 | // this.body = service.get_female_data()
175 | // }))
176 |
177 | app.listen(3004)
178 | console.log('listing on 3004')
179 |
--------------------------------------------------------------------------------
/mock/book/18218.json:
--------------------------------------------------------------------------------
1 | {
2 | "item": {
3 | "ad": 0,
4 | "word_count": 70977,
5 | "owner": 0,
6 | "allow_discount": 0,
7 | "ad_time": 0,
8 | "ad_duration": 0,
9 | "click": 2438627,
10 | "score_count": 113,
11 | "title": "老九门",
12 | "on_sale": true,
13 | "comment_count": 0,
14 | "has_ad": 0,
15 | "id": 1724032,
16 | "content": "【陈伟霆、张艺兴、赵丽颖主演,同名电视剧正在热播】民国时期,长沙车站开来一辆来历不明的火车,彻查发现来自一座深山中的百年矿山,因为常年开采导致地皮塌陷,矿山中弥漫着一股奇怪的浓雾,长沙老九门盗墓家族张家和红家经过了一番查探,发现了深埋矿山之下,有着另外一个秘密,经过一番探险,他们深入矿山之中,来到矿山的底部,看到了让人无法置信的景象。千年之前坠落的三颗陨石,长白山下埋藏的秘密,他们到底从矿山底部带出了什么东西,为何世界慢慢变的异样诡异?",
17 | "source": 2,
18 | "latest_id": 41,
19 | "score": 7.8,
20 | "allow_free_read": 1,
21 | "toc": [],
22 | "rights_id": 10080,
23 | "channel": [1],
24 | "updated": 1469173947,
25 | "finish": false,
26 | "tags": ["寻墓探险", "悬疑探险", "灵异"],
27 | "started": 1469168519,
28 | "price": 5,
29 | "chapter_count": 42,
30 | "authors": "南派三叔",
31 | "categories": [{
32 | "category_id": 8000000,
33 | "label": "灵异"
34 | }, {
35 | "category_id": 8000300,
36 | "label": "悬疑探险"
37 | }],
38 | "author_books_total": 2,
39 | "rights": "阅文集团旗下起点中文网",
40 | "level": 0,
41 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01TQfpyV6px/06EirDzmjj8zuK.jpg",
42 | "summary": "【陈伟霆、张艺兴、赵丽颖主演,同名电视正在热播】民国时期,长沙车站开来一辆来历不明的火车,彻查发现",
43 | "source_id": 323725,
44 | "fiction_id": 323725,
45 | "outer_id": "5791bb8850e3a4587b490db2",
46 | "latest": "第二十七章 缠斗"
47 | },
48 | "related": [{
49 | "ad": 0,
50 | "word_count": 599903,
51 | "owner": 0,
52 | "allow_discount": 0,
53 | "ad_time": 0,
54 | "ad_duration": 0,
55 | "score_count": 1,
56 | "title": "食咒",
57 | "on_sale": true,
58 | "comment_count": 0,
59 | "has_ad": 0,
60 | "click": 55868,
61 | "latest_id": 228,
62 | "score": 6.0,
63 | "allow_free_read": 1,
64 | "rights_id": 10080,
65 | "channel": [1],
66 | "updated": 1469613079,
67 | "finish": false,
68 | "tags": ["悬疑探险", "灵异"],
69 | "price": 5,
70 | "chapter_count": 229,
71 | "authors": "蟋与蝉",
72 | "categories": [{
73 | "category_id": 8000000,
74 | "label": "灵异"
75 | }, {
76 | "category_id": 8000300,
77 | "label": "悬疑探险"
78 | }],
79 | "rights": "阅文集团旗下起点中文网",
80 | "level": 0,
81 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01QivZjexs1/9FS6XpwmPq9ZxF.jpg",
82 | "summary": "饭店的东西是不可以乱吃的!因为你吃下去的很可能是......\r\n\r\n本文将带你进入厨子的黑暗世界。\r...",
83 | "fiction_id": 319741,
84 | "outer_id": "5718455f50e3a46f452a60be",
85 | "latest": "第四十七章:科技装备"
86 | }, {
87 | "ad": 0,
88 | "word_count": 243928,
89 | "owner": 0,
90 | "allow_discount": 0,
91 | "ad_time": 0,
92 | "ad_duration": 0,
93 | "score_count": 27,
94 | "title": "校园惊魂:我的初恋是厉鬼",
95 | "on_sale": true,
96 | "comment_count": 0,
97 | "has_ad": 0,
98 | "click": 277169,
99 | "latest_id": 99,
100 | "score": 8.66,
101 | "allow_free_read": 1,
102 | "rights_id": 10080,
103 | "channel": [1],
104 | "updated": 1465700482,
105 | "finish": false,
106 | "tags": ["灵异悬疑", "恐怖惊悚", "灵异"],
107 | "price": 5,
108 | "chapter_count": 100,
109 | "authors": "梦入星河",
110 | "categories": [{
111 | "category_id": 50000,
112 | "label": "灵异悬疑"
113 | }, {
114 | "category_id": 50200,
115 | "label": "恐怖惊悚"
116 | }, {
117 | "category_id": 8000000,
118 | "label": "灵异"
119 | }, {
120 | "category_id": 8000200,
121 | "label": "恐怖惊悚"
122 | }],
123 | "rights": "阅文集团旗下创世中文网",
124 | "level": 0,
125 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01i8Qo2TuU3/IcyOEngmwcpps5.jpg",
126 | "summary": "大红的霓裳,大红的冠顶,大红的绣花鞋,她一身上下,仿佛浸染过人世间最浓郁的鲜血,一头长发如墨,在烈烈...",
127 | "fiction_id": 271520,
128 | "outer_id": "5582352d50e3a473924c8758",
129 | "latest": "第109章 六阴解梦,梦魇散去"
130 | }, {
131 | "ad": 0,
132 | "word_count": 1506629,
133 | "owner": 0,
134 | "allow_discount": 0,
135 | "ad_time": 0,
136 | "ad_duration": 0,
137 | "score_count": 16,
138 | "title": "阴阳天师",
139 | "on_sale": true,
140 | "comment_count": 0,
141 | "has_ad": 0,
142 | "click": 203382,
143 | "latest_id": 720,
144 | "score": 7.5,
145 | "allow_free_read": 1,
146 | "rights_id": 10080,
147 | "channel": [1],
148 | "updated": 1468229857,
149 | "finish": false,
150 | "tags": ["灵异悬疑", "灵异奇谈", "灵异神怪", "灵异"],
151 | "price": 5,
152 | "chapter_count": 721,
153 | "authors": "落语",
154 | "categories": [{
155 | "category_id": 50000,
156 | "label": "灵异悬疑"
157 | }, {
158 | "category_id": 50300,
159 | "label": "灵异神怪"
160 | }, {
161 | "category_id": 8000000,
162 | "label": "灵异"
163 | }, {
164 | "category_id": 8000400,
165 | "label": "灵异奇谈"
166 | }],
167 | "rights": "阅文集团旗下创世中文网",
168 | "level": 0,
169 | "cover": "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01aEqw2vIGw/I3bD0QHJuwJjpg.jpg",
170 | "summary": "无论你信不信这个世界上有鬼,我却从小和它们为伍。七年前,我一直追随师父走南闯北,见识过形形色色的古怪...",
171 | "fiction_id": 302188,
172 | "outer_id": "5582351750e3a4738e4c8776",
173 | "latest": "第七百二十一章 各方震动"
174 | }],
175 | "author_books": [{
176 | "ad": 0,
177 | "word_count": 1441873,
178 | "owner": 0,
179 | "allow_discount": false,
180 | "ad_time": 0,
181 | "ad_duration": 0,
182 | "score_count": 8854,
183 | "title": "盗墓笔记",
184 | "on_sale": true,
185 | "comment_count": 1927,
186 | "has_ad": 0,
187 | "click": 34069097,
188 | "latest_id": 481,
189 | "score": 9.0,
190 | "allow_free_read": 1,
191 | "rights_id": 10080,
192 | "channel": [1],
193 | "updated": 1450801819,
194 | "finish": true,
195 | "tags": ["升级练功", "盗墓探险", "盗墓", "热血", "灵异悬疑"],
196 | "price": 5,
197 | "chapter_count": 482,
198 | "authors": "南派三叔",
199 | "categories": [{
200 | "category_id": 50000,
201 | "label": "灵异悬疑"
202 | }, {
203 | "category_id": 50400,
204 | "label": "盗墓探险"
205 | }, {
206 | "category_id": 8000000,
207 | "label": "灵异"
208 | }, {
209 | "category_id": 8000300,
210 | "label": "悬疑探险"
211 | }],
212 | "rights": "阅文集团旗下起点中文网",
213 | "level": 0,
214 | "cover": "http://cover.read.duokan.com/mfsv2/download/s010/p01yfPOuJRok/dQJSYfAnD3AB42.jpg",
215 | "summary": "这神秘的墓主人到底是谁,他们到底能不能找到真正的棺椁?",
216 | "fiction_id": 270635,
217 | "outer_id": "5581401c50e3a4079e15a4f9",
218 | "latest": "《后记》下"
219 | }],
220 | "result": 0
221 | }
--------------------------------------------------------------------------------
/app/js/jquery.base64.js:
--------------------------------------------------------------------------------
1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false,
2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false,
3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true,
4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true,
5 | regexp: false, rhino: true, safe: false, strict: false, sub: false,
6 | undef: true, white: false, widget: false, windows: false */
7 | /*global jQuery: false, window: false */
8 | "use strict";
9 |
10 | /*
11 | * Original code (c) 2010 Nick Galbreath
12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
13 | *
14 | * jQuery port (c) 2010 Carlo Zottmann
15 | * http://github.com/carlo/jquery-base64
16 | *
17 | * Permission is hereby granted, free of charge, to any person
18 | * obtaining a copy of this software and associated documentation
19 | * files (the "Software"), to deal in the Software without
20 | * restriction, including without limitation the rights to use,
21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | * copies of the Software, and to permit persons to whom the
23 | * Software is furnished to do so, subject to the following
24 | * conditions:
25 | *
26 | * The above copyright notice and this permission notice shall be
27 | * included in all copies or substantial portions of the Software.
28 | *
29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
36 | * OTHER DEALINGS IN THE SOFTWARE.
37 | */
38 |
39 | /* base64 encode/decode compatible with window.btoa/atob
40 | *
41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b")
42 | * to base64 (ascii, the "a").
43 | *
44 | * It is also found in Safari and Chrome. It is not available in IE.
45 | *
46 | * if (!window.btoa) window.btoa = $.base64.encode
47 | * if (!window.atob) window.atob = $.base64.decode
48 | *
49 | * The original spec's for atob/btoa are a bit lacking
50 | * https://developer.mozilla.org/en/DOM/window.atob
51 | * https://developer.mozilla.org/en/DOM/window.btoa
52 | *
53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255]
54 | * If any character is not [0,255], then an exception is thrown.
55 | *
56 | * window.atob and $.base64.decode take a base64-encoded string
57 | * If the input length is not a multiple of 4, or contains invalid characters
58 | * then an exception is thrown.
59 | */
60 |
61 | jQuery.base64 = ( function( $ ) {
62 |
63 | var _PADCHAR = "=",
64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
65 | _VERSION = "1.0";
66 |
67 |
68 | function _getbyte64( s, i ) {
69 | // This is oddly fast, except on Chrome/V8.
70 | // Minimal or no improvement in performance by using a
71 | // object with properties mapping chars to value (eg. 'A': 0)
72 |
73 | var idx = _ALPHA.indexOf( s.charAt( i ) );
74 |
75 | if ( idx === -1 ) {
76 | throw "Cannot decode base64";
77 | }
78 |
79 | return idx;
80 | }
81 |
82 |
83 | function _decode( s ) {
84 | var pads = 0,
85 | i,
86 | b10,
87 | imax = s.length,
88 | x = [];
89 |
90 | s = String( s );
91 |
92 | if ( imax === 0 ) {
93 | return s;
94 | }
95 |
96 | if ( imax % 4 !== 0 ) {
97 | throw "Cannot decode base64";
98 | }
99 |
100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) {
101 | pads = 1;
102 |
103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) {
104 | pads = 2;
105 | }
106 |
107 | // either way, we want to ignore this last block
108 | imax -= 4;
109 | }
110 |
111 | for ( i = 0; i < imax; i += 4 ) {
112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 );
113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) );
114 | }
115 |
116 | switch ( pads ) {
117 | case 1:
118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 );
119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) );
120 | break;
121 |
122 | case 2:
123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 );
124 | x.push( String.fromCharCode( b10 >> 16 ) );
125 | break;
126 | }
127 |
128 | return x.join( "" );
129 | }
130 |
131 |
132 | function _getbyte( s, i ) {
133 | var x = s.charCodeAt( i );
134 |
135 | if ( x > 255 ) {
136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5";
137 | }
138 |
139 | return x;
140 | }
141 |
142 |
143 | function _encode( s ) {
144 | if ( arguments.length !== 1 ) {
145 | throw "SyntaxError: exactly one argument required";
146 | }
147 |
148 | s = String( s );
149 |
150 | var i,
151 | b10,
152 | x = [],
153 | imax = s.length - s.length % 3;
154 |
155 | if ( s.length === 0 ) {
156 | return s;
157 | }
158 |
159 | for ( i = 0; i < imax; i += 3 ) {
160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 );
161 | x.push( _ALPHA.charAt( b10 >> 18 ) );
162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) );
163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) );
164 | x.push( _ALPHA.charAt( b10 & 0x3f ) );
165 | }
166 |
167 | switch ( s.length - imax ) {
168 | case 1:
169 | b10 = _getbyte( s, i ) << 16;
170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR );
171 | break;
172 |
173 | case 2:
174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 );
175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR );
176 | break;
177 | }
178 |
179 | return x.join( "" );
180 | }
181 |
182 |
183 | return {
184 | decode: _decode,
185 | encode: _encode,
186 | VERSION: _VERSION
187 | };
188 |
189 | }( jQuery ) );
190 |
--------------------------------------------------------------------------------
/js/jquery.base64.js:
--------------------------------------------------------------------------------
1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false,
2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false,
3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true,
4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true,
5 | regexp: false, rhino: true, safe: false, strict: false, sub: false,
6 | undef: true, white: false, widget: false, windows: false */
7 | /*global jQuery: false, window: false */
8 | "use strict";
9 |
10 | /*
11 | * Original code (c) 2010 Nick Galbreath
12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
13 | *
14 | * jQuery port (c) 2010 Carlo Zottmann
15 | * http://github.com/carlo/jquery-base64
16 | *
17 | * Permission is hereby granted, free of charge, to any person
18 | * obtaining a copy of this software and associated documentation
19 | * files (the "Software"), to deal in the Software without
20 | * restriction, including without limitation the rights to use,
21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | * copies of the Software, and to permit persons to whom the
23 | * Software is furnished to do so, subject to the following
24 | * conditions:
25 | *
26 | * The above copyright notice and this permission notice shall be
27 | * included in all copies or substantial portions of the Software.
28 | *
29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
36 | * OTHER DEALINGS IN THE SOFTWARE.
37 | */
38 |
39 | /* base64 encode/decode compatible with window.btoa/atob
40 | *
41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b")
42 | * to base64 (ascii, the "a").
43 | *
44 | * It is also found in Safari and Chrome. It is not available in IE.
45 | *
46 | * if (!window.btoa) window.btoa = $.base64.encode
47 | * if (!window.atob) window.atob = $.base64.decode
48 | *
49 | * The original spec's for atob/btoa are a bit lacking
50 | * https://developer.mozilla.org/en/DOM/window.atob
51 | * https://developer.mozilla.org/en/DOM/window.btoa
52 | *
53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255]
54 | * If any character is not [0,255], then an exception is thrown.
55 | *
56 | * window.atob and $.base64.decode take a base64-encoded string
57 | * If the input length is not a multiple of 4, or contains invalid characters
58 | * then an exception is thrown.
59 | */
60 |
61 | jQuery.base64 = ( function( $ ) {
62 |
63 | var _PADCHAR = "=",
64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
65 | _VERSION = "1.0";
66 |
67 |
68 | function _getbyte64( s, i ) {
69 | // This is oddly fast, except on Chrome/V8.
70 | // Minimal or no improvement in performance by using a
71 | // object with properties mapping chars to value (eg. 'A': 0)
72 |
73 | var idx = _ALPHA.indexOf( s.charAt( i ) );
74 |
75 | if ( idx === -1 ) {
76 | throw "Cannot decode base64";
77 | }
78 |
79 | return idx;
80 | }
81 |
82 |
83 | function _decode( s ) {
84 | var pads = 0,
85 | i,
86 | b10,
87 | imax = s.length,
88 | x = [];
89 |
90 | s = String( s );
91 |
92 | if ( imax === 0 ) {
93 | return s;
94 | }
95 |
96 | if ( imax % 4 !== 0 ) {
97 | throw "Cannot decode base64";
98 | }
99 |
100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) {
101 | pads = 1;
102 |
103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) {
104 | pads = 2;
105 | }
106 |
107 | // either way, we want to ignore this last block
108 | imax -= 4;
109 | }
110 |
111 | for ( i = 0; i < imax; i += 4 ) {
112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 );
113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) );
114 | }
115 |
116 | switch ( pads ) {
117 | case 1:
118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 );
119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) );
120 | break;
121 |
122 | case 2:
123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 );
124 | x.push( String.fromCharCode( b10 >> 16 ) );
125 | break;
126 | }
127 |
128 | return x.join( "" );
129 | }
130 |
131 |
132 | function _getbyte( s, i ) {
133 | var x = s.charCodeAt( i );
134 |
135 | if ( x > 255 ) {
136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5";
137 | }
138 |
139 | return x;
140 | }
141 |
142 |
143 | function _encode( s ) {
144 | if ( arguments.length !== 1 ) {
145 | throw "SyntaxError: exactly one argument required";
146 | }
147 |
148 | s = String( s );
149 |
150 | var i,
151 | b10,
152 | x = [],
153 | imax = s.length - s.length % 3;
154 |
155 | if ( s.length === 0 ) {
156 | return s;
157 | }
158 |
159 | for ( i = 0; i < imax; i += 3 ) {
160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 );
161 | x.push( _ALPHA.charAt( b10 >> 18 ) );
162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) );
163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) );
164 | x.push( _ALPHA.charAt( b10 & 0x3f ) );
165 | }
166 |
167 | switch ( s.length - imax ) {
168 | case 1:
169 | b10 = _getbyte( s, i ) << 16;
170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR );
171 | break;
172 |
173 | case 2:
174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 );
175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR );
176 | break;
177 | }
178 |
179 | return x.join( "" );
180 | }
181 |
182 |
183 | return {
184 | decode: _decode,
185 | encode: _encode,
186 | VERSION: _VERSION
187 | };
188 |
189 | }( jQuery ) );
190 |
--------------------------------------------------------------------------------
/app/js/scripts/jquery.base64.js:
--------------------------------------------------------------------------------
1 | /*jslint adsafe: false, bitwise: true, browser: true, cap: false, css: false,
2 | debug: false, devel: true, eqeqeq: true, es5: false, evil: false,
3 | forin: false, fragment: false, immed: true, laxbreak: false, newcap: true,
4 | nomen: false, on: false, onevar: true, passfail: false, plusplus: true,
5 | regexp: false, rhino: true, safe: false, strict: false, sub: false,
6 | undef: true, white: false, widget: false, windows: false */
7 | /*global jQuery: false, window: false */
8 | "use strict";
9 |
10 | /*
11 | * Original code (c) 2010 Nick Galbreath
12 | * http://code.google.com/p/stringencoders/source/browse/#svn/trunk/javascript
13 | *
14 | * jQuery port (c) 2010 Carlo Zottmann
15 | * http://github.com/carlo/jquery-base64
16 | *
17 | * Permission is hereby granted, free of charge, to any person
18 | * obtaining a copy of this software and associated documentation
19 | * files (the "Software"), to deal in the Software without
20 | * restriction, including without limitation the rights to use,
21 | * copy, modify, merge, publish, distribute, sublicense, and/or sell
22 | * copies of the Software, and to permit persons to whom the
23 | * Software is furnished to do so, subject to the following
24 | * conditions:
25 | *
26 | * The above copyright notice and this permission notice shall be
27 | * included in all copies or substantial portions of the Software.
28 | *
29 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
31 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
33 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
34 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
35 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
36 | * OTHER DEALINGS IN THE SOFTWARE.
37 | */
38 |
39 | /* base64 encode/decode compatible with window.btoa/atob
40 | *
41 | * window.atob/btoa is a Firefox extension to convert binary data (the "b")
42 | * to base64 (ascii, the "a").
43 | *
44 | * It is also found in Safari and Chrome. It is not available in IE.
45 | *
46 | * if (!window.btoa) window.btoa = $.base64.encode
47 | * if (!window.atob) window.atob = $.base64.decode
48 | *
49 | * The original spec's for atob/btoa are a bit lacking
50 | * https://developer.mozilla.org/en/DOM/window.atob
51 | * https://developer.mozilla.org/en/DOM/window.btoa
52 | *
53 | * window.btoa and $.base64.encode takes a string where charCodeAt is [0,255]
54 | * If any character is not [0,255], then an exception is thrown.
55 | *
56 | * window.atob and $.base64.decode take a base64-encoded string
57 | * If the input length is not a multiple of 4, or contains invalid characters
58 | * then an exception is thrown.
59 | */
60 |
61 | jQuery.base64 = ( function( $ ) {
62 |
63 | var _PADCHAR = "=",
64 | _ALPHA = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
65 | _VERSION = "1.0";
66 |
67 |
68 | function _getbyte64( s, i ) {
69 | // This is oddly fast, except on Chrome/V8.
70 | // Minimal or no improvement in performance by using a
71 | // object with properties mapping chars to value (eg. 'A': 0)
72 |
73 | var idx = _ALPHA.indexOf( s.charAt( i ) );
74 |
75 | if ( idx === -1 ) {
76 | throw "Cannot decode base64";
77 | }
78 |
79 | return idx;
80 | }
81 |
82 |
83 | function _decode( s ) {
84 | var pads = 0,
85 | i,
86 | b10,
87 | imax = s.length,
88 | x = [];
89 |
90 | s = String( s );
91 |
92 | if ( imax === 0 ) {
93 | return s;
94 | }
95 |
96 | if ( imax % 4 !== 0 ) {
97 | throw "Cannot decode base64";
98 | }
99 |
100 | if ( s.charAt( imax - 1 ) === _PADCHAR ) {
101 | pads = 1;
102 |
103 | if ( s.charAt( imax - 2 ) === _PADCHAR ) {
104 | pads = 2;
105 | }
106 |
107 | // either way, we want to ignore this last block
108 | imax -= 4;
109 | }
110 |
111 | for ( i = 0; i < imax; i += 4 ) {
112 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 ) | _getbyte64( s, i + 3 );
113 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff, b10 & 0xff ) );
114 | }
115 |
116 | switch ( pads ) {
117 | case 1:
118 | b10 = ( _getbyte64( s, i ) << 18 ) | ( _getbyte64( s, i + 1 ) << 12 ) | ( _getbyte64( s, i + 2 ) << 6 );
119 | x.push( String.fromCharCode( b10 >> 16, ( b10 >> 8 ) & 0xff ) );
120 | break;
121 |
122 | case 2:
123 | b10 = ( _getbyte64( s, i ) << 18) | ( _getbyte64( s, i + 1 ) << 12 );
124 | x.push( String.fromCharCode( b10 >> 16 ) );
125 | break;
126 | }
127 |
128 | return x.join( "" );
129 | }
130 |
131 |
132 | function _getbyte( s, i ) {
133 | var x = s.charCodeAt( i );
134 |
135 | if ( x > 255 ) {
136 | throw "INVALID_CHARACTER_ERR: DOM Exception 5";
137 | }
138 |
139 | return x;
140 | }
141 |
142 |
143 | function _encode( s ) {
144 | if ( arguments.length !== 1 ) {
145 | throw "SyntaxError: exactly one argument required";
146 | }
147 |
148 | s = String( s );
149 |
150 | var i,
151 | b10,
152 | x = [],
153 | imax = s.length - s.length % 3;
154 |
155 | if ( s.length === 0 ) {
156 | return s;
157 | }
158 |
159 | for ( i = 0; i < imax; i += 3 ) {
160 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 ) | _getbyte( s, i + 2 );
161 | x.push( _ALPHA.charAt( b10 >> 18 ) );
162 | x.push( _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) );
163 | x.push( _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) );
164 | x.push( _ALPHA.charAt( b10 & 0x3f ) );
165 | }
166 |
167 | switch ( s.length - imax ) {
168 | case 1:
169 | b10 = _getbyte( s, i ) << 16;
170 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _PADCHAR + _PADCHAR );
171 | break;
172 |
173 | case 2:
174 | b10 = ( _getbyte( s, i ) << 16 ) | ( _getbyte( s, i + 1 ) << 8 );
175 | x.push( _ALPHA.charAt( b10 >> 18 ) + _ALPHA.charAt( ( b10 >> 12 ) & 0x3F ) + _ALPHA.charAt( ( b10 >> 6 ) & 0x3f ) + _PADCHAR );
176 | break;
177 | }
178 |
179 | return x.join( "" );
180 | }
181 |
182 |
183 | return {
184 | decode: _decode,
185 | encode: _encode,
186 | VERSION: _VERSION
187 | };
188 |
189 | }( jQuery ) );
190 |
--------------------------------------------------------------------------------
/app/js/scripts/touch.js:
--------------------------------------------------------------------------------
1 | // Zepto.js
2 | // (c) 2010-2016 Thomas Fuchs
3 | // Zepto.js may be freely distributed under the MIT license.
4 |
5 | ;(function($){
6 | var touch = {},
7 | touchTimeout, tapTimeout, swipeTimeout, longTapTimeout,
8 | longTapDelay = 750,
9 | gesture
10 |
11 | function swipeDirection(x1, x2, y1, y2) {
12 | return Math.abs(x1 - x2) >=
13 | Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down')
14 | }
15 |
16 | function longTap() {
17 | longTapTimeout = null
18 | if (touch.last) {
19 | touch.el.trigger('longTap')
20 | touch = {}
21 | }
22 | }
23 |
24 | function cancelLongTap() {
25 | if (longTapTimeout) clearTimeout(longTapTimeout)
26 | longTapTimeout = null
27 | }
28 |
29 | function cancelAll() {
30 | if (touchTimeout) clearTimeout(touchTimeout)
31 | if (tapTimeout) clearTimeout(tapTimeout)
32 | if (swipeTimeout) clearTimeout(swipeTimeout)
33 | if (longTapTimeout) clearTimeout(longTapTimeout)
34 | touchTimeout = tapTimeout = swipeTimeout = longTapTimeout = null
35 | touch = {}
36 | }
37 |
38 | function isPrimaryTouch(event){
39 | return (event.pointerType == 'touch' ||
40 | event.pointerType == event.MSPOINTER_TYPE_TOUCH)
41 | && event.isPrimary
42 | }
43 |
44 | function isPointerEventType(e, type){
45 | return (e.type == 'pointer'+type ||
46 | e.type.toLowerCase() == 'mspointer'+type)
47 | }
48 |
49 | $(document).ready(function(){
50 | var now, delta, deltaX = 0, deltaY = 0, firstTouch, _isPointerType
51 |
52 | if ('MSGesture' in window) {
53 | gesture = new MSGesture()
54 | gesture.target = document.body
55 | }
56 |
57 | $(document)
58 | .bind('MSGestureEnd', function(e){
59 | var swipeDirectionFromVelocity =
60 | e.velocityX > 1 ? 'Right' : e.velocityX < -1 ? 'Left' : e.velocityY > 1 ? 'Down' : e.velocityY < -1 ? 'Up' : null
61 | if (swipeDirectionFromVelocity) {
62 | touch.el.trigger('swipe')
63 | touch.el.trigger('swipe'+ swipeDirectionFromVelocity)
64 | }
65 | })
66 | .on('touchstart MSPointerDown pointerdown', function(e){
67 | if((_isPointerType = isPointerEventType(e, 'down')) &&
68 | !isPrimaryTouch(e)) return
69 | firstTouch = _isPointerType ? e : e.touches[0]
70 | if (e.touches && e.touches.length === 1 && touch.x2) {
71 | // Clear out touch movement data if we have it sticking around
72 | // This can occur if touchcancel doesn't fire due to preventDefault, etc.
73 | touch.x2 = undefined
74 | touch.y2 = undefined
75 | }
76 | now = Date.now()
77 | delta = now - (touch.last || now)
78 | touch.el = $('tagName' in firstTouch.target ?
79 | firstTouch.target : firstTouch.target.parentNode)
80 | touchTimeout && clearTimeout(touchTimeout)
81 | touch.x1 = firstTouch.pageX
82 | touch.y1 = firstTouch.pageY
83 | if (delta > 0 && delta <= 250) touch.isDoubleTap = true
84 | touch.last = now
85 | longTapTimeout = setTimeout(longTap, longTapDelay)
86 | // adds the current touch contact for IE gesture recognition
87 | if (gesture && _isPointerType) gesture.addPointer(e.pointerId)
88 | })
89 | .on('touchmove MSPointerMove pointermove', function(e){
90 | if((_isPointerType = isPointerEventType(e, 'move')) &&
91 | !isPrimaryTouch(e)) return
92 | firstTouch = _isPointerType ? e : e.touches[0]
93 | cancelLongTap()
94 | touch.x2 = firstTouch.pageX
95 | touch.y2 = firstTouch.pageY
96 |
97 | deltaX += Math.abs(touch.x1 - touch.x2)
98 | deltaY += Math.abs(touch.y1 - touch.y2)
99 | })
100 | .on('touchend MSPointerUp pointerup', function(e){
101 | if((_isPointerType = isPointerEventType(e, 'up')) &&
102 | !isPrimaryTouch(e)) return
103 | cancelLongTap()
104 |
105 | // swipe
106 | if ((touch.x2 && Math.abs(touch.x1 - touch.x2) > 30) ||
107 | (touch.y2 && Math.abs(touch.y1 - touch.y2) > 30))
108 |
109 | swipeTimeout = setTimeout(function() {
110 | if (touch.el){
111 | touch.el.trigger('swipe')
112 | touch.el.trigger('swipe' + (swipeDirection(touch.x1, touch.x2, touch.y1, touch.y2)))
113 | }
114 | touch = {}
115 | }, 0)
116 |
117 | // normal tap
118 | else if ('last' in touch)
119 | // don't fire tap when delta position changed by more than 30 pixels,
120 | // for instance when moving to a point and back to origin
121 | if (deltaX < 30 && deltaY < 30) {
122 | // delay by one tick so we can cancel the 'tap' event if 'scroll' fires
123 | // ('tap' fires before 'scroll')
124 | tapTimeout = setTimeout(function() {
125 |
126 | // trigger universal 'tap' with the option to cancelTouch()
127 | // (cancelTouch cancels processing of single vs double taps for faster 'tap' response)
128 | var event = $.Event('tap')
129 | event.cancelTouch = cancelAll
130 | // [by paper] fix -> "TypeError: 'undefined' is not an object (evaluating 'touch.el.trigger'), when double tap
131 | if (touch.el) touch.el.trigger(event)
132 |
133 | // trigger double tap immediately
134 | if (touch.isDoubleTap) {
135 | if (touch.el) touch.el.trigger('doubleTap')
136 | touch = {}
137 | }
138 |
139 | // trigger single tap after 250ms of inactivity
140 | else {
141 | touchTimeout = setTimeout(function(){
142 | touchTimeout = null
143 | if (touch.el) touch.el.trigger('singleTap')
144 | touch = {}
145 | }, 250)
146 | }
147 | }, 0)
148 | } else {
149 | touch = {}
150 | }
151 | deltaX = deltaY = 0
152 |
153 | })
154 | // when the browser window loses focus,
155 | // for example when a modal dialog is shown,
156 | // cancel all ongoing events
157 | .on('touchcancel MSPointerCancel pointercancel', cancelAll)
158 |
159 | // scrolling the window indicates intention of the user
160 | // to scroll, not tap or swipe, so cancel all ongoing events
161 | $(window).on('scroll', cancelAll)
162 | })
163 |
164 | ;['swipe', 'swipeLeft', 'swipeRight', 'swipeUp', 'swipeDown',
165 | 'doubleTap', 'tap', 'singleTap', 'longTap'].forEach(function(eventName){
166 | $.fn[eventName] = function(callback){ return this.on(eventName, callback) }
167 | })
168 | })(Zepto)
169 |
--------------------------------------------------------------------------------
/mock/bookbacket.json:
--------------------------------------------------------------------------------
1 | {
2 | items: [{
3 | ad: 0,
4 | word_count: 999756,
5 | owner: 0,
6 | allow_discount: 0,
7 | ad_time: 3,
8 | click: 8561542,
9 | score_count: 600,
10 | title: "先婚厚爱",
11 | on_sale: true,
12 | comment_count: 61,
13 | ad_duration: 5,
14 | latest_id: 253,
15 | score: 8.41,
16 | allow_free_read: 1,
17 | rights_id: 10039,
18 | channel: [
19 | 2
20 | ],
21 | updated: 1465813422,
22 | finish: true,
23 | tags: [
24 | "现言",
25 | "都市言情"
26 | ],
27 | price: 5,
28 | chapter_count: 254,
29 | authors: "莫萦",
30 | categories: [{
31 | category_id: 100000,
32 | label: "现代言情"
33 | }, {
34 | category_id: 100100,
35 | label: "都市言情"
36 | }, {
37 | category_id: 18000000,
38 | label: "现代言情"
39 | }, {
40 | category_id: 18000100,
41 | label: "都市生活"
42 | }],
43 | rights: "潇湘书院",
44 | level: 0,
45 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p017Bgr0t7WU/iYz4mtvYjzwZjO.jpg",
46 | summary: "江城第一把手被她误以为相亲对象,身为剩女的她第一句话:“去领证吗?”",
47 | fiction_id: 46509,
48 | outer_id: "53e2dde80ea497276b5a2f06",
49 | latest: "番外宝贝女儿"
50 | }, {
51 | ad: 0,
52 | word_count: 4792595,
53 | owner: 0,
54 | allow_discount: false,
55 | ad_time: 3,
56 | click: 106686189,
57 | score_count: 12305,
58 | title: "七界武神",
59 | on_sale: true,
60 | comment_count: 308,
61 | ad_duration: 5,
62 | latest_id: 1535,
63 | score: 8.36,
64 | allow_free_read: 1,
65 | rights_id: 10080,
66 | channel: [
67 | 1
68 | ],
69 | updated: 1468301457,
70 | finish: false,
71 | tags: [
72 | "七界武神",
73 | "武魂",
74 | "叶天",
75 | "异世大陆",
76 | "玄幻",
77 | "异界大陆",
78 | "玄幻奇幻"
79 | ],
80 | price: 5,
81 | chapter_count: 1536,
82 | authors: "叶之凡",
83 | categories: [{
84 | category_id: 10000,
85 | label: "玄幻奇幻"
86 | }, {
87 | category_id: 10200,
88 | label: "异世大陆"
89 | }, {
90 | category_id: 1000000,
91 | label: "玄幻"
92 | }, {
93 | category_id: 1000200,
94 | label: "异界大陆"
95 | }],
96 | rights: "阅文集团旗下创世中文网",
97 | level: 0,
98 | cover: "http://cover.read.duokan.com/mfsv2/download/fdsc3/p01DMhyt0Zkm/sf6mdPxWm06zcc.jpg",
99 | summary: "华夏特种兵穿越武者世界,得到上古魔祖吞噬魔功,练成了吞噬体质,从而踏上巅峰,横扫天下!",
100 | fiction_id: 270794,
101 | outer_id: "558233a550e3a4738f4c81d4",
102 | latest: "第一千五百三十四章 圣主陨落"
103 | }, {
104 | ad: 0,
105 | word_count: 786451,
106 | owner: 0,
107 | allow_discount: 0,
108 | ad_time: 3,
109 | click: 2451425,
110 | score_count: 83,
111 | title: "宠妃难为",
112 | on_sale: true,
113 | comment_count: 0,
114 | ad_duration: 5,
115 | latest_id: 161,
116 | score: 8.62,
117 | allow_free_read: 1,
118 | rights_id: 20004,
119 | channel: [
120 | 2
121 | ],
122 | updated: 1439007915,
123 | finish: true,
124 | tags: [
125 | "原创",
126 | "言情",
127 | "爱情",
128 | "古典架空",
129 | "架空历史",
130 | "古代言情"
131 | ],
132 | price: 3,
133 | chapter_count: 162,
134 | authors: "碧云天",
135 | categories: [{
136 | category_id: 110000,
137 | label: "古代言情"
138 | }, {
139 | category_id: 110100,
140 | label: "架空历史"
141 | }, {
142 | category_id: 17000000,
143 | label: "古代言情"
144 | }, {
145 | category_id: 17000200,
146 | label: "古典架空"
147 | }],
148 | rights: "晋江文学",
149 | level: 0,
150 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p018NN4CqhaC/BmIs9AONWa0zsO.jpg",
151 | summary: "十七岁,仟夕瑶进宫两年,无宠,想的不过是怎么逃离这后宫,结果那许多嫔肚子里没有消息,她却一下子蹦出个...",
152 | fiction_id: 308833,
153 | outer_id: "55c18acf50e3a4299f2b569c",
154 | latest: "第 162 章"
155 | }, {
156 | ad: 0,
157 | word_count: 6422470,
158 | owner: 0,
159 | allow_discount: false,
160 | ad_time: 3,
161 | click: 15451173,
162 | score_count: 5217,
163 | title: "完美世界",
164 | on_sale: true,
165 | comment_count: 240,
166 | ad_duration: 5,
167 | latest_id: 2031,
168 | score: 9.07,
169 | allow_free_read: 1,
170 | rights_id: 10080,
171 | channel: [
172 | 1
173 | ],
174 | updated: 1468265441,
175 | finish: false,
176 | tags: [
177 | "玄幻奇幻",
178 | "玄幻",
179 | "东方玄幻"
180 | ],
181 | price: 5,
182 | chapter_count: 2032,
183 | authors: "辰东",
184 | categories: [{
185 | category_id: 10000,
186 | label: "玄幻奇幻"
187 | }, {
188 | category_id: 10100,
189 | label: "东方玄幻"
190 | }, {
191 | category_id: 1000000,
192 | label: "玄幻"
193 | }, {
194 | category_id: 1000100,
195 | label: "东方玄幻"
196 | }],
197 | rights: "阅文集团旗下起点中文网",
198 | level: 0,
199 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01CBWdv06tQ/0wQb35pmpRhaIr.jpg",
200 | summary: "一粒尘可填海,一根草斩尽日月星辰,弹指间天翻地覆。 群雄并起,万族林立,诸圣争霸,乱天动地。问苍茫大...",
201 | fiction_id: 270644,
202 | outer_id: "558142d650e3a4079d15aab3",
203 | latest: "请假,暂停一天"
204 | }, {
205 | ad: 0,
206 | word_count: 1842693,
207 | owner: 0,
208 | allow_discount: 0,
209 | ad_time: 3,
210 | click: 549763,
211 | score_count: 138,
212 | title: "重生之国民男神",
213 | on_sale: true,
214 | comment_count: 0,
215 | ad_duration: 5,
216 | latest_id: 432,
217 | score: 9.6,
218 | allow_free_read: 1,
219 | rights_id: 10039,
220 | channel: [
221 | 2
222 | ],
223 | updated: 1466750712,
224 | finish: false,
225 | tags: [
226 | "重生之国民男神",
227 | "现代言情",
228 | "都市言情",
229 | "都市生活"
230 | ],
231 | price: 5,
232 | chapter_count: 433,
233 | authors: "水千澈",
234 | categories: [{
235 | category_id: 100000,
236 | label: "现代言情"
237 | }, {
238 | category_id: 100100,
239 | label: "都市言情"
240 | }, {
241 | category_id: 18000000,
242 | label: "现代言情"
243 | }, {
244 | category_id: 18000100,
245 | label: "都市生活"
246 | }],
247 | rights: "潇湘书院",
248 | level: 0,
249 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01OKCgsrZfq/3IzTun8ZP3Yrud.jpg",
250 | summary: "前生司凰被至亲控制陷害,贵为连冠影帝,却死无葬身之地。意外重生,再回起点,获得古怪传承。司凰摸着下巴...",
251 | fiction_id: 317733,
252 | outer_id: "569e9d8150e3a468e9b8ec23",
253 | latest: "不爱你还能爱谁"
254 | }, {
255 | ad: 0,
256 | word_count: 1441873,
257 | owner: 0,
258 | allow_discount: false,
259 | ad_time: 3,
260 | click: 31431177,
261 | score_count: 8745,
262 | title: "盗墓笔记",
263 | on_sale: true,
264 | comment_count: 1927,
265 | ad_duration: 5,
266 | latest_id: 481,
267 | score: 8.99,
268 | allow_free_read: 1,
269 | rights_id: 10080,
270 | channel: [
271 | 1
272 | ],
273 | updated: 1450801819,
274 | finish: true,
275 | tags: [
276 | "升级练功",
277 | "盗墓探险",
278 | "盗墓",
279 | "热血",
280 | "灵异悬疑"
281 | ],
282 | price: 5,
283 | chapter_count: 482,
284 | authors: "南派三叔",
285 | categories: [{
286 | category_id: 50000,
287 | label: "灵异悬疑"
288 | }, {
289 | category_id: 50400,
290 | label: "盗墓探险"
291 | }, {
292 | category_id: 8000000,
293 | label: "灵异"
294 | }, {
295 | category_id: 8000300,
296 | label: "悬疑探险"
297 | }],
298 | rights: "阅文集团旗下起点中文网",
299 | level: 0,
300 | cover: "http://cover.read.duokan.com/mfsv2/download/s010/p01yfPOuJRok/dQJSYfAnD3AB42.jpg",
301 | summary: "这神秘的墓主人到底是谁,他们到底能不能找到真正的棺椁?",
302 | fiction_id: 270635,
303 | outer_id: "5581401c50e3a4079e15a4f9",
304 | latest: "《后记》下"
305 | }],
306 | result: 0
307 | }
--------------------------------------------------------------------------------
/js/jquery.jsonp.js:
--------------------------------------------------------------------------------
1 | /*
2 | * jQuery JSONP Core Plugin 2.4.0 (2012-08-21)
3 | *
4 | * https://github.com/jaubourg/jquery-jsonp
5 | *
6 | * Copyright (c) 2012 Julian Aubourg
7 | *
8 | * This document is licensed as free software under the terms of the
9 | * MIT License: http://www.opensource.org/licenses/mit-license.php
10 | */
11 | ( function( $ ) {
12 |
13 | // ###################### UTILITIES ##
14 |
15 | // Noop
16 | function noop() {
17 | }
18 |
19 | // Generic callback
20 | function genericCallback( data ) {
21 | lastValue = [ data ];
22 | }
23 |
24 | // Call if defined
25 | function callIfDefined( method , object , parameters ) {
26 | return method && method.apply( object.context || object , parameters );
27 | }
28 |
29 | // Give joining character given url
30 | function qMarkOrAmp( url ) {
31 | return /\?/ .test( url ) ? "&" : "?";
32 | }
33 |
34 | var // String constants (for better minification)
35 | STR_ASYNC = "async",
36 | STR_CHARSET = "charset",
37 | STR_EMPTY = "",
38 | STR_ERROR = "error",
39 | STR_INSERT_BEFORE = "insertBefore",
40 | STR_JQUERY_JSONP = "_jqjsp",
41 | STR_ON = "on",
42 | STR_ON_CLICK = STR_ON + "click",
43 | STR_ON_ERROR = STR_ON + STR_ERROR,
44 | STR_ON_LOAD = STR_ON + "load",
45 | STR_ON_READY_STATE_CHANGE = STR_ON + "readystatechange",
46 | STR_READY_STATE = "readyState",
47 | STR_REMOVE_CHILD = "removeChild",
48 | STR_SCRIPT_TAG = "