├── .gitignore ├── License ├── app.js ├── bin └── www ├── configs └── config.js ├── package-lock.json ├── package.json ├── readme.md ├── routes ├── ip.js ├── joke.js ├── mobile.js ├── netease.js ├── test.js ├── v1.js ├── v2.js ├── weather.js └── welcome.js ├── static ├── css │ ├── font-awesome.min.css │ ├── main.css │ └── reset.css ├── docs.html ├── docs.md ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ ├── fontawesome-webfont.woff │ └── fontawesome-webfont.woff2 ├── images │ ├── aliyun.png │ ├── banner.jpg │ ├── fav.svg │ ├── fav_16x16.png │ ├── fav_36x36.png │ ├── fav_72x72.png │ ├── favicon.ico │ ├── favicon_36.ico │ ├── favicon_72.ico │ └── qiniu.png ├── index.html └── js │ ├── base64.js │ ├── jquery.min.js │ ├── main.js │ ├── marked.js │ ├── skel.min.js │ ├── util.js │ └── v1.js ├── utils ├── disabledIP.js ├── encrypt.js ├── logUtils.js ├── util.js └── utils.js └── views ├── error.pug ├── layout.pug ├── test.pug └── welcome.pug /.gitignore: -------------------------------------------------------------------------------- 1 | .sass 2 | .DS_Store 3 | node_modules/ 4 | npm-debug.log 5 | config/.avoscloud/ 6 | .avoscloud/ 7 | -------------------------------------------------------------------------------- /License: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 xCss (xioveliu@gmail.com) and contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var path = require('path'); 4 | var favicon = require('serve-favicon'); 5 | var logger = require('morgan'); 6 | var cookieParser = require('cookie-parser'); 7 | var bodyParser = require('body-parser'); 8 | var ga = require('universal-analytics'); 9 | //Welcome Page 10 | var welcome = require('./routes/welcome'); 11 | //日志输出 12 | var logUtils = require('./utils/logUtils'); 13 | //查询ip 14 | var ip = require('./routes/ip'); 15 | //网易云音乐 16 | var netease = require('./routes/netease'); 17 | //JsonBird version 1.0 18 | var v1 = require('./routes/v1'); 19 | var v2 = require('./routes/v2'); 20 | //笑话接口 21 | var joke = require('./routes/joke'); 22 | //手机号码归属地接口 23 | var mobile = require('./routes/mobile'); 24 | //天气 25 | var weather = require('./routes/weather'); 26 | //test 27 | //var test = require('./routes/test'); 28 | 29 | var app = express(); 30 | app.set('views', path.join(__dirname, 'views')); 31 | // view engine setup 32 | app.set('view engine', 'pug'); 33 | app.enable('trust proxy'); 34 | app.disable('x-powered-by'); 35 | // uncomment after placing your favicon in /public 36 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 37 | // app.use(logger('combined', { 38 | // skip: function(req, res) { return res.statusCode < 400 } 39 | // })); 40 | //app.use(bodyParser.raw({ type: '*/*' })); 41 | app.use(function(req, res, next) { 42 | // 设置跨域头 ************** 本地使用时请去掉以下3行的注释 43 | // res.header("Access-Control-Allow-Origin", "*"); 44 | // res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept,Access-Control-Allow-Origin"); 45 | // res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS"); 46 | 47 | /*** 48 | * 处理OPTIONS请求 49 | */ 50 | if (req.method === 'OPTIONS') { 51 | res.sendStatus(200); 52 | } else next(); 53 | }); 54 | app.use(bodyParser.json()); 55 | app.use(bodyParser.urlencoded({ extended: true })); 56 | 57 | app.use(cookieParser()); 58 | var visitor = ga('UA-61934506-2'); 59 | app.use(function(req,res,next){ 60 | var path = req.path; 61 | var ip = req.ip; 62 | var ua = req['headers']['user-agent']; 63 | var dr = req.get('Referrer') || req['headers']['referer'] || '/'; 64 | let dh = req.query.url || req.params.url || ''; 65 | 66 | let params = { 67 | dp:req.path, 68 | dh:dh, 69 | uip:ip, 70 | ua:ua, 71 | dr:dr 72 | } 73 | if(path.indexOf('/static/') == -1){ 74 | visitor.pageview(params).send(); 75 | } 76 | next() 77 | }); 78 | //静态文件访问路径 79 | app.use('/static', express.static(path.join(__dirname, 'static'))); 80 | app.use(favicon(__dirname + '/static/images/favicon.ico')); 81 | 82 | 83 | 84 | app.use('/', welcome); 85 | // app.use('/test', test); 86 | app.use('/ip', ip); 87 | app.use('/v1', v1); 88 | app.use('/v2', v2); 89 | app.use('/netease', netease); 90 | app.use('/joke', joke); 91 | app.use('/mobile', mobile); 92 | app.use('/weather', weather); 93 | 94 | 95 | /** 96 | * Robots.txt 97 | */ 98 | app.use('/robots.txt', function(req, res, next) { 99 | res.header('content-type', 'text/plain'); 100 | res.send('User-Agent: * \nAllow: /'); 101 | }); 102 | 103 | // catch 404 and forward to error handler 104 | app.use(function(req, res, next) { 105 | var err = new Error('Not Found'); 106 | err.status = 404; 107 | 108 | next(err); 109 | }); 110 | 111 | // error handlers 112 | 113 | // development error handler 114 | // will print stacktrace 115 | if (app.get('env') === 'development') { 116 | app.use(function(err, req, res, next) { 117 | res.status(err.status || 500); 118 | res.send({ 119 | data: {}, 120 | status: { 121 | code: err.status, 122 | message: err.message 123 | } 124 | }); 125 | }); 126 | } 127 | 128 | // production error handler 129 | // no stacktraces leaked to user 130 | app.use(function(err, req, res, next) { 131 | res.status(err.status || 500); 132 | res.send({ 133 | data: {}, 134 | status: { 135 | code: err.status, 136 | message: err.message 137 | } 138 | }); 139 | }); 140 | 141 | 142 | 143 | module.exports = app; -------------------------------------------------------------------------------- /bin/www: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | process.env.TZ = 'Asia/Shanghai'; 7 | 8 | var app = require('../app'); 9 | var debug = require('debug')('API:server'); 10 | var http = require('http'); 11 | //var cluster = require('cluster'); 12 | //var cpus = require('os').cpus().length; 13 | 14 | // if (cluster.isMaster) { 15 | // console.log('[master] ' + "start master..."); 16 | 17 | // for (var i = 0; i < cpus; i++) { 18 | // cluster.fork(); 19 | // } 20 | 21 | // cluster.on('listening', function(worker, address) { 22 | // console.log('[master] ' + 'listening: worker' + worker.id + ',pid:' + worker.process.pid + ', Address:' + address.address + ":" + address.port); 23 | // }); 24 | // cluster.on('exit', function(worker, code, signal) { 25 | // console.log(`[master] pid:${worker.id} is died, restarting...`); 26 | // cluster.fork(); 27 | // }); 28 | // } else { 29 | /** 30 | * Get port from environment and store in Express. 31 | */ 32 | 33 | var port = normalizePort(process.env.PORT || '1000'); 34 | app.set('port', port); 35 | 36 | /** 37 | * Create HTTP server. 38 | */ 39 | 40 | var server = http.createServer(app); 41 | 42 | /** 43 | * Listen on provided port, on all network interfaces. 44 | */ 45 | 46 | server.listen(port); 47 | server.on('error', onError); 48 | server.on('listening', onListening); 49 | 50 | /** 51 | * Normalize a port into a number, string, or false. 52 | */ 53 | 54 | function normalizePort(val) { 55 | var port = parseInt(val, 10); 56 | 57 | if (isNaN(port)) { 58 | // named pipe 59 | return val; 60 | } 61 | 62 | if (port >= 0) { 63 | // port number 64 | return port; 65 | } 66 | 67 | return false; 68 | } 69 | 70 | /** 71 | * Event listener for HTTP server "error" event. 72 | */ 73 | 74 | function onError(error) { 75 | if (error.syscall !== 'listen') { 76 | throw error; 77 | } 78 | 79 | var bind = typeof port === 'string' ? 80 | 'Pipe ' + port : 81 | 'Port ' + port; 82 | 83 | // handle specific listen errors with friendly messages 84 | switch (error.code) { 85 | case 'EACCES': 86 | console.error(bind + ' requires elevated privileges'); 87 | process.exit(1); 88 | break; 89 | case 'EADDRINUSE': 90 | console.error(bind + ' is already in use'); 91 | process.exit(1); 92 | break; 93 | default: 94 | throw error; 95 | } 96 | } 97 | 98 | /** 99 | * Event listener for HTTP server "listening" event. 100 | */ 101 | 102 | function onListening() { 103 | var addr = server.address(); 104 | var bind = typeof addr === 'string' ? 105 | 'pipe ' + addr : 106 | 'port ' + addr.port; 107 | debug('Listening on ' + bind); 108 | } 109 | //} -------------------------------------------------------------------------------- /configs/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | //site config 3 | site: { 4 | title: 'JsonBird', 5 | author: 'xCss ', 6 | description: '业界领先的远程数据接口代理服务' 7 | } 8 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "JsonBird", 3 | "version": "1.4.0", 4 | "description": "A remote data interface proxy service", 5 | "author": { 6 | "name": "xCss", 7 | "mail": "yy.liu@foxmail.com" 8 | }, 9 | "license": "MIT", 10 | "private": true, 11 | "scripts": { 12 | "start": "supervisor node ./bin/www", 13 | "super": "supervisor ./bin/www" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/xCss/JsonBird.git" 18 | }, 19 | "keywords": [ 20 | "JsonBird", 21 | "proxy", 22 | "data interface proxy", 23 | "remote data interface proxy" 24 | ], 25 | "bugs": { 26 | "url": "https://github.com/xCss/JsonBird/issues" 27 | }, 28 | "readme": "./readme.md", 29 | "homepage": "https://github.com/xCss/JsonBird#readme", 30 | "dependencies": { 31 | "big-integer": "^1.6.17", 32 | "blueimp-md5": "^2.7.0", 33 | "body-parser": "~1.15.1", 34 | "cookie-parser": "~1.4.3", 35 | "debug": "~2.2.0", 36 | "express": "~4.13.4", 37 | "express-generator": "^4.13.4", 38 | "helmet": "^2.3.0", 39 | "js-base64": "^2.1.9", 40 | "lodash": "^4.17.4", 41 | "morgan": "~1.7.0", 42 | "node-ga": "^0.1.1", 43 | "pug": "^2.0.0-beta6", 44 | "querystring": "^0.2.0", 45 | "request": "^2.81.0", 46 | "serve-favicon": "~2.3.0", 47 | "superagent": "^2.3.0", 48 | "universal-analytics": "^0.4.16" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # JsonBird :hatching_chick: 2 | A remote data interface proxy service | 一个远程数据接口代理服务 3 | 4 | 5 | ![npm:v3.10.3](https://img.shields.io/badge/npm-v3.10.3-blue.svg) 6 | ![express:v4.13.4](https://img.shields.io/badge/express-v4.13.4-blue.svg) 7 | ![node.js support:v0.10.0+](https://img.shields.io/badge/node.js%20supports-v0.10.0+-green.svg) 8 | ![build:passing](https://img.shields.io/badge/build-passing-green.svg) 9 | [![license:MIT](https://img.shields.io/badge/license-MIT-blue.svg)](/License) 10 | [![releases:v1.4.2](https://img.shields.io/badge/releases-v1.4.2-blue.svg)](https://github.com/xCss/JsonBird/releases) 11 | 12 | **[HomePage](https://bird.ioliu.cn)** | [Demo](https://jsfiddle.net/LNing/duL5Lby7/) 13 | 14 | ## 必要条件 15 | - [Node.js](https://nodejs.org) 0.10+ 16 | - [npm](https://www.npmjs.com/) (normally comes with Node.js) 17 | 18 | ## 安装、运行、访问,一气呵成 19 | ``` bash 20 | # clone 21 | $ git clone https://github.com/xcss/JsonBird.git 22 | # install 23 | $ cd JsonBird && npm i 24 | # run service 25 | $ npm start 26 | # open your browser,input link `http://127.0.0.1:1000` 27 | ``` 28 | 29 | ## 快速开始 30 | 1. https://bird.ioliu.cn/v1?url=http[s]://YouWantProxyJSONUrls.com&[?]params1=val1¶ms2=val2[&callback=fn] 31 | 2. [详细食用方法请点此](https://github.com/xCss/JsonBird/wiki) 32 | 33 | ## 我们能做到 34 | 1. :sparkles: 让不支持跨域的远程数据接口支持跨域 35 | 2. :muscle: 让不支持JSONP的远程数据接口支持JSONP (添加参数`&callback=fn`) 36 | 37 | 38 | ## License 39 | 40 | [MIT License](/License) 41 | -------------------------------------------------------------------------------- /routes/ip.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var router = express.Router(); 4 | var base = 'http://apis.juhe.cn/ip/ip2addr?key=28c0a6a5eb9cca3f38bc5877a83c9868'; 5 | //var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' }; 6 | router.get('/*', function(req, res, next) { 7 | ip2address(req, res, next); 8 | }); 9 | router.post('/*', function(req, res, next) { 10 | ip2address(req, res, next); 11 | }); 12 | /** 13 | * 获取IP地址所对应的地区 14 | */ 15 | function ip2address(req, res, next) { 16 | var ip = req.query.ip || req.body.ip || req.headers['x-real-ip'] || req.ip.replace(/\:\:1/, '127.0.0.1'); 17 | var callback = req.query.callback || req.body.callback; 18 | var type = req.query.type || req.body.type; 19 | var url = base + '&ip=' + ip; 20 | if (type) { 21 | url += '&dtype=' + type; 22 | } 23 | var output = { 24 | data: {}, 25 | status: { 26 | code: 200, 27 | message: '' 28 | } 29 | }; 30 | request.get(url).end(function(err, response) { 31 | 32 | var body = {}; 33 | if (response && response.text) { 34 | body = response.text; 35 | } else if (response && response.body) { 36 | body = response.body; 37 | } 38 | 39 | if (type !== 'xml') { 40 | if (typeof body === 'string') { 41 | try { 42 | body = JSON.parse(body); 43 | } catch (e) { 44 | output.status = { 45 | code: -1 46 | }; 47 | } 48 | } 49 | 50 | output.data = (body.result && body.result.data ? body.result.data : body.result) || {}; 51 | output.data['ip'] = ip; 52 | if (!err && response.status === 200) { 53 | // 54 | } else { 55 | output.status = { 56 | code: -1, 57 | message: err || body.reason || 'Something bad happend.' 58 | }; 59 | } 60 | if (callback) { 61 | return res.jsonp(output); 62 | } else { 63 | return res.json(output); 64 | } 65 | } else { 66 | res.header('content-type', 'text/xml;charset=utf-8'); 67 | return res.send(body); 68 | } 69 | 70 | }); 71 | } 72 | 73 | 74 | module.exports = router; -------------------------------------------------------------------------------- /routes/joke.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var router = express.Router(); 4 | var key = '64a40e3c55e88cc8cd66a78d030bddce'; 5 | //var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' }; 6 | /** 7 | * Get 请求 8 | */ 9 | router.get('/', function(req, res, next) { 10 | getJOKE(req, res, next); 11 | }); 12 | /** 13 | * 随机获取 14 | */ 15 | router.get('/rand', function(req, res, next) { 16 | getJOKE(req, res, next, 'rand'); 17 | }); 18 | 19 | /** 20 | * Post 请求 21 | */ 22 | router.post('/', function(req, res, next) { 23 | getJOKE(req, res, next); 24 | }); 25 | /** 26 | * 随机获取 27 | */ 28 | router.post('/rand', function(req, res, next) { 29 | getJOKE(req, res, next, 'rand'); 30 | }); 31 | 32 | /** 33 | * 统一的请求 34 | */ 35 | function getJOKE(req, res, next, op) { 36 | var page = req.query.page || req.body.page || 1; 37 | var pagesize = req.query.pagesize || req.body.pagesize || 1; 38 | var sort = req.query.sort || req.body.sort; 39 | var time = req.query.time || req.body.time; 40 | var type = req.query.type || req.body.type || 'pic'; 41 | var callback = req.query.callback || req.body.callback; 42 | var url = ''; 43 | if (!!op && op === 'rand') { 44 | if (type !== 'pic') { 45 | type = null; 46 | } 47 | url = "http://v.juhe.cn/joke/randJoke.php?key=" + key; 48 | if (!!type) { 49 | url += "&type=" + type; 50 | } 51 | } else { 52 | url = "http://japi.juhe.cn/joke/"; 53 | if (!!type && type === 'text') { 54 | url += "content/text.from?key="; 55 | } else { 56 | url += "img/text.from?key="; 57 | } 58 | url += key + "&page=" + page + "&pagesize=" + pagesize; 59 | if (!!sort && !!time) { 60 | url += "&sort=" + sort + "&time=" + time; 61 | url = url.replace(/text/, 'list'); 62 | } 63 | } 64 | var output = { 65 | data: {}, 66 | status: { 67 | code: 200, 68 | message: '' 69 | } 70 | }; 71 | request.get(url).end(function(err, response) { 72 | var body = {}; 73 | if (response && response.text) { 74 | body = response.text; 75 | } else if (response && response.body) { 76 | body = response.body; 77 | } 78 | if (typeof body === 'string') { 79 | try { 80 | body = JSON.parse(body); 81 | } catch (e) { 82 | output.status = { 83 | code: -1 84 | }; 85 | } 86 | } 87 | output.data = (body.result && body.result.data ? body.result.data : body.result) || {}; 88 | if (!err && response.status === 200 && body.error_code === 0) { 89 | // 90 | } else { 91 | output.status = { 92 | code: -1, 93 | message: err || body.reason || 'Something bad happend.' 94 | }; 95 | } 96 | if (callback) { 97 | return res.jsonp(output); 98 | } else { 99 | return res.json(output); 100 | } 101 | }); 102 | } 103 | 104 | 105 | 106 | module.exports = router; -------------------------------------------------------------------------------- /routes/mobile.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const request = require('superagent'); 3 | const utils = require('../utils/utils'); 4 | const router = express.Router(); 5 | const base = 'http://jshmgsdmfb.market.alicloudapi.com/shouji/query'; 6 | const APPCODE = '5db8c4dbb31b424ab673cec87fb6770e'; 7 | router.get('/', function(req, res, next) { 8 | getMobile(req, res, next); 9 | }); 10 | router.post('/', function(req, res, next) { 11 | getMobile(req, res, next); 12 | }); 13 | 14 | let getMobile = (req, res, next) => { 15 | let params = utils.convert(req,res,next,base); 16 | let config = params[0]; 17 | let protocol = params[1]; 18 | let host = params[2]; 19 | let cb = params[3]; 20 | let _params = params[4]; 21 | let output = { 22 | data: {}, 23 | status: { 24 | code: -1, 25 | message: '请确定你的请求方式像这样:/mobile?shouji=13800138000' 26 | } 27 | }; 28 | if(_params['shouji']){ 29 | config['headers'] = { 30 | "Host":"jshmgsdmfb.market.alicloudapi.com", 31 | "X-Ca-Timestamp":Date.now(), 32 | "gateway_channel":"http", 33 | "X-Ca-Request-Mode":"debug", 34 | "X-Ca-Key":"24605515", 35 | "X-Ca-Stage":"RELEASE", 36 | "Content-Type":"application/json; charset=utf-8", 37 | "Authorization":`APPCODE ${APPCODE}` 38 | } 39 | //config['gzip'] = null; 40 | //res.send(config) 41 | utils.createServer(config).then(ret => { 42 | cb && res.jsonp(ret) || res.send(ret); 43 | }).catch(ex => { 44 | output = { 45 | status: { 46 | code: -2, 47 | message: Object.keys(ex).length>0 ? ex : 'unknow error, please checked your phone number' 48 | } 49 | } 50 | cb && res.jsonp(output) || res.send(output); 51 | }); 52 | }else{ 53 | cb && res.jsonp(output) || res.send(output); 54 | } 55 | } 56 | 57 | module.exports = router; -------------------------------------------------------------------------------- /routes/netease.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | const util = require('../utils/util') 5 | 6 | const links = { 7 | song: '/weapi/v3/song/detail', 8 | song_url: '/weapi/song/enhance/player/url', 9 | playlist: '/weapi/v3/playlist/detail' 10 | } 11 | 12 | /* GET users listing. */ 13 | router.get('/:channel', function(req, res, next) { 14 | request(req,res,next) 15 | }); 16 | function request(req,res,next){ 17 | 18 | const id = req.query.id 19 | const br = req.query.br || 999000 20 | const channel = req.params['channel'] 21 | // console.log(req) 22 | let config = { 23 | path: links[channel], 24 | params: { 25 | "ids": [id], 26 | "br": 999000, 27 | "csrf_token": "" 28 | } 29 | } 30 | switch (channel) { 31 | case 'playlist': 32 | config['params'] = { 33 | "id": id, 34 | "offset": 0, 35 | "total": true, 36 | "limit": 1000, 37 | "n": 1000, 38 | "csrf_token": "" 39 | } 40 | break; 41 | case 'song': 42 | config['params'] = { 43 | 'c': JSON.stringify([{ id: id }]), 44 | "ids": '[' + id + ']', 45 | "csrf_token": "" 46 | } 47 | break; 48 | default: 49 | res.send({ 50 | data: {}, 51 | status: { 52 | code: -1, 53 | msg: 'no support url. Please set `song` or `playlist`' 54 | } 55 | }) 56 | return; 57 | break; 58 | } 59 | util.requestServer(config).then(ret => { 60 | if (channel == 'song') { 61 | let songs = ret.songs 62 | if (songs && songs.length) { 63 | config['path'] = links.song_url 64 | config['params'] = { 65 | "ids": [id], 66 | "br": br, 67 | "csrf_token": "" 68 | } 69 | util.requestServer(config).then(rt => { 70 | let song = songs[0] 71 | song['mp3'] = rt.data[0] 72 | res.send({ 73 | data: song, 74 | status: { 75 | code: 200, 76 | msg: '' 77 | } 78 | }) 79 | }).catch(ex => { 80 | res.send({ 81 | data: ex, 82 | status: { 83 | code: -1, 84 | msg: 'something happend. Please checked your id or url' 85 | } 86 | }) 87 | }) 88 | } else { 89 | res.send({ 90 | data: {}, 91 | status: { 92 | code: -1, 93 | msg: 'sorry, no result, please changed song id.' 94 | } 95 | }) 96 | } 97 | } else { 98 | res.send(ret) 99 | } 100 | 101 | }).catch(err => { 102 | console.log(err) 103 | res.send({ 104 | data: err, 105 | status: { 106 | code: -1, 107 | msg: 'something happend. Please checked your id or url' 108 | } 109 | }) 110 | }) 111 | } 112 | 113 | 114 | module.exports = router; -------------------------------------------------------------------------------- /routes/test.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var router = express.Router(); 4 | var crypto = require('../utils/Crypto'); 5 | /* GET test page. */ 6 | router.get('/', function(req, res, next) { 7 | var id = req.query.id || ''; 8 | if (id) { 9 | var sid = crypto.encrypted_id(id); 10 | var k = 'http://m2.music.126.net/' + sid + '/' + id + '.mp3' 11 | res.send({ 12 | link: k 13 | }); 14 | } else { 15 | res.send({ msg: 'no id' }); 16 | } 17 | }); 18 | 19 | 20 | module.exports = router; -------------------------------------------------------------------------------- /routes/v1.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var router = express.Router(); 4 | var disabledIP = require('../utils/disabledIP').list; 5 | var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' }; 6 | 7 | router.get('/*', function(req, res, next) { 8 | getJSON(req, res, next); 9 | }); 10 | 11 | router.post('/*', function(req, res, next) { 12 | getJSON(req, res, next); 13 | }); 14 | 15 | function getJSON(req, res, next) { 16 | var ip = req.headers['x-real-ip'] ? req.headers['x-real-ip'] : req.ip.replace(/::ffff:/, ''); 17 | var host = req.hostname; 18 | var protocol = req.protocol; 19 | var originalUrl = req.originalUrl; 20 | var url = req.query.url || req.body.url; 21 | var callback = req.query.callback || req.body.callback; 22 | var params = req.body; 23 | var output = { 24 | data: { 25 | IP: ip, 26 | Info: 'Please Set URL Like This: ' + protocol + '://' + host + '/v1/?url=http[s]://YourWantProxyUrl.Com' 27 | }, 28 | status: { 29 | code: 200, 30 | message: '' 31 | } 32 | }; 33 | if (disabledIP.indexOf(ip) > -1) { 34 | 35 | output['data']['info'] = '很抱歉,您的IP因为滥用接口已被禁用,如有疑问,请致信 xioveliu@gmail.com '; 36 | output['status'] = -1; 37 | output['message'] = 'DISABLED IP'; 38 | res.json(output); 39 | 40 | } else { 41 | 42 | method = req.method.toUpperCase(); 43 | var _cookies = req.cookies; 44 | var headers = { 'user-agent': req.headers['user-agent'] }; 45 | //console.log(headers); 46 | if (url) { 47 | var _temp = {}; 48 | switch (method) { 49 | case 'GET': 50 | // get request 51 | if (/\?url\=/.test(originalUrl)) { 52 | url = originalUrl.split('url=')[1]; 53 | } 54 | 55 | if (params) { 56 | for (var i in params) { 57 | _temp[i] = encodeURI(params[i]); 58 | } 59 | } 60 | url = url.indexOf('?') === -1 ? url.replace(/\&/, '?') : url; 61 | url = /^(http|https)\:\/\//.test(url) ? url : 'http://' + url; 62 | url = url.replace(/\&callback\=(\w+)/, ''); 63 | request 64 | .get(url) 65 | .set(headers) 66 | .set(_cookies) 67 | .query(_temp) 68 | .end(function(err, response) { 69 | var body = {}; 70 | if (response && response.text) { 71 | body = response.text; 72 | } else if (response && response.body) { 73 | body = response.body; 74 | } 75 | if (typeof body === 'string') { 76 | try { 77 | body = JSON.parse(body); 78 | } catch (e) { 79 | output.status = { 80 | code: -1 81 | }; 82 | } 83 | } 84 | if (!err && response.statusCode == 200) { 85 | output = body; 86 | } else { 87 | output = { 88 | data: {}, 89 | status: { 90 | code: -1, 91 | message: err || 'Something bad happend.' 92 | } 93 | }; 94 | } 95 | if (callback) { 96 | res.jsonp(output); 97 | } else { 98 | res.json(output); 99 | } 100 | }); 101 | break; 102 | default: 103 | // post request 104 | if (params) { 105 | for (var i in params) { 106 | _temp[i] = params[i]; 107 | } 108 | } 109 | request 110 | .post(url) 111 | .set(headers) 112 | .set(_cookies) 113 | .type('form') 114 | .send(_temp) 115 | .end(function(err, response) { 116 | var body = {}; 117 | if (response && response.text) { 118 | body = response.text; 119 | } else if (response && response.body) { 120 | body = response.body; 121 | } 122 | if (typeof body === 'string') { 123 | try { 124 | body = JSON.parse(body); 125 | } catch (e) { 126 | output.status = { 127 | code: -1 128 | }; 129 | } 130 | } 131 | if (!err && response.statusCode == 200) { 132 | output = body; 133 | } else { 134 | output = { 135 | data: {}, 136 | status: { 137 | code: -1, 138 | message: err || 'Something bad happend.' 139 | } 140 | }; 141 | } 142 | if (callback) { 143 | res.jsonp(output); 144 | } else { 145 | res.json(output); 146 | } 147 | }); 148 | break; 149 | } 150 | } else { 151 | if (callback) { 152 | res.jsonp(output); 153 | } else { 154 | res.json(output); 155 | } 156 | } 157 | } 158 | 159 | } 160 | 161 | 162 | 163 | module.exports = router; -------------------------------------------------------------------------------- /routes/v2.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const request = require('request'); 3 | const router = express.Router(); 4 | const disabledIP = require('../utils/disabledIP').list; 5 | const qs = require('qs'); 6 | const utils = require('../utils/utils'); 7 | 8 | router.get('/*', function(req, res, next) { 9 | convert(req, res, next) 10 | }); 11 | 12 | router.post('/*', function(req, res, next) { 13 | convert(req, res, next) 14 | }); 15 | 16 | const convert = (req, res, next) => { 17 | let params = utils.convert(req,res,next); 18 | let config = params[0]; 19 | let protocol = params[1]; 20 | let host = params[2]; 21 | let cb = params[3]; 22 | let output = { 23 | data: {}, 24 | status: { 25 | code: -1, 26 | message: 'Please Set URL Like This: ' + protocol + '://' + host + '/v2/?url=http[s]://YourWantProxyUrl.Com' 27 | } 28 | }; 29 | if(config.url){ 30 | utils.createServer(config).then(ret => { 31 | cb && res.jsonp(ret) || res.send(ret); 32 | }).catch(ex => { 33 | output = { 34 | status: { 35 | code: -2, 36 | message: Object.keys(ex).length>0 ? ex : 'unknow error, please checked your link' 37 | } 38 | } 39 | cb && res.jsonp(output) || res.send(output); 40 | }); 41 | }else{ 42 | cb && res.jsonp(output) || res.send(output); 43 | } 44 | } 45 | 46 | module.exports = router; -------------------------------------------------------------------------------- /routes/weather.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const request = require('superagent'); 3 | const utils = require('../utils/utils'); 4 | const router = express.Router(); 5 | const base = 'http://jisutqybmf.market.alicloudapi.com/weather/query'; 6 | const APPCODE = process.env.APPCODE ; 7 | 8 | router.get('/*', function(req, res, next) { 9 | getWeather(req, res, next); 10 | }); 11 | router.post('/*', function(req, res, next) { 12 | getWeather(req, res, next); 13 | }); 14 | 15 | let getWeather = (req, res, next) => { 16 | let params = utils.convert(req,res,next,base); 17 | let config = params[0]; 18 | let protocol = params[1]; 19 | let host = params[2]; 20 | let cb = params[3]; 21 | let _params = params[4]; 22 | let output = { 23 | data: {}, 24 | status: { 25 | code: -1, 26 | message: '请确定你的请求方式像这样:/weather?city=番禺' 27 | } 28 | }; 29 | let referer = config['headers']['referer'] 30 | let origin = config['headers']['origin'] 31 | if((referer && referer.indexOf('180.169.17.10')>-1) || (origin && origin.indexOf('180.169.17.10')>-1)){ 32 | output = { 33 | status: { 34 | code: -2, 35 | message: '很抱歉,由于您的使用量过大,现已停止您的数据请求,如需商务合作请联系hi@big.moe' 36 | } 37 | } 38 | cb && res.jsonp(output) || res.send(output); 39 | return 40 | } 41 | if(_params['city']){ 42 | config['headers'] = { 43 | "Host":"jisutqybmf.market.alicloudapi.com", 44 | "X-Ca-Timestamp":Date.now(), 45 | "gateway_channel":"http", 46 | "X-Ca-Request-Mode":"debug", 47 | "X-Ca-Key":"24605515", 48 | "X-Ca-Stage":"RELEASE", 49 | "Content-Type":"application/json; charset=utf-8", 50 | "Authorization":`APPCODE ${APPCODE}` 51 | } 52 | //config['gzip'] = ''; 53 | utils.createServer(config).then(ret => { 54 | cb && res.jsonp(ret) || res.send(ret); 55 | }).catch(ex => { 56 | output = { 57 | status: { 58 | code: -2, 59 | message: Object.keys(ex).length>0 ? ex : 'unknow error, please checked your city name' 60 | } 61 | } 62 | console.log(`cb:${cb}`) 63 | cb && res.jsonp(output) || res.json(output); 64 | }); 65 | }else{ 66 | cb && res.jsonp(output) || res.json(output); 67 | } 68 | } 69 | 70 | module.exports = router; -------------------------------------------------------------------------------- /routes/welcome.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var request = require('superagent'); 3 | var config = require('../configs/config').site; 4 | var cookie = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36' }; 5 | var router = express.Router(); 6 | 7 | /* GET home page. */ 8 | router.get('/', function(req, res, next) { 9 | //var ip = req.ip; 10 | var ip = req.headers['x-real-ip']; 11 | ip2addr(ip, function(data) { 12 | var params = { 13 | head: config.title, 14 | title: 'Welcome | ' + config.title + ' - ' + config.description, 15 | description: config.description 16 | }; 17 | if (data) { 18 | params['address'] = '欢迎来自' + data.area + data.location + '的朋友'; 19 | } 20 | res.render('welcome', params); 21 | }); 22 | 23 | }); 24 | 25 | function ip2addr(ip, callback) { 26 | request 27 | .get('http://apis.juhe.cn/ip/ip2addr') 28 | .query({ ip: ip, key: '28c0a6a5eb9cca3f38bc5877a83c9868' }) 29 | .set(cookie) 30 | .end(function(err, res) { 31 | var body = {}; 32 | if (res && res.text) { 33 | body = res.text; 34 | } else if (res && res.body) { 35 | body = res.body; 36 | } 37 | if (!err && res.statusCode == 200) { 38 | if (typeof body === 'string') { 39 | try { 40 | body = JSON.parse(body); 41 | } catch (e) {} 42 | } 43 | callback && callback(body.result); 44 | } else { 45 | console.log(' / request info:' + err); 46 | callback && callback(); 47 | } 48 | }); 49 | } 50 | 51 | 52 | module.exports = router; -------------------------------------------------------------------------------- /static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.6.3 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.6.3');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.6.3') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.6.3') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.6.3') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.6.3') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.6.3#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /static/css/reset.css: -------------------------------------------------------------------------------- 1 | *, :after, :before{box-sizing: border-box;}html{color:#000}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0}body{font:12px/1.5 'Lantinghei SC','HanHei SC','PingFang SC',arial,'Microsoft Yahei',georgia,verdana,helvetica,sans-serif;color:#000}ul,ol,menu{list-style:none}fieldset,img{border:none}img,object,select,input,textarea,button{outline:0;vertical-align:middle}button{border:none;cursor:pointer;padding:0}table{border-collapse:collapse;border-spacing:0}article,aside,footer,header,section,nav,menu,figure,figcaption,hgroup,details{display:block}address,caption,cite,code,dfn,em,strong,th,var,i,b,small,abbr{font-style:normal;font-weight:400}caption,th{text-align:left}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:400}abbr,acronym{border:0;font-variant:normal}sup{vertical-align:text-top}sub{vertical-align:text-bottom}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit}legend{color:#000}a{text-decoration:none}::selection{background-color:#39f;color:#fff}::-moz-selection{background-color:#39f;color:#fff}q:before,q:after{content:''}a:hover{text-decoration:underline} -------------------------------------------------------------------------------- /static/docs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 接口文档 | JsonBird - 业界领先的远程数据接口代理服务 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 33 | 95 | 96 | 97 | 98 | 110 | 112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | 138 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /static/docs.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | - [v1](#-v1-) 3 | - [ip](#-ip-) 4 | - [joke](#-joke-) 5 | - [mobile](#-mobile-) 6 | - [netease](#-netease-) 7 | - [weather](#-weather-) 8 | 9 | 10 | ## `v1`代理接口: 11 | - 请求方式:`GET/POST` 12 | - 请求地址:https://bird.ioliu.cn/v1 13 | - 返回值类型:`json/jsonp` 14 | 15 | |名称|类型|必须|说明| 16 | |----|----|----|----| 17 | |url|string|是|需要代理的远程数据接口| 18 | |callback|string|否|回调函数名| 19 | 20 | ## `ip`地址查询接口 21 | - 请求方式:`GET/POST` 22 | - 请求地址:https://bird.ioliu.cn/ip 23 | - 返回值类型:`json/jsonp/xml` 24 | 25 | |名称|类型|必须|说明| 26 | |----|----|----|----| 27 | |ip|string|否|要查询的ip,(不带ip参数则查询当前浏览者ip)| 28 | |type|string|否|返回数据的格式,`xml`或`json`,默认`json`| 29 | |callback|string|否|回调函数名| 30 | 31 | ## `joke`笑话接口 32 | - 请求方式:`GET/POST` 33 | - 趣图地址: https://bird.ioliu.cn/joke/ 34 | - 笑话地址: https://bird.ioliu.cn/joke/?type=text 35 | - 随机趣图: https://bird.ioliu.cn/joke/rand 36 | - 随机笑话: https://bird.ioliu.cn/joke/rand?type=text 37 | - 返回值类型:`json/jsonp` 38 | 39 | > ⚠️ 若`sort`存在,则`time`必须同时存在 40 | ⚠️ 随机获取链接不需要参数`page、pagesize、sort、time` 41 | 42 | |名称|类型|必须|说明| 43 | |----|----|----|----| 44 | |type|string|否|类型, (pic:趣图, text:笑话, 默认pic)| 45 | |page|int|否|当前页数,默认1| 46 | |pagesize|int|否|每次返回条数,默认1,最大20| 47 | |sort*|string|否|类型,desc:指定时间之前发布的,asc:指定时间之后发布的| 48 | |time*|string|否|时间戳(10位),如:1418816972| 49 | |callback|string|否|回调函数名| 50 | 51 | ## `mobile`手机号码接口 52 | - 请求方式:`GET/POST` 53 | - 请求地址:https://bird.ioliu.cn/mobile 54 | - 返回值类型:`json/jsonp/xml` 55 | 56 | |名称|类型|必须|说明| 57 | |----|----|----|----| 58 | |phone|string|是|需要查询的手机号码或手机号码前7位| 59 | |type|string|否|返回数据的格式,`xml`或`json`,默认`json`| 60 | |callback|string|否|回调函数名| 61 | 62 | ## `netease`网易云音乐接口 63 | - 请求方式:`GET` 64 | - 歌曲接口:https://bird.ioliu.cn/netease?id=222222 65 | - 歌单接口:https://bird.ioliu.cn/netease?playlist_id=222222 66 | - 返回值类型:`json/jsonp` 67 | 68 | |名称|类型|必须|说明| 69 | |----|----|----|----| 70 | |id/playlist_id|string|是|`id/playlist_id`:需要查询的歌曲或者歌单`id`| 71 | |callback|string|否|回调函数名| 72 | 73 | ## `weather`天气查询接口 74 | - 请求方式:`GET/POST` 75 | - 请求地址:https://bird.ioliu.cn/weather 76 | - 返回值类型:`json/jsonp/xml` 77 | 78 | |名称|类型|必须|说明| 79 | |----|----|----|----| 80 | |city|string|是|要查询的城市,如:温州、上海、北京| 81 | |type|string|否|返回数据的格式,`xml`或`json`,默认`json`| 82 | |callback|string|否|回调函数名| 83 | -------------------------------------------------------------------------------- /static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /static/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /static/images/aliyun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/aliyun.png -------------------------------------------------------------------------------- /static/images/banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/banner.jpg -------------------------------------------------------------------------------- /static/images/fav.svg: -------------------------------------------------------------------------------- 1 | image/svg+xml 2 | -------------------------------------------------------------------------------- /static/images/fav_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/fav_16x16.png -------------------------------------------------------------------------------- /static/images/fav_36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/fav_36x36.png -------------------------------------------------------------------------------- /static/images/fav_72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/fav_72x72.png -------------------------------------------------------------------------------- /static/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/favicon.ico -------------------------------------------------------------------------------- /static/images/favicon_36.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/favicon_36.ico -------------------------------------------------------------------------------- /static/images/favicon_72.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/favicon_72.ico -------------------------------------------------------------------------------- /static/images/qiniu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xCss/JsonBird/46cbb69b4b89cea17439a0925b0f3c12a1b22388/static/images/qiniu.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Welcome | JsonBird - 业界领先的远程数据接口代理服务 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 37 | 63 |
64 |
65 |

我们能做到:

66 |
    67 |
  1. 不支持跨域的远程数据接口支持跨域
  2. 68 |
  3. 不支持JSONP的远程数据接口支持JSONP(添加参数&callback=cb_name)。
  4. 69 |
  5. 提供专业的HTTPS解决方案,让数据传输更安全(同时解决远程数据接口不支持HTTPS的问题)。
  6. 70 |
  7. 多种数据请求方式与返回格式。
  8. 71 |
  9. 丰富的数据接口 ...
  10. 72 |
73 |
74 |
75 |
76 |
77 |

已经实现的数据接口:

78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 |
接口名称接口地址请求方式
远程代理https://bird.ioliu.cn/v1GET/POST
笑话https://bird.ioliu.cn/jokeGET/POST
IP 地址https://bird.ioliu.cn/ipGET/POST
天气查询https://bird.ioliu.cn/weatherGET/POST
号码归属地https://bird.ioliu.cn/mobileGET/POST
网易云音乐
(歌曲/歌单)
https://bird.ioliu.cn/neteaseGET/POST
120 |
121 |
122 |
123 | 138 | 154 | 155 | 156 | -------------------------------------------------------------------------------- /static/js/base64.js: -------------------------------------------------------------------------------- 1 | /* 2 | * $Id: base64.js,v 2.15 2014/04/05 12:58:57 dankogai Exp dankogai $ 3 | * 4 | * Licensed under the BSD 3-Clause License. 5 | * http://opensource.org/licenses/BSD-3-Clause 6 | * 7 | * References: 8 | * http://en.wikipedia.org/wiki/Base64 9 | */ 10 | 11 | (function(global) { 12 | 'use strict'; 13 | // existing version for noConflict() 14 | var _Base64 = global.Base64; 15 | var version = "2.1.9"; 16 | // if node.js, we use Buffer 17 | var buffer; 18 | if (typeof module !== 'undefined' && module.exports) { 19 | try { 20 | buffer = require('buffer').Buffer; 21 | } catch (err) {} 22 | } 23 | // constants 24 | var b64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; 25 | var b64tab = function(bin) { 26 | var t = {}; 27 | for (var i = 0, l = bin.length; i < l; i++) t[bin.charAt(i)] = i; 28 | return t; 29 | }(b64chars); 30 | var fromCharCode = String.fromCharCode; 31 | // encoder stuff 32 | var cb_utob = function(c) { 33 | if (c.length < 2) { 34 | var cc = c.charCodeAt(0); 35 | return cc < 0x80 ? c : 36 | cc < 0x800 ? (fromCharCode(0xc0 | (cc >>> 6)) + 37 | fromCharCode(0x80 | (cc & 0x3f))) : 38 | (fromCharCode(0xe0 | ((cc >>> 12) & 0x0f)) + 39 | fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + 40 | fromCharCode(0x80 | (cc & 0x3f))); 41 | } else { 42 | var cc = 0x10000 + 43 | (c.charCodeAt(0) - 0xD800) * 0x400 + 44 | (c.charCodeAt(1) - 0xDC00); 45 | return (fromCharCode(0xf0 | ((cc >>> 18) & 0x07)) + 46 | fromCharCode(0x80 | ((cc >>> 12) & 0x3f)) + 47 | fromCharCode(0x80 | ((cc >>> 6) & 0x3f)) + 48 | fromCharCode(0x80 | (cc & 0x3f))); 49 | } 50 | }; 51 | var re_utob = /[\uD800-\uDBFF][\uDC00-\uDFFFF]|[^\x00-\x7F]/g; 52 | var utob = function(u) { 53 | return u.replace(re_utob, cb_utob); 54 | }; 55 | var cb_encode = function(ccc) { 56 | var padlen = [0, 2, 1][ccc.length % 3], 57 | ord = ccc.charCodeAt(0) << 16 | 58 | ((ccc.length > 1 ? ccc.charCodeAt(1) : 0) << 8) | 59 | ((ccc.length > 2 ? ccc.charCodeAt(2) : 0)), 60 | chars = [ 61 | b64chars.charAt(ord >>> 18), 62 | b64chars.charAt((ord >>> 12) & 63), 63 | padlen >= 2 ? '=' : b64chars.charAt((ord >>> 6) & 63), 64 | padlen >= 1 ? '=' : b64chars.charAt(ord & 63) 65 | ]; 66 | return chars.join(''); 67 | }; 68 | var btoa = global.btoa ? function(b) { 69 | return global.btoa(b); 70 | } : function(b) { 71 | return b.replace(/[\s\S]{1,3}/g, cb_encode); 72 | }; 73 | var _encode = buffer ? function(u) { 74 | return (u.constructor === buffer.constructor ? u : new buffer(u)) 75 | .toString('base64') 76 | } : 77 | function(u) { return btoa(utob(u)) }; 78 | var encode = function(u, urisafe) { 79 | return !urisafe ? 80 | _encode(String(u)) : 81 | _encode(String(u)).replace(/[+\/]/g, function(m0) { 82 | return m0 == '+' ? '-' : '_'; 83 | }).replace(/=/g, ''); 84 | }; 85 | var encodeURI = function(u) { return encode(u, true) }; 86 | // decoder stuff 87 | var re_btou = new RegExp([ 88 | '[\xC0-\xDF][\x80-\xBF]', 89 | '[\xE0-\xEF][\x80-\xBF]{2}', 90 | '[\xF0-\xF7][\x80-\xBF]{3}' 91 | ].join('|'), 'g'); 92 | var cb_btou = function(cccc) { 93 | switch (cccc.length) { 94 | case 4: 95 | var cp = ((0x07 & cccc.charCodeAt(0)) << 18) | 96 | ((0x3f & cccc.charCodeAt(1)) << 12) | 97 | ((0x3f & cccc.charCodeAt(2)) << 6) | 98 | (0x3f & cccc.charCodeAt(3)), 99 | offset = cp - 0x10000; 100 | return (fromCharCode((offset >>> 10) + 0xD800) + 101 | fromCharCode((offset & 0x3FF) + 0xDC00)); 102 | case 3: 103 | return fromCharCode( 104 | ((0x0f & cccc.charCodeAt(0)) << 12) | 105 | ((0x3f & cccc.charCodeAt(1)) << 6) | 106 | (0x3f & cccc.charCodeAt(2)) 107 | ); 108 | default: 109 | return fromCharCode( 110 | ((0x1f & cccc.charCodeAt(0)) << 6) | 111 | (0x3f & cccc.charCodeAt(1)) 112 | ); 113 | } 114 | }; 115 | var btou = function(b) { 116 | return b.replace(re_btou, cb_btou); 117 | }; 118 | var cb_decode = function(cccc) { 119 | var len = cccc.length, 120 | padlen = len % 4, 121 | n = (len > 0 ? b64tab[cccc.charAt(0)] << 18 : 0) | 122 | (len > 1 ? b64tab[cccc.charAt(1)] << 12 : 0) | 123 | (len > 2 ? b64tab[cccc.charAt(2)] << 6 : 0) | 124 | (len > 3 ? b64tab[cccc.charAt(3)] : 0), 125 | chars = [ 126 | fromCharCode(n >>> 16), 127 | fromCharCode((n >>> 8) & 0xff), 128 | fromCharCode(n & 0xff) 129 | ]; 130 | chars.length -= [0, 0, 2, 1][padlen]; 131 | return chars.join(''); 132 | }; 133 | var atob = global.atob ? function(a) { 134 | return global.atob(a); 135 | } : function(a) { 136 | return a.replace(/[\s\S]{1,4}/g, cb_decode); 137 | }; 138 | var _decode = buffer ? function(a) { 139 | return (a.constructor === buffer.constructor ? 140 | a : new buffer(a, 'base64')).toString(); 141 | } : 142 | function(a) { return btou(atob(a)) }; 143 | var decode = function(a) { 144 | return _decode( 145 | String(a).replace(/[-_]/g, function(m0) { return m0 == '-' ? '+' : '/' }) 146 | .replace(/[^A-Za-z0-9\+\/]/g, '') 147 | ); 148 | }; 149 | var noConflict = function() { 150 | var Base64 = global.Base64; 151 | global.Base64 = _Base64; 152 | return Base64; 153 | }; 154 | // export Base64 155 | global.Base64 = { 156 | VERSION: version, 157 | atob: atob, 158 | btoa: btoa, 159 | fromBase64: decode, 160 | toBase64: encode, 161 | utob: utob, 162 | encode: encode, 163 | encodeURI: encodeURI, 164 | btou: btou, 165 | decode: decode, 166 | noConflict: noConflict 167 | }; 168 | // if ES5 is available, make Base64.extendString() available 169 | if (typeof Object.defineProperty === 'function') { 170 | var noEnum = function(v) { 171 | return { value: v, enumerable: false, writable: true, configurable: true }; 172 | }; 173 | global.Base64.extendString = function() { 174 | Object.defineProperty( 175 | String.prototype, 'fromBase64', noEnum(function() { 176 | return decode(this) 177 | })); 178 | Object.defineProperty( 179 | String.prototype, 'toBase64', noEnum(function(urisafe) { 180 | return encode(this, urisafe) 181 | })); 182 | Object.defineProperty( 183 | String.prototype, 'toBase64URI', noEnum(function() { 184 | return encode(this, true) 185 | })); 186 | }; 187 | } 188 | // that's it! 189 | if (global['Meteor']) { 190 | Base64 = global.Base64; // for normal export in Meteor.js 191 | } 192 | })(this); -------------------------------------------------------------------------------- /static/js/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | Projection by TEMPLATED 3 | templated.co @templatedco 4 | Released for free under the Creative Commons Attribution 3.0 license (templated.co/license) 5 | */ 6 | 7 | (function($) { 8 | 9 | // Breakpoints. 10 | skel.breakpoints({ 11 | xlarge: '(max-width: 1680px)', 12 | large: '(max-width: 1280px)', 13 | medium: '(max-width: 980px)', 14 | small: '(max-width: 736px)', 15 | xsmall: '(max-width: 480px)' 16 | }); 17 | 18 | $(function() { 19 | 20 | var $window = $(window), 21 | $body = $('body'); 22 | 23 | // Disable animations/transitions until the page has loaded. 24 | $body.addClass('is-loading'); 25 | 26 | $window.on('load', function() { 27 | window.setTimeout(function() { 28 | $body.removeClass('is-loading'); 29 | }, 100); 30 | }); 31 | 32 | // Prioritize "important" elements on medium. 33 | skel.on('+medium -medium', function() { 34 | $.prioritize( 35 | '.important\\28 medium\\29', 36 | skel.breakpoint('medium').active 37 | ); 38 | }); 39 | 40 | // Off-Canvas Navigation. 41 | 42 | // Navigation Panel. 43 | $( 44 | '' 48 | ) 49 | .appendTo($body) 50 | .panel({ 51 | delay: 500, 52 | hideOnClick: true, 53 | hideOnSwipe: true, 54 | resetScroll: true, 55 | resetForms: true, 56 | side: 'left' 57 | }); 58 | 59 | // Fix: Remove transitions on WP<10 (poor/buggy performance). 60 | if (skel.vars.os == 'wp' && skel.vars.osVersion < 10) 61 | $('#navPanel') 62 | .css('transition', 'none'); 63 | 64 | }); 65 | 66 | })(jQuery); 67 | -------------------------------------------------------------------------------- /static/js/marked.js: -------------------------------------------------------------------------------- 1 | /** 2 | * marked - a markdown parser 3 | * Copyright (c) 2011-2014, Christopher Jeffrey. (MIT Licensed) 4 | * https://github.com/chjj/marked 5 | */ 6 | 7 | ; 8 | (function() { 9 | 10 | /** 11 | * Block-Level Grammar 12 | */ 13 | 14 | var block = { 15 | newline: /^\n+/, 16 | code: /^( {4}[^\n]+\n*)+/, 17 | fences: noop, 18 | hr: /^( *[-*_]){3,} *(?:\n+|$)/, 19 | heading: /^ *(#{1,6}) *([^\n]+?) *#* *(?:\n+|$)/, 20 | nptable: noop, 21 | lheading: /^([^\n]+)\n *(=|-){2,} *(?:\n+|$)/, 22 | blockquote: /^( *>[^\n]+(\n(?!def)[^\n]+)*\n*)+/, 23 | list: /^( *)(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?!\1bull )\n*|\s*$)/, 24 | html: /^ *(?:comment *(?:\n|\s*$)|closed *(?:\n{2,}|\s*$)|closing *(?:\n{2,}|\s*$))/, 25 | def: /^ *\[([^\]]+)\]: *]+)>?(?: +["(]([^\n]+)[")])? *(?:\n+|$)/, 26 | table: noop, 27 | paragraph: /^((?:[^\n]+\n?(?!hr|heading|lheading|blockquote|tag|def))+)\n*/, 28 | text: /^[^\n]+/ 29 | }; 30 | 31 | block.bullet = /(?:[*+-]|\d+\.)/; 32 | block.item = /^( *)(bull) [^\n]*(?:\n(?!\1bull )[^\n]*)*/; 33 | block.item = replace(block.item, 'gm') 34 | (/bull/g, block.bullet) 35 | (); 36 | 37 | block.list = replace(block.list) 38 | (/bull/g, block.bullet) 39 | ('hr', '\\n+(?=\\1?(?:[-*_] *){3,}(?:\\n+|$))') 40 | ('def', '\\n+(?=' + block.def.source + ')') 41 | (); 42 | 43 | block.blockquote = replace(block.blockquote) 44 | ('def', block.def) 45 | (); 46 | 47 | block._tag = '(?!(?:' + 48 | 'a|em|strong|small|s|cite|q|dfn|abbr|data|time|code' + 49 | '|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo' + 50 | '|span|br|wbr|ins|del|img)\\b)\\w+(?!:/|[^\\w\\s@]*@)\\b'; 51 | 52 | block.html = replace(block.html) 53 | ('comment', //) 54 | ('closed', /<(tag)[\s\S]+?<\/\1>/) 55 | ('closing', /])*?>/) 56 | (/tag/g, block._tag) 57 | (); 58 | 59 | block.paragraph = replace(block.paragraph) 60 | ('hr', block.hr) 61 | ('heading', block.heading) 62 | ('lheading', block.lheading) 63 | ('blockquote', block.blockquote) 64 | ('tag', '<' + block._tag) 65 | ('def', block.def) 66 | (); 67 | 68 | /** 69 | * Normal Block Grammar 70 | */ 71 | 72 | block.normal = merge({}, block); 73 | 74 | /** 75 | * GFM Block Grammar 76 | */ 77 | 78 | block.gfm = merge({}, block.normal, { 79 | fences: /^ *(`{3,}|~{3,})[ \.]*(\S+)? *\n([\s\S]*?)\s*\1 *(?:\n+|$)/, 80 | paragraph: /^/, 81 | heading: /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/ 82 | }); 83 | 84 | block.gfm.paragraph = replace(block.paragraph) 85 | ('(?!', '(?!' + 86 | block.gfm.fences.source.replace('\\1', '\\2') + '|' + 87 | block.list.source.replace('\\1', '\\3') + '|') 88 | (); 89 | 90 | /** 91 | * GFM + Tables Block Grammar 92 | */ 93 | 94 | block.tables = merge({}, block.gfm, { 95 | nptable: /^ *(\S.*\|.*)\n *([-:]+ *\|[-| :]*)\n((?:.*\|.*(?:\n|$))*)\n*/, 96 | table: /^ *\|(.+)\n *\|( *[-:]+[-| :]*)\n((?: *\|.*(?:\n|$))*)\n*/ 97 | }); 98 | 99 | /** 100 | * Block Lexer 101 | */ 102 | 103 | function Lexer(options) { 104 | this.tokens = []; 105 | this.tokens.links = {}; 106 | this.options = options || marked.defaults; 107 | this.rules = block.normal; 108 | 109 | if (this.options.gfm) { 110 | if (this.options.tables) { 111 | this.rules = block.tables; 112 | } else { 113 | this.rules = block.gfm; 114 | } 115 | } 116 | } 117 | 118 | /** 119 | * Expose Block Rules 120 | */ 121 | 122 | Lexer.rules = block; 123 | 124 | /** 125 | * Static Lex Method 126 | */ 127 | 128 | Lexer.lex = function(src, options) { 129 | var lexer = new Lexer(options); 130 | return lexer.lex(src); 131 | }; 132 | 133 | /** 134 | * Preprocessing 135 | */ 136 | 137 | Lexer.prototype.lex = function(src) { 138 | src = src 139 | .replace(/\r\n|\r/g, '\n') 140 | .replace(/\t/g, ' ') 141 | .replace(/\u00a0/g, ' ') 142 | .replace(/\u2424/g, '\n'); 143 | 144 | return this.token(src, true); 145 | }; 146 | 147 | /** 148 | * Lexing 149 | */ 150 | 151 | Lexer.prototype.token = function(src, top, bq) { 152 | var src = src.replace(/^ +$/gm, ''), 153 | next, loose, cap, bull, b, item, space, i, l; 154 | 155 | while (src) { 156 | // newline 157 | if (cap = this.rules.newline.exec(src)) { 158 | src = src.substring(cap[0].length); 159 | if (cap[0].length > 1) { 160 | this.tokens.push({ 161 | type: 'space' 162 | }); 163 | } 164 | } 165 | 166 | // code 167 | if (cap = this.rules.code.exec(src)) { 168 | src = src.substring(cap[0].length); 169 | cap = cap[0].replace(/^ {4}/gm, ''); 170 | this.tokens.push({ 171 | type: 'code', 172 | text: !this.options.pedantic ? 173 | cap.replace(/\n+$/, '') : 174 | cap 175 | }); 176 | continue; 177 | } 178 | 179 | // fences (gfm) 180 | if (cap = this.rules.fences.exec(src)) { 181 | src = src.substring(cap[0].length); 182 | this.tokens.push({ 183 | type: 'code', 184 | lang: cap[2], 185 | text: cap[3] || '' 186 | }); 187 | continue; 188 | } 189 | 190 | // heading 191 | if (cap = this.rules.heading.exec(src)) { 192 | src = src.substring(cap[0].length); 193 | this.tokens.push({ 194 | type: 'heading', 195 | depth: cap[1].length, 196 | text: cap[2] 197 | }); 198 | continue; 199 | } 200 | 201 | // table no leading pipe (gfm) 202 | if (top && (cap = this.rules.nptable.exec(src))) { 203 | src = src.substring(cap[0].length); 204 | 205 | item = { 206 | type: 'table', 207 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 208 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 209 | cells: cap[3].replace(/\n$/, '').split('\n') 210 | }; 211 | 212 | for (i = 0; i < item.align.length; i++) { 213 | if (/^ *-+: *$/.test(item.align[i])) { 214 | item.align[i] = 'right'; 215 | } else if (/^ *:-+: *$/.test(item.align[i])) { 216 | item.align[i] = 'center'; 217 | } else if (/^ *:-+ *$/.test(item.align[i])) { 218 | item.align[i] = 'left'; 219 | } else { 220 | item.align[i] = null; 221 | } 222 | } 223 | 224 | for (i = 0; i < item.cells.length; i++) { 225 | item.cells[i] = item.cells[i].split(/ *\| */); 226 | } 227 | 228 | this.tokens.push(item); 229 | 230 | continue; 231 | } 232 | 233 | // lheading 234 | if (cap = this.rules.lheading.exec(src)) { 235 | src = src.substring(cap[0].length); 236 | this.tokens.push({ 237 | type: 'heading', 238 | depth: cap[2] === '=' ? 1 : 2, 239 | text: cap[1] 240 | }); 241 | continue; 242 | } 243 | 244 | // hr 245 | if (cap = this.rules.hr.exec(src)) { 246 | src = src.substring(cap[0].length); 247 | this.tokens.push({ 248 | type: 'hr' 249 | }); 250 | continue; 251 | } 252 | 253 | // blockquote 254 | if (cap = this.rules.blockquote.exec(src)) { 255 | src = src.substring(cap[0].length); 256 | 257 | this.tokens.push({ 258 | type: 'blockquote_start' 259 | }); 260 | 261 | cap = cap[0].replace(/^ *> ?/gm, ''); 262 | 263 | // Pass `top` to keep the current 264 | // "toplevel" state. This is exactly 265 | // how markdown.pl works. 266 | this.token(cap, top, true); 267 | 268 | this.tokens.push({ 269 | type: 'blockquote_end' 270 | }); 271 | 272 | continue; 273 | } 274 | 275 | // list 276 | if (cap = this.rules.list.exec(src)) { 277 | src = src.substring(cap[0].length); 278 | bull = cap[2]; 279 | 280 | this.tokens.push({ 281 | type: 'list_start', 282 | ordered: bull.length > 1 283 | }); 284 | 285 | // Get each top-level item. 286 | cap = cap[0].match(this.rules.item); 287 | 288 | next = false; 289 | l = cap.length; 290 | i = 0; 291 | 292 | for (; i < l; i++) { 293 | item = cap[i]; 294 | 295 | // Remove the list item's bullet 296 | // so it is seen as the next token. 297 | space = item.length; 298 | item = item.replace(/^ *([*+-]|\d+\.) +/, ''); 299 | 300 | // Outdent whatever the 301 | // list item contains. Hacky. 302 | if (~item.indexOf('\n ')) { 303 | space -= item.length; 304 | item = !this.options.pedantic ? 305 | item.replace(new RegExp('^ {1,' + space + '}', 'gm'), '') : 306 | item.replace(/^ {1,4}/gm, ''); 307 | } 308 | 309 | // Determine whether the next list item belongs here. 310 | // Backpedal if it does not belong in this list. 311 | if (this.options.smartLists && i !== l - 1) { 312 | b = block.bullet.exec(cap[i + 1])[0]; 313 | if (bull !== b && !(bull.length > 1 && b.length > 1)) { 314 | src = cap.slice(i + 1).join('\n') + src; 315 | i = l - 1; 316 | } 317 | } 318 | 319 | // Determine whether item is loose or not. 320 | // Use: /(^|\n)(?! )[^\n]+\n\n(?!\s*$)/ 321 | // for discount behavior. 322 | loose = next || /\n\n(?!\s*$)/.test(item); 323 | if (i !== l - 1) { 324 | next = item.charAt(item.length - 1) === '\n'; 325 | if (!loose) loose = next; 326 | } 327 | 328 | this.tokens.push({ 329 | type: loose ? 330 | 'loose_item_start' : 331 | 'list_item_start' 332 | }); 333 | 334 | // Recurse. 335 | this.token(item, false, bq); 336 | 337 | this.tokens.push({ 338 | type: 'list_item_end' 339 | }); 340 | } 341 | 342 | this.tokens.push({ 343 | type: 'list_end' 344 | }); 345 | 346 | continue; 347 | } 348 | 349 | // html 350 | if (cap = this.rules.html.exec(src)) { 351 | src = src.substring(cap[0].length); 352 | this.tokens.push({ 353 | type: this.options.sanitize ? 354 | 'paragraph' : 355 | 'html', 356 | pre: !this.options.sanitizer && 357 | (cap[1] === 'pre' || cap[1] === 'script' || cap[1] === 'style'), 358 | text: cap[0] 359 | }); 360 | continue; 361 | } 362 | 363 | // def 364 | if ((!bq && top) && (cap = this.rules.def.exec(src))) { 365 | src = src.substring(cap[0].length); 366 | this.tokens.links[cap[1].toLowerCase()] = { 367 | href: cap[2], 368 | title: cap[3] 369 | }; 370 | continue; 371 | } 372 | 373 | // table (gfm) 374 | if (top && (cap = this.rules.table.exec(src))) { 375 | src = src.substring(cap[0].length); 376 | 377 | item = { 378 | type: 'table', 379 | header: cap[1].replace(/^ *| *\| *$/g, '').split(/ *\| */), 380 | align: cap[2].replace(/^ *|\| *$/g, '').split(/ *\| */), 381 | cells: cap[3].replace(/(?: *\| *)?\n$/, '').split('\n') 382 | }; 383 | 384 | for (i = 0; i < item.align.length; i++) { 385 | if (/^ *-+: *$/.test(item.align[i])) { 386 | item.align[i] = 'right'; 387 | } else if (/^ *:-+: *$/.test(item.align[i])) { 388 | item.align[i] = 'center'; 389 | } else if (/^ *:-+ *$/.test(item.align[i])) { 390 | item.align[i] = 'left'; 391 | } else { 392 | item.align[i] = null; 393 | } 394 | } 395 | 396 | for (i = 0; i < item.cells.length; i++) { 397 | item.cells[i] = item.cells[i] 398 | .replace(/^ *\| *| *\| *$/g, '') 399 | .split(/ *\| */); 400 | } 401 | 402 | this.tokens.push(item); 403 | 404 | continue; 405 | } 406 | 407 | // top-level paragraph 408 | if (top && (cap = this.rules.paragraph.exec(src))) { 409 | src = src.substring(cap[0].length); 410 | this.tokens.push({ 411 | type: 'paragraph', 412 | text: cap[1].charAt(cap[1].length - 1) === '\n' ? 413 | cap[1].slice(0, -1) : 414 | cap[1] 415 | }); 416 | continue; 417 | } 418 | 419 | // text 420 | if (cap = this.rules.text.exec(src)) { 421 | // Top-level should never reach here. 422 | src = src.substring(cap[0].length); 423 | this.tokens.push({ 424 | type: 'text', 425 | text: cap[0] 426 | }); 427 | continue; 428 | } 429 | 430 | if (src) { 431 | throw new 432 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 433 | } 434 | } 435 | 436 | return this.tokens; 437 | }; 438 | 439 | /** 440 | * Inline-Level Grammar 441 | */ 442 | 443 | var inline = { 444 | escape: /^\\([\\`*{}\[\]()#+\-.!_>])/, 445 | autolink: /^<([^ >]+(@|:\/)[^ >]+)>/, 446 | url: noop, 447 | tag: /^|^<\/?\w+(?:"[^"]*"|'[^']*'|[^'">])*?>/, 448 | link: /^!?\[(inside)\]\(href\)/, 449 | reflink: /^!?\[(inside)\]\s*\[([^\]]*)\]/, 450 | nolink: /^!?\[((?:\[[^\]]*\]|[^\[\]])*)\]/, 451 | strong: /^__([\s\S]+?)__(?!_)|^\*\*([\s\S]+?)\*\*(?!\*)/, 452 | em: /^\b_((?:[^_]|__)+?)_\b|^\*((?:\*\*|[\s\S])+?)\*(?!\*)/, 453 | code: /^(`+)\s*([\s\S]*?[^`])\s*\1(?!`)/, 454 | br: /^ {2,}\n(?!\s*$)/, 455 | del: noop, 456 | text: /^[\s\S]+?(?=[\\?(?:\s+['"]([\s\S]*?)['"])?\s*/; 461 | 462 | inline.link = replace(inline.link) 463 | ('inside', inline._inside) 464 | ('href', inline._href) 465 | (); 466 | 467 | inline.reflink = replace(inline.reflink) 468 | ('inside', inline._inside) 469 | (); 470 | 471 | /** 472 | * Normal Inline Grammar 473 | */ 474 | 475 | inline.normal = merge({}, inline); 476 | 477 | /** 478 | * Pedantic Inline Grammar 479 | */ 480 | 481 | inline.pedantic = merge({}, inline.normal, { 482 | strong: /^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/, 483 | em: /^_(?=\S)([\s\S]*?\S)_(?!_)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/ 484 | }); 485 | 486 | /** 487 | * GFM Inline Grammar 488 | */ 489 | 490 | inline.gfm = merge({}, inline.normal, { 491 | escape: replace(inline.escape)('])', '~|])')(), 492 | url: /^(https?:\/\/[^\s<]+[^<.,:;"')\]\s])/, 493 | del: /^~~(?=\S)([\s\S]*?\S)~~/, 494 | text: replace(inline.text) 495 | (']|', '~]|') 496 | ('|', '|https?://|') 497 | () 498 | }); 499 | 500 | /** 501 | * GFM + Line Breaks Inline Grammar 502 | */ 503 | 504 | inline.breaks = merge({}, inline.gfm, { 505 | br: replace(inline.br)('{2,}', '*')(), 506 | text: replace(inline.gfm.text)('{2,}', '*')() 507 | }); 508 | 509 | /** 510 | * Inline Lexer & Compiler 511 | */ 512 | 513 | function InlineLexer(links, options) { 514 | this.options = options || marked.defaults; 515 | this.links = links; 516 | this.rules = inline.normal; 517 | this.renderer = this.options.renderer || new Renderer; 518 | this.renderer.options = this.options; 519 | 520 | if (!this.links) { 521 | throw new 522 | Error('Tokens array requires a `links` property.'); 523 | } 524 | 525 | if (this.options.gfm) { 526 | if (this.options.breaks) { 527 | this.rules = inline.breaks; 528 | } else { 529 | this.rules = inline.gfm; 530 | } 531 | } else if (this.options.pedantic) { 532 | this.rules = inline.pedantic; 533 | } 534 | } 535 | 536 | /** 537 | * Expose Inline Rules 538 | */ 539 | 540 | InlineLexer.rules = inline; 541 | 542 | /** 543 | * Static Lexing/Compiling Method 544 | */ 545 | 546 | InlineLexer.output = function(src, links, options) { 547 | var inline = new InlineLexer(links, options); 548 | return inline.output(src); 549 | }; 550 | 551 | /** 552 | * Lexing/Compiling 553 | */ 554 | 555 | InlineLexer.prototype.output = function(src) { 556 | var out = '', 557 | link, text, href, cap; 558 | 559 | while (src) { 560 | // escape 561 | if (cap = this.rules.escape.exec(src)) { 562 | src = src.substring(cap[0].length); 563 | out += cap[1]; 564 | continue; 565 | } 566 | 567 | // autolink 568 | if (cap = this.rules.autolink.exec(src)) { 569 | src = src.substring(cap[0].length); 570 | if (cap[2] === '@') { 571 | text = cap[1].charAt(6) === ':' ? 572 | this.mangle(cap[1].substring(7)) : 573 | this.mangle(cap[1]); 574 | href = this.mangle('mailto:') + text; 575 | } else { 576 | text = escape(cap[1]); 577 | href = text; 578 | } 579 | out += this.renderer.link(href, null, text); 580 | continue; 581 | } 582 | 583 | // url (gfm) 584 | if (!this.inLink && (cap = this.rules.url.exec(src))) { 585 | src = src.substring(cap[0].length); 586 | text = escape(cap[1]); 587 | href = text; 588 | out += this.renderer.link(href, null, text); 589 | continue; 590 | } 591 | 592 | // tag 593 | if (cap = this.rules.tag.exec(src)) { 594 | if (!this.inLink && /^/i.test(cap[0])) { 597 | this.inLink = false; 598 | } 599 | src = src.substring(cap[0].length); 600 | out += this.options.sanitize ? 601 | this.options.sanitizer ? 602 | this.options.sanitizer(cap[0]) : 603 | escape(cap[0]) : 604 | cap[0] 605 | continue; 606 | } 607 | 608 | // link 609 | if (cap = this.rules.link.exec(src)) { 610 | src = src.substring(cap[0].length); 611 | this.inLink = true; 612 | out += this.outputLink(cap, { 613 | href: cap[2], 614 | title: cap[3] 615 | }); 616 | this.inLink = false; 617 | continue; 618 | } 619 | 620 | // reflink, nolink 621 | if ((cap = this.rules.reflink.exec(src)) || 622 | (cap = this.rules.nolink.exec(src))) { 623 | src = src.substring(cap[0].length); 624 | link = (cap[2] || cap[1]).replace(/\s+/g, ' '); 625 | link = this.links[link.toLowerCase()]; 626 | if (!link || !link.href) { 627 | out += cap[0].charAt(0); 628 | src = cap[0].substring(1) + src; 629 | continue; 630 | } 631 | this.inLink = true; 632 | out += this.outputLink(cap, link); 633 | this.inLink = false; 634 | continue; 635 | } 636 | 637 | // strong 638 | if (cap = this.rules.strong.exec(src)) { 639 | src = src.substring(cap[0].length); 640 | out += this.renderer.strong(this.output(cap[2] || cap[1])); 641 | continue; 642 | } 643 | 644 | // em 645 | if (cap = this.rules.em.exec(src)) { 646 | src = src.substring(cap[0].length); 647 | out += this.renderer.em(this.output(cap[2] || cap[1])); 648 | continue; 649 | } 650 | 651 | // code 652 | if (cap = this.rules.code.exec(src)) { 653 | src = src.substring(cap[0].length); 654 | out += this.renderer.codespan(escape(cap[2], true)); 655 | continue; 656 | } 657 | 658 | // br 659 | if (cap = this.rules.br.exec(src)) { 660 | src = src.substring(cap[0].length); 661 | out += this.renderer.br(); 662 | continue; 663 | } 664 | 665 | // del (gfm) 666 | if (cap = this.rules.del.exec(src)) { 667 | src = src.substring(cap[0].length); 668 | out += this.renderer.del(this.output(cap[1])); 669 | continue; 670 | } 671 | 672 | // text 673 | if (cap = this.rules.text.exec(src)) { 674 | src = src.substring(cap[0].length); 675 | out += this.renderer.text(escape(this.smartypants(cap[0]))); 676 | continue; 677 | } 678 | 679 | if (src) { 680 | throw new 681 | Error('Infinite loop on byte: ' + src.charCodeAt(0)); 682 | } 683 | } 684 | 685 | return out; 686 | }; 687 | 688 | /** 689 | * Compile Link 690 | */ 691 | 692 | InlineLexer.prototype.outputLink = function(cap, link) { 693 | var href = escape(link.href), 694 | title = link.title ? escape(link.title) : null; 695 | 696 | return cap[0].charAt(0) !== '!' ? 697 | this.renderer.link(href, title, this.output(cap[1])) : 698 | this.renderer.image(href, title, escape(cap[1])); 699 | }; 700 | 701 | /** 702 | * Smartypants Transformations 703 | */ 704 | 705 | InlineLexer.prototype.smartypants = function(text) { 706 | if (!this.options.smartypants) return text; 707 | return text 708 | // em-dashes 709 | .replace(/---/g, '\u2014') 710 | // en-dashes 711 | .replace(/--/g, '\u2013') 712 | // opening singles 713 | .replace(/(^|[-\u2014/(\[{"\s])'/g, '$1\u2018') 714 | // closing singles & apostrophes 715 | .replace(/'/g, '\u2019') 716 | // opening doubles 717 | .replace(/(^|[-\u2014/(\[{\u2018\s])"/g, '$1\u201c') 718 | // closing doubles 719 | .replace(/"/g, '\u201d') 720 | // ellipses 721 | .replace(/\.{3}/g, '\u2026'); 722 | }; 723 | 724 | /** 725 | * Mangle Links 726 | */ 727 | 728 | InlineLexer.prototype.mangle = function(text) { 729 | if (!this.options.mangle) return text; 730 | var out = '', 731 | l = text.length, 732 | i = 0, 733 | ch; 734 | 735 | for (; i < l; i++) { 736 | ch = text.charCodeAt(i); 737 | if (Math.random() > 0.5) { 738 | ch = 'x' + ch.toString(16); 739 | } 740 | out += '&#' + ch + ';'; 741 | } 742 | 743 | return out; 744 | }; 745 | 746 | /** 747 | * Renderer 748 | */ 749 | 750 | function Renderer(options) { 751 | this.options = options || {}; 752 | } 753 | 754 | Renderer.prototype.code = function(code, lang, escaped) { 755 | if (this.options.highlight) { 756 | var out = this.options.highlight(code, lang); 757 | if (out != null && out !== code) { 758 | escaped = true; 759 | code = out; 760 | } 761 | } 762 | 763 | if (!lang) { 764 | return '
' +
 765 |                 (escaped ? code : escape(code, true)) +
 766 |                 '\n
'; 767 | } 768 | 769 | return '
' +
 773 |             (escaped ? code : escape(code, true)) +
 774 |             '\n
\n'; 775 | }; 776 | 777 | Renderer.prototype.blockquote = function(quote) { 778 | return '
\n' + quote + '
\n'; 779 | }; 780 | 781 | Renderer.prototype.html = function(html) { 782 | return html; 783 | }; 784 | 785 | Renderer.prototype.heading = function(text, level, raw) { 786 | return '' + 792 | text + 793 | '\n'; 796 | }; 797 | 798 | Renderer.prototype.hr = function() { 799 | return this.options.xhtml ? '
\n' : '
\n'; 800 | }; 801 | 802 | Renderer.prototype.list = function(body, ordered) { 803 | var type = ordered ? 'ol' : 'ul'; 804 | return '<' + type + '>\n' + body + '\n'; 805 | }; 806 | 807 | Renderer.prototype.listitem = function(text) { 808 | return '
  • ' + text + '
  • \n'; 809 | }; 810 | 811 | Renderer.prototype.paragraph = function(text) { 812 | return '

    ' + text + '

    \n'; 813 | }; 814 | 815 | Renderer.prototype.table = function(header, body) { 816 | return '\n' + 817 | '\n' + 818 | header + 819 | '\n' + 820 | '\n' + 821 | body + 822 | '\n' + 823 | '
    \n'; 824 | }; 825 | 826 | Renderer.prototype.tablerow = function(content) { 827 | return '\n' + content + '\n'; 828 | }; 829 | 830 | Renderer.prototype.tablecell = function(content, flags) { 831 | var type = flags.header ? 'th' : 'td'; 832 | var tag = flags.align ? 833 | '<' + type + ' style="text-align:' + flags.align + '">' : 834 | '<' + type + '>'; 835 | return tag + content + '\n'; 836 | }; 837 | 838 | // span level renderer 839 | Renderer.prototype.strong = function(text) { 840 | return '' + text + ''; 841 | }; 842 | 843 | Renderer.prototype.em = function(text) { 844 | return '' + text + ''; 845 | }; 846 | 847 | Renderer.prototype.codespan = function(text) { 848 | return '' + text + ''; 849 | }; 850 | 851 | Renderer.prototype.br = function() { 852 | return this.options.xhtml ? '
    ' : '
    '; 853 | }; 854 | 855 | Renderer.prototype.del = function(text) { 856 | return '' + text + ''; 857 | }; 858 | 859 | Renderer.prototype.link = function(href, title, text) { 860 | if (this.options.sanitize) { 861 | try { 862 | var prot = decodeURIComponent(unescape(href)) 863 | .replace(/[^\w:]/g, '') 864 | .toLowerCase(); 865 | } catch (e) { 866 | return ''; 867 | } 868 | if (prot.indexOf('javascript:') === 0 || prot.indexOf('vbscript:') === 0) { 869 | return ''; 870 | } 871 | } 872 | var out = '
    '; 877 | return out; 878 | }; 879 | 880 | Renderer.prototype.image = function(href, title, text) { 881 | var out = '' + text + '' : '>'; 886 | return out; 887 | }; 888 | 889 | Renderer.prototype.text = function(text) { 890 | return text; 891 | }; 892 | 893 | /** 894 | * Parsing & Compiling 895 | */ 896 | 897 | function Parser(options) { 898 | this.tokens = []; 899 | this.token = null; 900 | this.options = options || marked.defaults; 901 | this.options.renderer = this.options.renderer || new Renderer; 902 | this.renderer = this.options.renderer; 903 | this.renderer.options = this.options; 904 | } 905 | 906 | /** 907 | * Static Parse Method 908 | */ 909 | 910 | Parser.parse = function(src, options, renderer) { 911 | var parser = new Parser(options, renderer); 912 | return parser.parse(src); 913 | }; 914 | 915 | /** 916 | * Parse Loop 917 | */ 918 | 919 | Parser.prototype.parse = function(src) { 920 | this.inline = new InlineLexer(src.links, this.options, this.renderer); 921 | this.tokens = src.reverse(); 922 | 923 | var out = ''; 924 | while (this.next()) { 925 | out += this.tok(); 926 | } 927 | 928 | return out; 929 | }; 930 | 931 | /** 932 | * Next Token 933 | */ 934 | 935 | Parser.prototype.next = function() { 936 | return this.token = this.tokens.pop(); 937 | }; 938 | 939 | /** 940 | * Preview Next Token 941 | */ 942 | 943 | Parser.prototype.peek = function() { 944 | return this.tokens[this.tokens.length - 1] || 0; 945 | }; 946 | 947 | /** 948 | * Parse Text Tokens 949 | */ 950 | 951 | Parser.prototype.parseText = function() { 952 | var body = this.token.text; 953 | 954 | while (this.peek().type === 'text') { 955 | body += '\n' + this.next().text; 956 | } 957 | 958 | return this.inline.output(body); 959 | }; 960 | 961 | /** 962 | * Parse Current Token 963 | */ 964 | 965 | Parser.prototype.tok = function() { 966 | switch (this.token.type) { 967 | case 'space': 968 | { 969 | return ''; 970 | } 971 | case 'hr': 972 | { 973 | return this.renderer.hr(); 974 | } 975 | case 'heading': 976 | { 977 | return this.renderer.heading( 978 | this.inline.output(this.token.text), 979 | this.token.depth, 980 | this.token.text); 981 | } 982 | case 'code': 983 | { 984 | return this.renderer.code(this.token.text, 985 | this.token.lang, 986 | this.token.escaped); 987 | } 988 | case 'table': 989 | { 990 | var header = '', 991 | body = '', 992 | i, row, cell, flags, j; 993 | 994 | // header 995 | cell = ''; 996 | for (i = 0; i < this.token.header.length; i++) { 997 | flags = { header: true, align: this.token.align[i] }; 998 | cell += this.renderer.tablecell( 999 | this.inline.output(this.token.header[i]), { header: true, align: this.token.align[i] } 1000 | ); 1001 | } 1002 | header += this.renderer.tablerow(cell); 1003 | 1004 | for (i = 0; i < this.token.cells.length; i++) { 1005 | row = this.token.cells[i]; 1006 | 1007 | cell = ''; 1008 | for (j = 0; j < row.length; j++) { 1009 | cell += this.renderer.tablecell( 1010 | this.inline.output(row[j]), { header: false, align: this.token.align[j] } 1011 | ); 1012 | } 1013 | 1014 | body += this.renderer.tablerow(cell); 1015 | } 1016 | return this.renderer.table(header, body); 1017 | } 1018 | case 'blockquote_start': 1019 | { 1020 | var body = ''; 1021 | 1022 | while (this.next().type !== 'blockquote_end') { 1023 | body += this.tok(); 1024 | } 1025 | 1026 | return this.renderer.blockquote(body); 1027 | } 1028 | case 'list_start': 1029 | { 1030 | var body = '', 1031 | ordered = this.token.ordered; 1032 | 1033 | while (this.next().type !== 'list_end') { 1034 | body += this.tok(); 1035 | } 1036 | 1037 | return this.renderer.list(body, ordered); 1038 | } 1039 | case 'list_item_start': 1040 | { 1041 | var body = ''; 1042 | 1043 | while (this.next().type !== 'list_item_end') { 1044 | body += this.token.type === 'text' ? 1045 | this.parseText() : 1046 | this.tok(); 1047 | } 1048 | 1049 | return this.renderer.listitem(body); 1050 | } 1051 | case 'loose_item_start': 1052 | { 1053 | var body = ''; 1054 | 1055 | while (this.next().type !== 'list_item_end') { 1056 | body += this.tok(); 1057 | } 1058 | 1059 | return this.renderer.listitem(body); 1060 | } 1061 | case 'html': 1062 | { 1063 | var html = !this.token.pre && !this.options.pedantic ? 1064 | this.inline.output(this.token.text) : 1065 | this.token.text; 1066 | return this.renderer.html(html); 1067 | } 1068 | case 'paragraph': 1069 | { 1070 | return this.renderer.paragraph(this.inline.output(this.token.text)); 1071 | } 1072 | case 'text': 1073 | { 1074 | return this.renderer.paragraph(this.parseText()); 1075 | } 1076 | } 1077 | }; 1078 | 1079 | /** 1080 | * Helpers 1081 | */ 1082 | 1083 | function escape(html, encode) { 1084 | return html 1085 | .replace(!encode ? /&(?!#?\w+;)/g : /&/g, '&') 1086 | .replace(//g, '>') 1088 | .replace(/"/g, '"') 1089 | .replace(/'/g, '''); 1090 | } 1091 | 1092 | function unescape(html) { 1093 | // explicitly match decimal, hex, and named HTML entities 1094 | return html.replace(/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/g, function(_, n) { 1095 | n = n.toLowerCase(); 1096 | if (n === 'colon') return ':'; 1097 | if (n.charAt(0) === '#') { 1098 | return n.charAt(1) === 'x' ? 1099 | String.fromCharCode(parseInt(n.substring(2), 16)) : 1100 | String.fromCharCode(+n.substring(1)); 1101 | } 1102 | return ''; 1103 | }); 1104 | } 1105 | 1106 | function replace(regex, opt) { 1107 | regex = regex.source; 1108 | opt = opt || ''; 1109 | return function self(name, val) { 1110 | if (!name) return new RegExp(regex, opt); 1111 | val = val.source || val; 1112 | val = val.replace(/(^|[^\[])\^/g, '$1'); 1113 | regex = regex.replace(name, val); 1114 | return self; 1115 | }; 1116 | } 1117 | 1118 | function noop() {} 1119 | noop.exec = noop; 1120 | 1121 | function merge(obj) { 1122 | var i = 1, 1123 | target, key; 1124 | 1125 | for (; i < arguments.length; i++) { 1126 | target = arguments[i]; 1127 | for (key in target) { 1128 | if (Object.prototype.hasOwnProperty.call(target, key)) { 1129 | obj[key] = target[key]; 1130 | } 1131 | } 1132 | } 1133 | 1134 | return obj; 1135 | } 1136 | 1137 | 1138 | /** 1139 | * Marked 1140 | */ 1141 | 1142 | function marked(src, opt, callback) { 1143 | if (callback || typeof opt === 'function') { 1144 | if (!callback) { 1145 | callback = opt; 1146 | opt = null; 1147 | } 1148 | 1149 | opt = merge({}, marked.defaults, opt || {}); 1150 | 1151 | var highlight = opt.highlight, 1152 | tokens, pending, i = 0; 1153 | 1154 | try { 1155 | tokens = Lexer.lex(src, opt) 1156 | } catch (e) { 1157 | return callback(e); 1158 | } 1159 | 1160 | pending = tokens.length; 1161 | 1162 | var done = function(err) { 1163 | if (err) { 1164 | opt.highlight = highlight; 1165 | return callback(err); 1166 | } 1167 | 1168 | var out; 1169 | 1170 | try { 1171 | out = Parser.parse(tokens, opt); 1172 | } catch (e) { 1173 | err = e; 1174 | } 1175 | 1176 | opt.highlight = highlight; 1177 | 1178 | return err ? 1179 | callback(err) : 1180 | callback(null, out); 1181 | }; 1182 | 1183 | if (!highlight || highlight.length < 3) { 1184 | return done(); 1185 | } 1186 | 1187 | delete opt.highlight; 1188 | 1189 | if (!pending) return done(); 1190 | 1191 | for (; i < tokens.length; i++) { 1192 | (function(token) { 1193 | if (token.type !== 'code') { 1194 | return --pending || done(); 1195 | } 1196 | return highlight(token.text, token.lang, function(err, code) { 1197 | if (err) return done(err); 1198 | if (code == null || code === token.text) { 1199 | return --pending || done(); 1200 | } 1201 | token.text = code; 1202 | token.escaped = true; 1203 | --pending || done(); 1204 | }); 1205 | })(tokens[i]); 1206 | } 1207 | 1208 | return; 1209 | } 1210 | try { 1211 | if (opt) opt = merge({}, marked.defaults, opt); 1212 | return Parser.parse(Lexer.lex(src, opt), opt); 1213 | } catch (e) { 1214 | e.message += '\nPlease report this to https://github.com/chjj/marked.'; 1215 | if ((opt || marked.defaults).silent) { 1216 | return '

    An error occured:

    ' +
    1217 |                     escape(e.message + '', true) +
    1218 |                     '
    '; 1219 | } 1220 | throw e; 1221 | } 1222 | } 1223 | 1224 | /** 1225 | * Options 1226 | */ 1227 | 1228 | marked.options = 1229 | marked.setOptions = function(opt) { 1230 | merge(marked.defaults, opt); 1231 | return marked; 1232 | }; 1233 | 1234 | marked.defaults = { 1235 | gfm: true, 1236 | tables: true, 1237 | breaks: false, 1238 | pedantic: false, 1239 | sanitize: false, 1240 | sanitizer: null, 1241 | mangle: true, 1242 | smartLists: false, 1243 | silent: false, 1244 | highlight: null, 1245 | langPrefix: 'lang-', 1246 | smartypants: false, 1247 | headerPrefix: '', 1248 | renderer: new Renderer, 1249 | xhtml: false 1250 | }; 1251 | 1252 | /** 1253 | * Expose 1254 | */ 1255 | 1256 | marked.Parser = Parser; 1257 | marked.parser = Parser.parse; 1258 | 1259 | marked.Renderer = Renderer; 1260 | 1261 | marked.Lexer = Lexer; 1262 | marked.lexer = Lexer.lex; 1263 | 1264 | marked.InlineLexer = InlineLexer; 1265 | marked.inlineLexer = InlineLexer.output; 1266 | 1267 | marked.parse = marked; 1268 | 1269 | if (typeof module !== 'undefined' && typeof exports === 'object') { 1270 | module.exports = marked; 1271 | } else if (typeof define === 'function' && define.amd) { 1272 | define(function() { return marked; }); 1273 | } else { 1274 | this.marked = marked; 1275 | } 1276 | 1277 | }).call(function() { 1278 | return this || (typeof window !== 'undefined' ? window : global); 1279 | }()); -------------------------------------------------------------------------------- /static/js/skel.min.js: -------------------------------------------------------------------------------- 1 | /* skel.js v3.0.1 | (c) skel.io | MIT licensed */ 2 | var skel=function(){"use strict";var t={breakpointIds:null,events:{},isInit:!1,obj:{attachments:{},breakpoints:{},head:null,states:{}},sd:"/",state:null,stateHandlers:{},stateId:"",vars:{},DOMReady:null,indexOf:null,isArray:null,iterate:null,matchesMedia:null,extend:function(e,n){t.iterate(n,function(i){t.isArray(n[i])?(t.isArray(e[i])||(e[i]=[]),t.extend(e[i],n[i])):"object"==typeof n[i]?("object"!=typeof e[i]&&(e[i]={}),t.extend(e[i],n[i])):e[i]=n[i]})},newStyle:function(t){var e=document.createElement("style");return e.type="text/css",e.innerHTML=t,e},_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,i=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+i in n||"Webkit"+i in n||"O"+i in n||"ms"+i in n},on:function(e,n){var i=e.split(/[\s]+/);return t.iterate(i,function(e){var a=i[e];if(t.isInit){if("init"==a)return void n();if("change"==a)n();else{var r=a.charAt(0);if("+"==r||"!"==r){var o=a.substring(1);if(o in t.obj.breakpoints)if("+"==r&&t.obj.breakpoints[o].active)n();else if("!"==r&&!t.obj.breakpoints[o].active)return void n()}}}t.events[a]||(t.events[a]=[]),t.events[a].push(n)}),t},trigger:function(e){return t.events[e]&&0!=t.events[e].length?(t.iterate(t.events[e],function(n){t.events[e][n]()}),t):void 0},breakpoint:function(e){return t.obj.breakpoints[e]},breakpoints:function(e){function n(t,e){this.name=this.id=t,this.media=e,this.active=!1,this.wasActive=!1}return n.prototype.matches=function(){return t.matchesMedia(this.media)},n.prototype.sync=function(){this.wasActive=this.active,this.active=this.matches()},t.iterate(e,function(i){t.obj.breakpoints[i]=new n(i,e[i])}),window.setTimeout(function(){t.poll()},0),t},addStateHandler:function(e,n){t.stateHandlers[e]=n},callStateHandler:function(e){var n=t.stateHandlers[e]();t.iterate(n,function(e){t.state.attachments.push(n[e])})},changeState:function(e){t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].sync()}),t.vars.lastStateId=t.stateId,t.stateId=e,t.breakpointIds=t.stateId===t.sd?[]:t.stateId.substring(1).split(t.sd),t.obj.states[t.stateId]?t.state=t.obj.states[t.stateId]:(t.obj.states[t.stateId]={attachments:[]},t.state=t.obj.states[t.stateId],t.iterate(t.stateHandlers,t.callStateHandler)),t.detachAll(t.state.attachments),t.attachAll(t.state.attachments),t.vars.stateId=t.stateId,t.vars.state=t.state,t.trigger("change"),t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].active?t.obj.breakpoints[e].wasActive||t.trigger("+"+e):t.obj.breakpoints[e].wasActive&&t.trigger("-"+e)})},generateStateConfig:function(e,n){var i={};return t.extend(i,e),t.iterate(t.breakpointIds,function(e){t.extend(i,n[t.breakpointIds[e]])}),i},getStateId:function(){var e="";return t.iterate(t.obj.breakpoints,function(n){var i=t.obj.breakpoints[n];i.matches()&&(e+=t.sd+i.id)}),e},poll:function(){var e="";e=t.getStateId(),""===e&&(e=t.sd),e!==t.stateId&&t.changeState(e)},_attach:null,attach:function(e){var n=t.obj.head,i=e.element;return i.parentNode&&i.parentNode.tagName?!1:(t._attach||(t._attach=n.firstChild),n.insertBefore(i,t._attach.nextSibling),e.permanent&&(t._attach=i),!0)},attachAll:function(e){var n=[];t.iterate(e,function(t){n[e[t].priority]||(n[e[t].priority]=[]),n[e[t].priority].push(e[t])}),n.reverse(),t.iterate(n,function(e){t.iterate(n[e],function(i){t.attach(n[e][i])})})},detach:function(t){var e=t.element;return t.permanent||!e.parentNode||e.parentNode&&!e.parentNode.tagName?!1:(e.parentNode.removeChild(e),!0)},detachAll:function(e){var n={};t.iterate(e,function(t){n[e[t].id]=!0}),t.iterate(t.obj.attachments,function(e){e in n||t.detach(t.obj.attachments[e])})},attachment:function(e){return e in t.obj.attachments?t.obj.attachments[e]:null},newAttachment:function(e,n,i,a){return t.obj.attachments[e]={id:e,element:n,priority:i,permanent:a}},init:function(){t.initMethods(),t.initVars(),t.initEvents(),t.obj.head=document.getElementsByTagName("head")[0],t.isInit=!0,t.trigger("init")},initEvents:function(){t.on("resize",function(){t.poll()}),t.on("orientationChange",function(){t.poll()}),t.DOMReady(function(){t.trigger("ready")}),window.onload&&t.on("load",window.onload),window.onload=function(){t.trigger("load")},window.onresize&&t.on("resize",window.onresize),window.onresize=function(){t.trigger("resize")},window.onorientationchange&&t.on("orientationChange",window.onorientationchange),window.onorientationchange=function(){t.trigger("orientationChange")}},initMethods:function(){document.addEventListener?!function(e,n){t.DOMReady=n()}("domready",function(){function t(t){for(r=1;t=n.shift();)t()}var e,n=[],i=document,a="DOMContentLoaded",r=/^loaded|^c/.test(i.readyState);return i.addEventListener(a,e=function(){i.removeEventListener(a,e),t()}),function(t){r?t():n.push(t)}}):!function(e,n){t.DOMReady=n()}("domready",function(t){function e(t){for(h=1;t=i.shift();)t()}var n,i=[],a=!1,r=document,o=r.documentElement,s=o.doScroll,c="DOMContentLoaded",d="addEventListener",u="onreadystatechange",l="readyState",f=s?/^loaded|^c/:/^loaded|c/,h=f.test(r[l]);return r[d]&&r[d](c,n=function(){r.removeEventListener(c,n,a),e()},a),s&&r.attachEvent(u,n=function(){/^c/.test(r[l])&&(r.detachEvent(u,n),e())}),t=s?function(e){self!=top?h?e():i.push(e):function(){try{o.doScroll("left")}catch(n){return setTimeout(function(){t(e)},50)}e()}()}:function(t){h?t():i.push(t)}}),Array.prototype.indexOf?t.indexOf=function(t,e){return t.indexOf(e)}:t.indexOf=function(t,e){if("string"==typeof t)return t.indexOf(e);var n,i,a=e?e:0;if(!this)throw new TypeError;if(i=this.length,0===i||a>=i)return-1;for(0>a&&(a=i-Math.abs(a)),n=a;i>n;n++)if(this[n]===t)return n;return-1},Array.isArray?t.isArray=function(t){return Array.isArray(t)}:t.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)},Object.keys?t.iterate=function(t,e){if(!t)return[];var n,i=Object.keys(t);for(n=0;i[n]&&e(i[n],t[i[n]])!==!1;n++);}:t.iterate=function(t,e){if(!t)return[];var n;for(n in t)if(Object.prototype.hasOwnProperty.call(t,n)&&e(n,t[n])===!1)break},window.matchMedia?t.matchesMedia=function(t){return""==t?!0:window.matchMedia(t).matches}:window.styleMedia||window.media?t.matchesMedia=function(t){if(""==t)return!0;var e=window.styleMedia||window.media;return e.matchMedium(t||"all")}:window.getComputedStyle?t.matchesMedia=function(t){if(""==t)return!0;var e=document.createElement("style"),n=document.getElementsByTagName("script")[0],i=null;e.type="text/css",e.id="matchmediajs-test",n.parentNode.insertBefore(e,n),i="getComputedStyle"in window&&window.getComputedStyle(e,null)||e.currentStyle;var a="@media "+t+"{ #matchmediajs-test { width: 1px; } }";return e.styleSheet?e.styleSheet.cssText=a:e.textContent=a,"1px"===i.width}:t.matchesMedia=function(t){if(""==t)return!0;var e,n,i,a,r={"min-width":null,"max-width":null},o=!1;for(i=t.split(/\s+and\s+/),e=0;er["max-width"]||null!==r["min-height"]&&cr["max-height"]?!1:!0},navigator.userAgent.match(/MSIE ([0-9]+)/)&&RegExp.$1<9&&(t.newStyle=function(t){var e=document.createElement("span");return e.innerHTML=' ",e})},initVars:function(){var e,n,i,a=navigator.userAgent;e="other",n=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(RegExp.$1),!1):void 0}),t.vars.browser=e,t.vars.browserVersion=n,e="other",n=0,i=[["ios",/([0-9_]+) like Mac OS X/,function(t){return t.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(t){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(t){return t.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(i[2]?i[2](RegExp.$1):RegExp.$1),!1):void 0}),t.vars.os=e,t.vars.osVersion=n,t.vars.IEVersion="ie"==t.vars.browser?t.vars.browserVersion:99,t.vars.touch="wp"==t.vars.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),t.vars.mobile="wp"==t.vars.os||"android"==t.vars.os||"ios"==t.vars.os||"bb"==t.vars.os}};return t.init(),t}();!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.skel=e()}(this,function(){return skel}); 3 | -------------------------------------------------------------------------------- /static/js/util.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | /** 4 | * Generate an indented list of links from a nav. Meant for use with panel(). 5 | * @return {jQuery} jQuery object. 6 | */ 7 | $.fn.navList = function() { 8 | 9 | var $this = $(this); 10 | $a = $this.find('a'), 11 | b = []; 12 | 13 | $a.each(function() { 14 | 15 | var $this = $(this), 16 | indent = Math.max(0, $this.parents('li').length - 1), 17 | href = $this.attr('href'), 18 | target = $this.attr('target'); 19 | 20 | b.push( 21 | '
    ' + 26 | '' + 27 | $this.text() + 28 | '' 29 | ); 30 | 31 | }); 32 | 33 | return b.join(''); 34 | 35 | }; 36 | 37 | /** 38 | * Panel-ify an element. 39 | * @param {object} userConfig User config. 40 | * @return {jQuery} jQuery object. 41 | */ 42 | $.fn.panel = function(userConfig) { 43 | 44 | // No elements? 45 | if (this.length == 0) 46 | return $this; 47 | 48 | // Multiple elements? 49 | if (this.length > 1) { 50 | 51 | for (var i=0; i < this.length; i++) 52 | $(this[i]).panel(userConfig); 53 | 54 | return $this; 55 | 56 | } 57 | 58 | // Vars. 59 | var $this = $(this), 60 | $body = $('body'), 61 | $window = $(window), 62 | id = $this.attr('id'), 63 | config; 64 | 65 | // Config. 66 | config = $.extend({ 67 | 68 | // Delay. 69 | delay: 0, 70 | 71 | // Hide panel on link click. 72 | hideOnClick: false, 73 | 74 | // Hide panel on escape keypress. 75 | hideOnEscape: false, 76 | 77 | // Hide panel on swipe. 78 | hideOnSwipe: false, 79 | 80 | // Reset scroll position on hide. 81 | resetScroll: false, 82 | 83 | // Reset forms on hide. 84 | resetForms: false, 85 | 86 | // Side of viewport the panel will appear. 87 | side: null, 88 | 89 | // Target element for "class". 90 | target: $this, 91 | 92 | // Class to toggle. 93 | visibleClass: 'visible' 94 | 95 | }, userConfig); 96 | 97 | // Expand "target" if it's not a jQuery object already. 98 | if (typeof config.target != 'jQuery') 99 | config.target = $(config.target); 100 | 101 | // Panel. 102 | 103 | // Methods. 104 | $this._hide = function(event) { 105 | 106 | // Already hidden? Bail. 107 | if (!config.target.hasClass(config.visibleClass)) 108 | return; 109 | 110 | // If an event was provided, cancel it. 111 | if (event) { 112 | 113 | event.preventDefault(); 114 | event.stopPropagation(); 115 | 116 | } 117 | 118 | // Hide. 119 | config.target.removeClass(config.visibleClass); 120 | 121 | // Post-hide stuff. 122 | window.setTimeout(function() { 123 | 124 | // Reset scroll position. 125 | if (config.resetScroll) 126 | $this.scrollTop(0); 127 | 128 | // Reset forms. 129 | if (config.resetForms) 130 | $this.find('form').each(function() { 131 | this.reset(); 132 | }); 133 | 134 | }, config.delay); 135 | 136 | }; 137 | 138 | // Vendor fixes. 139 | $this 140 | .css('-ms-overflow-style', '-ms-autohiding-scrollbar') 141 | .css('-webkit-overflow-scrolling', 'touch'); 142 | 143 | // Hide on click. 144 | if (config.hideOnClick) { 145 | 146 | $this.find('a') 147 | .css('-webkit-tap-highlight-color', 'rgba(0,0,0,0)'); 148 | 149 | $this 150 | .on('click', 'a', function(event) { 151 | 152 | var $a = $(this), 153 | href = $a.attr('href'), 154 | target = $a.attr('target'); 155 | 156 | if (!href || href == '#' || href == '' || href == '#' + id) 157 | return; 158 | 159 | // Cancel original event. 160 | event.preventDefault(); 161 | event.stopPropagation(); 162 | 163 | // Hide panel. 164 | $this._hide(); 165 | 166 | // Redirect to href. 167 | window.setTimeout(function() { 168 | 169 | if (target == '_blank') 170 | window.open(href); 171 | else 172 | window.location.href = href; 173 | 174 | }, config.delay + 10); 175 | 176 | }); 177 | 178 | } 179 | 180 | // Event: Touch stuff. 181 | $this.on('touchstart', function(event) { 182 | 183 | $this.touchPosX = event.originalEvent.touches[0].pageX; 184 | $this.touchPosY = event.originalEvent.touches[0].pageY; 185 | 186 | }) 187 | 188 | $this.on('touchmove', function(event) { 189 | 190 | if ($this.touchPosX === null 191 | || $this.touchPosY === null) 192 | return; 193 | 194 | var diffX = $this.touchPosX - event.originalEvent.touches[0].pageX, 195 | diffY = $this.touchPosY - event.originalEvent.touches[0].pageY, 196 | th = $this.outerHeight(), 197 | ts = ($this.get(0).scrollHeight - $this.scrollTop()); 198 | 199 | // Hide on swipe? 200 | if (config.hideOnSwipe) { 201 | 202 | var result = false, 203 | boundary = 20, 204 | delta = 50; 205 | 206 | switch (config.side) { 207 | 208 | case 'left': 209 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX > delta); 210 | break; 211 | 212 | case 'right': 213 | result = (diffY < boundary && diffY > (-1 * boundary)) && (diffX < (-1 * delta)); 214 | break; 215 | 216 | case 'top': 217 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY > delta); 218 | break; 219 | 220 | case 'bottom': 221 | result = (diffX < boundary && diffX > (-1 * boundary)) && (diffY < (-1 * delta)); 222 | break; 223 | 224 | default: 225 | break; 226 | 227 | } 228 | 229 | if (result) { 230 | 231 | $this.touchPosX = null; 232 | $this.touchPosY = null; 233 | $this._hide(); 234 | 235 | return false; 236 | 237 | } 238 | 239 | } 240 | 241 | // Prevent vertical scrolling past the top or bottom. 242 | if (($this.scrollTop() < 0 && diffY < 0) 243 | || (ts > (th - 2) && ts < (th + 2) && diffY > 0)) { 244 | 245 | event.preventDefault(); 246 | event.stopPropagation(); 247 | 248 | } 249 | 250 | }); 251 | 252 | // Event: Prevent certain events inside the panel from bubbling. 253 | $this.on('click touchend touchstart touchmove', function(event) { 254 | event.stopPropagation(); 255 | }); 256 | 257 | // Event: Hide panel if a child anchor tag pointing to its ID is clicked. 258 | $this.on('click', 'a[href="#' + id + '"]', function(event) { 259 | 260 | event.preventDefault(); 261 | event.stopPropagation(); 262 | 263 | config.target.removeClass(config.visibleClass); 264 | 265 | }); 266 | 267 | // Body. 268 | 269 | // Event: Hide panel on body click/tap. 270 | $body.on('click touchend', function(event) { 271 | $this._hide(event); 272 | }); 273 | 274 | // Event: Toggle. 275 | $body.on('click', 'a[href="#' + id + '"]', function(event) { 276 | 277 | event.preventDefault(); 278 | event.stopPropagation(); 279 | 280 | config.target.toggleClass(config.visibleClass); 281 | 282 | }); 283 | 284 | // Window. 285 | 286 | // Event: Hide on ESC. 287 | if (config.hideOnEscape) 288 | $window.on('keydown', function(event) { 289 | 290 | if (event.keyCode == 27) 291 | $this._hide(event); 292 | 293 | }); 294 | 295 | return $this; 296 | 297 | }; 298 | 299 | /** 300 | * Apply "placeholder" attribute polyfill to one or more forms. 301 | * @return {jQuery} jQuery object. 302 | */ 303 | $.fn.placeholder = function() { 304 | 305 | // Browser natively supports placeholders? Bail. 306 | if (typeof (document.createElement('input')).placeholder != 'undefined') 307 | return $(this); 308 | 309 | // No elements? 310 | if (this.length == 0) 311 | return $this; 312 | 313 | // Multiple elements? 314 | if (this.length > 1) { 315 | 316 | for (var i=0; i < this.length; i++) 317 | $(this[i]).placeholder(); 318 | 319 | return $this; 320 | 321 | } 322 | 323 | // Vars. 324 | var $this = $(this); 325 | 326 | // Text, TextArea. 327 | $this.find('input[type=text],textarea') 328 | .each(function() { 329 | 330 | var i = $(this); 331 | 332 | if (i.val() == '' 333 | || i.val() == i.attr('placeholder')) 334 | i 335 | .addClass('polyfill-placeholder') 336 | .val(i.attr('placeholder')); 337 | 338 | }) 339 | .on('blur', function() { 340 | 341 | var i = $(this); 342 | 343 | if (i.attr('name').match(/-polyfill-field$/)) 344 | return; 345 | 346 | if (i.val() == '') 347 | i 348 | .addClass('polyfill-placeholder') 349 | .val(i.attr('placeholder')); 350 | 351 | }) 352 | .on('focus', function() { 353 | 354 | var i = $(this); 355 | 356 | if (i.attr('name').match(/-polyfill-field$/)) 357 | return; 358 | 359 | if (i.val() == i.attr('placeholder')) 360 | i 361 | .removeClass('polyfill-placeholder') 362 | .val(''); 363 | 364 | }); 365 | 366 | // Password. 367 | $this.find('input[type=password]') 368 | .each(function() { 369 | 370 | var i = $(this); 371 | var x = $( 372 | $('
    ') 373 | .append(i.clone()) 374 | .remove() 375 | .html() 376 | .replace(/type="password"/i, 'type="text"') 377 | .replace(/type=password/i, 'type=text') 378 | ); 379 | 380 | if (i.attr('id') != '') 381 | x.attr('id', i.attr('id') + '-polyfill-field'); 382 | 383 | if (i.attr('name') != '') 384 | x.attr('name', i.attr('name') + '-polyfill-field'); 385 | 386 | x.addClass('polyfill-placeholder') 387 | .val(x.attr('placeholder')).insertAfter(i); 388 | 389 | if (i.val() == '') 390 | i.hide(); 391 | else 392 | x.hide(); 393 | 394 | i 395 | .on('blur', function(event) { 396 | 397 | event.preventDefault(); 398 | 399 | var x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 400 | 401 | if (i.val() == '') { 402 | 403 | i.hide(); 404 | x.show(); 405 | 406 | } 407 | 408 | }); 409 | 410 | x 411 | .on('focus', function(event) { 412 | 413 | event.preventDefault(); 414 | 415 | var i = x.parent().find('input[name=' + x.attr('name').replace('-polyfill-field', '') + ']'); 416 | 417 | x.hide(); 418 | 419 | i 420 | .show() 421 | .focus(); 422 | 423 | }) 424 | .on('keypress', function(event) { 425 | 426 | event.preventDefault(); 427 | x.val(''); 428 | 429 | }); 430 | 431 | }); 432 | 433 | // Events. 434 | $this 435 | .on('submit', function() { 436 | 437 | $this.find('input[type=text],input[type=password],textarea') 438 | .each(function(event) { 439 | 440 | var i = $(this); 441 | 442 | if (i.attr('name').match(/-polyfill-field$/)) 443 | i.attr('name', ''); 444 | 445 | if (i.val() == i.attr('placeholder')) { 446 | 447 | i.removeClass('polyfill-placeholder'); 448 | i.val(''); 449 | 450 | } 451 | 452 | }); 453 | 454 | }) 455 | .on('reset', function(event) { 456 | 457 | event.preventDefault(); 458 | 459 | $this.find('select') 460 | .val($('option:first').val()); 461 | 462 | $this.find('input,textarea') 463 | .each(function() { 464 | 465 | var i = $(this), 466 | x; 467 | 468 | i.removeClass('polyfill-placeholder'); 469 | 470 | switch (this.type) { 471 | 472 | case 'submit': 473 | case 'reset': 474 | break; 475 | 476 | case 'password': 477 | i.val(i.attr('defaultValue')); 478 | 479 | x = i.parent().find('input[name=' + i.attr('name') + '-polyfill-field]'); 480 | 481 | if (i.val() == '') { 482 | i.hide(); 483 | x.show(); 484 | } 485 | else { 486 | i.show(); 487 | x.hide(); 488 | } 489 | 490 | break; 491 | 492 | case 'checkbox': 493 | case 'radio': 494 | i.attr('checked', i.attr('defaultValue')); 495 | break; 496 | 497 | case 'text': 498 | case 'textarea': 499 | i.val(i.attr('defaultValue')); 500 | 501 | if (i.val() == '') { 502 | i.addClass('polyfill-placeholder'); 503 | i.val(i.attr('placeholder')); 504 | } 505 | 506 | break; 507 | 508 | default: 509 | i.val(i.attr('defaultValue')); 510 | break; 511 | 512 | } 513 | }); 514 | 515 | }); 516 | 517 | return $this; 518 | 519 | }; 520 | 521 | /** 522 | * Moves elements to/from the first positions of their respective parents. 523 | * @param {jQuery} $elements Elements (or selector) to move. 524 | * @param {bool} condition If true, moves elements to the top. Otherwise, moves elements back to their original locations. 525 | */ 526 | $.prioritize = function($elements, condition) { 527 | 528 | var key = '__prioritize'; 529 | 530 | // Expand $elements if it's not already a jQuery object. 531 | if (typeof $elements != 'jQuery') 532 | $elements = $($elements); 533 | 534 | // Step through elements. 535 | $elements.each(function() { 536 | 537 | var $e = $(this), $p, 538 | $parent = $e.parent(); 539 | 540 | // No parent? Bail. 541 | if ($parent.length == 0) 542 | return; 543 | 544 | // Not moved? Move it. 545 | if (!$e.data(key)) { 546 | 547 | // Condition is false? Bail. 548 | if (!condition) 549 | return; 550 | 551 | // Get placeholder (which will serve as our point of reference for when this element needs to move back). 552 | $p = $e.prev(); 553 | 554 | // Couldn't find anything? Means this element's already at the top, so bail. 555 | if ($p.length == 0) 556 | return; 557 | 558 | // Move element to top of parent. 559 | $e.prependTo($parent); 560 | 561 | // Mark element as moved. 562 | $e.data(key, $p); 563 | 564 | } 565 | 566 | // Moved already? 567 | else { 568 | 569 | // Condition is true? Bail. 570 | if (condition) 571 | return; 572 | 573 | $p = $e.data(key); 574 | 575 | // Move element back to its original location (using our placeholder). 576 | $e.insertAfter($p); 577 | 578 | // Unmark element as moved. 579 | $e.removeData(key); 580 | 581 | } 582 | 583 | }); 584 | 585 | }; 586 | 587 | })(jQuery); -------------------------------------------------------------------------------- /static/js/v1.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var _typeof = 4 | typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? 5 | function(obj) { return typeof obj; } : 6 | function(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" : typeof obj; }; 7 | 8 | (function(root, f) { 9 | if ((typeof exports === "undefined" ? "undefined" : _typeof(exports)) === 'object' && typeof module !== 'undefined') { 10 | module.exports = f; 11 | } else if (typeof define === 'function' && defind.amd) { 12 | define([], f); 13 | } else { 14 | root.BIRD = f(); 15 | } 16 | })(window || global || self || this, function() { 17 | 18 | }); -------------------------------------------------------------------------------- /utils/disabledIP.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | list: [ 3 | // '117.144.208.34' 4 | ] 5 | }; -------------------------------------------------------------------------------- /utils/encrypt.js: -------------------------------------------------------------------------------- 1 | // 参考 https://github.com/darknessomi/musicbox/wiki/ 2 | const crypto = require('crypto') 3 | const bigInt = require('big-integer') 4 | const modulus = '00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7' 5 | const nonce = '0CoJUm6Qyw8W8jud' 6 | const pubKey = '010001' 7 | 8 | String.prototype.hexEncode = function() { 9 | let hex, i 10 | 11 | let result = "" 12 | for (i = 0; i < this.length; i++) { 13 | hex = this.charCodeAt(i).toString(16) 14 | result += ("" + hex).slice(-4) 15 | } 16 | return result 17 | } 18 | 19 | const createSecretKey = (size) => { 20 | const keys = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 21 | let key = "" 22 | for (let i = 0; i < size; i++) { 23 | let pos = Math.random() * keys.length 24 | pos = Math.floor(pos) 25 | key = key + keys.charAt(pos) 26 | } 27 | return key 28 | } 29 | 30 | const aesEncrypt = (text, secKey) => { 31 | const _text = text 32 | const lv = new Buffer('0102030405060708', "binary") 33 | const _secKey = new Buffer(secKey, "binary") 34 | const cipher = crypto.createCipheriv('AES-128-CBC', _secKey, lv) 35 | let encrypted = cipher.update(_text, 'utf8', 'base64') 36 | encrypted += cipher.final('base64') 37 | return encrypted 38 | } 39 | 40 | const zfill = (str, size) => { 41 | while (str.length < size) str = "0" + str 42 | return str 43 | } 44 | 45 | const rsaEncrypt = (text, pubKey, modulus) => { 46 | const _text = text.split('').reverse().join('') 47 | const biText = bigInt(new Buffer(_text).toString('hex'), 16), 48 | biEx = bigInt(pubKey, 16), 49 | biMod = bigInt(modulus, 16), 50 | biRet = biText.modPow(biEx, biMod) 51 | return zfill(biRet.toString(16), 256) 52 | } 53 | 54 | const encrypt = (params) => { 55 | const text = JSON.stringify(params) 56 | const secKey = createSecretKey(16) 57 | const encText = aesEncrypt(aesEncrypt(text, nonce), secKey) 58 | const encSecKey = rsaEncrypt(secKey, pubKey, modulus) 59 | return { 60 | params: encText, 61 | encSecKey: encSecKey 62 | } 63 | } 64 | 65 | module.exports = encrypt -------------------------------------------------------------------------------- /utils/logUtils.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | print: function(params) { 3 | var arr = ['-------------------------------------']; 4 | // var date = new Date(); 5 | // var month = date.getMonth() + 1; 6 | // var day = date.getDay(); 7 | // var hours = date.getHours(); 8 | // var minutes = date.getMinutes(); 9 | // var seconds = date.getSeconds(); 10 | // var datestr = date.getFullYear() + '-' + (month < 10 ? '0' + month : month) + '-' + (day < 10 ? '0' + day : day) + ' ' + (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes) + ':' + (seconds < 10 ? '0' + seconds : seconds); 11 | //arr.push('Time: ' + datestr); 12 | for (var i in params) { 13 | arr.push(i + ': ' + params[i]); 14 | } 15 | arr.push('-------------------------------------'); 16 | console.log(arr.join('\n')); 17 | } 18 | }; -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | const encrypt = require('./encrypt'); 2 | const request = require('request'); 3 | const superagent = require('superagent'); 4 | const qs = require('querystring'); 5 | 6 | let options = { 7 | method: 'POST', 8 | headers: { 9 | 'Accept': '*/*', 10 | 'Accept-Language': 'zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4', 11 | 'Connection': 'keep-alive', 12 | 'Content-Type': 'application/x-www-form-urlencoded', 13 | 'Referer': 'http://music.163.com', 14 | 'Host': 'music.163.com', 15 | 'Cookie': 'appver=2.0.2;', 16 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36', 17 | } 18 | } 19 | // const requestServer = (config) => { 20 | // options['uri'] = `http://music.163.com${config.path}` 21 | // options['form'] = encrypt(config.params) 22 | // return new Promise((resolve, reject) => { 23 | // request(options, (err, ret, body) => { 24 | // if (!err && ret.statusCode === 200) { 25 | // resolve(JSON.parse(body)) 26 | // } else { 27 | // reject(err) 28 | // } 29 | // }) 30 | // }) 31 | // } 32 | 33 | const requestServer = (config) => { 34 | //console.log(config) 35 | return new Promise((resolve, reject) => { 36 | superagent 37 | .post(`http://music.163.com${config.path}`) 38 | .set(options.headers) 39 | .send(encrypt(config.params)) 40 | .end((err, res) => { 41 | if (err || !res.ok) { 42 | reject(err) 43 | } else { 44 | let ret = res.text || res.body 45 | if (typeof ret === 'object') 46 | resolve(ret) 47 | else { 48 | resolve(JSON.parse(ret)) 49 | } 50 | } 51 | }) 52 | }) 53 | } 54 | 55 | module.exports = { 56 | requestServer 57 | } -------------------------------------------------------------------------------- /utils/utils.js: -------------------------------------------------------------------------------- 1 | const request = require('request'); 2 | const qs = require('qs'); 3 | /** 4 | * 公共请求 5 | * @param {Object} config 6 | * @return {Object} Promise 7 | */ 8 | const createServer = (config) => { 9 | //console.log(config) 10 | return new Promise((resolve, reject) => { 11 | request(config, (err, ret, body) => { 12 | if (!err) { 13 | resolve(body) 14 | } else { 15 | reject(err) 16 | } 17 | }) 18 | }) 19 | } 20 | /** 21 | * 转换对象 22 | * @param {Object} req 23 | * @param {Object} res 24 | * @param {Object} next 25 | * @return {Array} [config,protocol,host,cb] 26 | */ 27 | 28 | const convert = (req,res,next,url) => { 29 | let host = req.hostname; 30 | let protocol = req.protocol; 31 | let method = req.method.toUpperCase(); 32 | let ip = req.headers['x-real-ip'] ? req.headers['x-real-ip'] : req.ip.replace(/::ffff:/, ''); 33 | let _params = method === 'GET' ? req.query : req.body; 34 | _params['url'] = req.body.url || req.query.url || ''; 35 | let cb = _params.callback; 36 | let headers = req.headers; 37 | //console.log(req.headers.referer || req.headers.referrer) 38 | let config = { 39 | method: method, 40 | gzip: true 41 | }; 42 | let params = {}; 43 | for (let i in _params) { 44 | let temp = _params[i]; 45 | if (i === 'url' && temp) { 46 | let o = temp.split('?'); 47 | let uri = o[0] 48 | config['url'] = uri; 49 | headers['host'] = uri.replace(/^(http|https):\/\//g, '').split('/')[0]; 50 | if (o.length > 1) { 51 | o[1].split('&').forEach(item => { 52 | let k = item.split('='); 53 | params[k[0]] = encodeURI(k[1]); 54 | }) 55 | } 56 | }else if(i=='headers'){ 57 | let _t = temp || '{}' 58 | let _chs = eval(`(${_t})`) 59 | for(let ch in _chs){ 60 | headers[ch] = _chs[ch] 61 | } 62 | } else { 63 | params[i] = temp; 64 | } 65 | } 66 | let _hs = headers 67 | let hs = {} 68 | for(let _h in _hs){ 69 | let h = _hs[_h] 70 | if(/(content-length)/.test(_h)) continue 71 | else hs[_h] = _hs[_h] 72 | } 73 | //console.log(hs) 74 | if (method === 'POST') config['form'] = params; 75 | else config['url'] = config['url'] ? `${config['url']}?${qs.stringify(params)}` : (url?`${url}?${qs.stringify(params)}`:null) ; 76 | config['headers'] = hs; 77 | return [config,protocol,host,cb,params] 78 | } 79 | 80 | module.exports = { 81 | convert, 82 | createServer 83 | } -------------------------------------------------------------------------------- /views/error.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | h1= message 5 | h2= error.status 6 | pre #{error.stack} 7 | -------------------------------------------------------------------------------- /views/layout.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/static/css/main.css') 6 | meta(name="viewport",content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0") 7 | meta(name="author",content="xCss xioveliu@gmail.com") 8 | meta(name="keywords",content="JsonBird,数据代理服务,数据接口代理,远程数据接口代理,远程接口,代理服务,A Remote Data Interface Proxy Service,CORS,HTTPS解决方案") 9 | meta(name="description",content="业界领先的远程数据接口代理服务") 10 | link(rel="shortcut icon",href="/static/images/favicon.ico") 11 | link(rel="bookmark",href="/static/images/favicon_72.ico") 12 | link(rel="apple-touch-icon",href="/static/images/fav_72x72.png") 13 | link(rel="apple-touch-icon",sizes="16x16",href="/static/images/fav-16x16.png") 14 | link(rel="apple-touch-icon",sizes="36x36",href="/static/images/fav-36x36.png") 15 | link(rel="apple-touch-icon",sizes="72x72",href="/static/images/fav-72x72.png") 16 | script(src="/static/js/jquery.min.js") 17 | script(src="/static/js/skel.min.js") 18 | script(src="/static/js/util.js") 19 | script(src="/static/js/main.js") 20 | script(src="//cdn1.lncld.net/static/js/3.0.4/av-min.js") 21 | script(src="//unpkg.com/valine@latest/dist/Valine.min.js") 22 | style. 23 | .v input,.v button{ 24 | height:auto; 25 | box-shadow:none; 26 | display:inline-block; 27 | color:#233333; 28 | } 29 | .v input{ 30 | padding:10px 5px !important; 31 | } 32 | .v input:focus,.v textarea:focus{ 33 | box-shadow:none; 34 | } 35 | 36 | body 37 | block content 38 | -------------------------------------------------------------------------------- /views/test.pug: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title= title 5 | link(rel='stylesheet', href='/static/css/style.css') 6 | link(rel='stylesheet', href='/static/css/test.css') 7 | meta( name="viewport",content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0") 8 | meta( name="author",content="xCss xioveliu@gmail.com") 9 | body 10 | h1= title 11 | p Welcome to #{title} 12 | 13 | -------------------------------------------------------------------------------- /views/welcome.pug: -------------------------------------------------------------------------------- 1 | extends layout 2 | 3 | block content 4 | header#header 5 | div.inner 6 | a.logo(href='/') JsonBird 7 | nav#nav 8 | a(href='/') 首页 9 | a(href='http://jsrun.net/PriKp',target="_blank") 示例 10 | a(href='#interface') 数据接口 11 | a(href='https://github.com/xCss/JsonBird/wiki',target="_blank") 接口文档 12 | a(href='https://github.com/xCss/JsonBird',target="_blank") Github 13 | a.navPanelToggle(href='#navPanel') 14 | span.fa.fa-bars 15 | section#banner 16 | div.inner 17 | header 18 | h1 Welcome to JsonBird 19 | p 业界领先的远程数据接口代理服务 20 | div.flex 21 | div 22 | span.icon.fa-bolt 23 | h3 Fast 24 | p 专业线路,极限速度 25 | div 26 | span.icon.fa-lock 27 | h3 Safe 28 | p 全站 HTTPS,保证数据安全 29 | div 30 | span.icon.fa-arrows 31 | h3 Multiple 32 | p 多种接口,自由选择 33 | footer.flex 34 | div 35 | a.button(href='#interface') 数据接口 36 | div 37 | a.button(href="/v1?url=http://www.bing.com/HPImageArchive.aspx?format=js&idx=1&n=1",target="_blank") 快速开始 38 | section#wecando.wrapper 39 | div.inner 40 | h4 我们能做到: 41 | ol 42 | li 让不支持跨域的远程数据接口支持跨域。 43 | li 让不支持JSONP的远程数据接口支持JSONP(添加参数&callback=cb_name)。 44 | li 提供专业的HTTPS解决方案,让数据传输更安全(同时解决远程数据接口不支持HTTPS的问题)。 45 | li 多种数据请求方式与返回格式。 46 | li 丰富的数据接口 ... 47 | section#interface.wrapper 48 | div.inner 49 | h4 已经实现的数据接口: 50 | div.table-wrapper 51 | table.alt 52 | thead 53 | tr 54 | th(width="200") 接口名称 55 | th 接口地址 56 | th 请求方式 57 | tbody 58 | tr 59 | td 远程代理 60 | td https://bird.ioliu.cn/v1 61 | td GET/POST 62 | tr 63 | td 远程代理v2
    64 | 支持自定义header 65 | td https://bird.ioliu.cn/v2
    66 | 自定义header请求设置 67 | td GET/POST 68 | tr 69 | td 笑话 70 | td https://bird.ioliu.cn/joke 71 | td GET/POST 72 | tr 73 | td IP 地址 74 | td https://bird.ioliu.cn/ip 75 | td GET/POST 76 | tr 77 | td 天气查询
    2017/09/03更新 78 | td https://bird.ioliu.cn/weather 79 | td GET 80 | tr 81 | td 号码归属地
    2017/09/03更新 82 | td https://bird.ioliu.cn/mobile 83 | td GET 84 | tr 85 | td 网易云音乐
    (歌曲/歌单) 86 | td https://bird.ioliu.cn/netease 87 | td GET 88 | div.comment 89 | 90 | 91 | footer#footer 92 | div.inner 93 | div.copyright © 94 | script. 95 | document.write(`2016 - ${(new Date()).getFullYear()} `) 96 | a(href='https://ioliu.cn',target="_blank") 云淡风轻 97 | 98 | 99 | 100 | script. 101 | var valine = new Valine(); 102 | valine.init({ 103 | el: '.comment', 104 | appId: 'z3bnu4iiAfsfAAUxAKaIjNO2-gzGzoHsz', 105 | appKey: '9mssBF0PqBl9bUM9dOQShCQk', 106 | placeholder: 'ヾノ≧∀≦)o来啊,快活啊!' 107 | }) 108 | 109 | window.onload = function(){ 110 | //- baidu Analytics 111 | if(!/^http:\/\/localhost/.test(location.href)){ 112 | var _hmt = _hmt || []; 113 | var hm = document.createElement("script"); 114 | if(/ioliu/.test(location.href)){ 115 | hm.src = "//hm.baidu.com/hm.js?930e83393fcca01f1abff14df21cec12"; 116 | }else{ 117 | hm.src = "//hm.baidu.com/hm.js?9b00cc8218f3159497e6207cee8dd0c6"; 118 | } 119 | var s = document.getElementsByTagName("script")[0]; 120 | s.parentNode.insertBefore(hm, s); 121 | } 122 | }; 123 | --------------------------------------------------------------------------------