mirror of
https://github.com/misskey-dev/summaly.git
synced 2025-05-09 15:47:18 +09:00
Initial commit 🍕
This commit is contained in:
commit
80b010a7f8
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules
|
||||||
|
/typings
|
||||||
|
/built
|
||||||
|
npm-debug.log
|
5
.npmignore
Normal file
5
.npmignore
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
/node_modules
|
||||||
|
/src
|
||||||
|
/typings
|
||||||
|
/tmp
|
||||||
|
npm-debug.log
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2016 syuilo
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
12
README.md
Normal file
12
README.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
webcard
|
||||||
|
=======
|
||||||
|
|
||||||
|
Generate an html of any web page's summary.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
`$ npm install webcard`
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
[MIT](LICENSE)
|
20
dtsm.json
Normal file
20
dtsm.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"repos": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/borisyankov/DefinitelyTyped.git",
|
||||||
|
"ref": "master"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"path": "typings",
|
||||||
|
"bundle": "typings/bundle.d.ts",
|
||||||
|
"link": {
|
||||||
|
"npm": {
|
||||||
|
"include": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"request/request.d.ts": {
|
||||||
|
"ref": "0c5c7a2d2bd0ce7dcab963a8402a9042749ca2da"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
gulpfile.js
Normal file
35
gulpfile.js
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const gulp = require('gulp');
|
||||||
|
const babel = require('gulp-babel');
|
||||||
|
const ts = require('gulp-typescript');
|
||||||
|
const es = require('event-stream');
|
||||||
|
|
||||||
|
const project = ts.createProject('tsconfig.json');
|
||||||
|
|
||||||
|
gulp.task('build', [
|
||||||
|
'build:ts',
|
||||||
|
'build:copy'
|
||||||
|
]);
|
||||||
|
|
||||||
|
gulp.task('build:ts', () => {
|
||||||
|
const tsResult = project
|
||||||
|
.src()
|
||||||
|
.pipe(ts(project))
|
||||||
|
.pipe(babel({
|
||||||
|
presets: ['es2015', 'stage-3']
|
||||||
|
}));
|
||||||
|
|
||||||
|
return es.merge(
|
||||||
|
tsResult.pipe(gulp.dest('./built/'))/*,
|
||||||
|
tsResult.dts.pipe(gulp.dest('./built/'))*/
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
gulp.task('build:copy', () => {
|
||||||
|
return es.merge(
|
||||||
|
gulp.src([
|
||||||
|
'./src/**/*.pug'
|
||||||
|
]).pipe(gulp.dest('./built/'))
|
||||||
|
);
|
||||||
|
});
|
32
package.json
Normal file
32
package.json
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"name": "webcard",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"description": "Generate an html of any web page's summary",
|
||||||
|
"author": "syuilo <i@syuilo.com>",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": "https://github.com/syuilo/webcard.git",
|
||||||
|
"bugs": "https://github.com/syuilo/webcard/issues",
|
||||||
|
"main": "./built/index.js",
|
||||||
|
"typings": "./built/index.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node ./built/index.js",
|
||||||
|
"build": "gulp build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"babel-preset-es2015": "6.13.2",
|
||||||
|
"babel-preset-stage-3": "6.11.0",
|
||||||
|
"event-stream": "3.3.4",
|
||||||
|
"gulp": "3.9.1",
|
||||||
|
"gulp-babel": "6.1.2",
|
||||||
|
"gulp-typescript": "2.13.6",
|
||||||
|
"typescript": "1.8.10"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"babel-core": "6.13.2",
|
||||||
|
"babel-polyfill": "6.13.0",
|
||||||
|
"cheerio-httpcli": "^0.6.9",
|
||||||
|
"html-entities": "^1.2.0",
|
||||||
|
"pug": "^2.0.0-beta6",
|
||||||
|
"request": "^2.74.0"
|
||||||
|
}
|
||||||
|
}
|
136
src/general/index.ts
Normal file
136
src/general/index.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import * as URL from 'url';
|
||||||
|
import * as request from 'request';
|
||||||
|
const pug = require('pug');
|
||||||
|
import Options from '../options';
|
||||||
|
|
||||||
|
const Entities = require('html-entities').AllHtmlEntities;
|
||||||
|
const entities = new Entities();
|
||||||
|
|
||||||
|
const client = require('cheerio-httpcli');
|
||||||
|
client.referer = false;
|
||||||
|
client.timeout = 10000;
|
||||||
|
|
||||||
|
export default async (url: URL.Url, opts: Options): Promise<string> => {
|
||||||
|
const res = await client.fetch(url.href);
|
||||||
|
|
||||||
|
if (res.error) {
|
||||||
|
throw 'something happened';
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentType: string = res.response.headers['content-type'];
|
||||||
|
|
||||||
|
// HTMLじゃなかった場合は中止
|
||||||
|
if (contentType.indexOf('text/html') === -1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const $: any = res.$;
|
||||||
|
|
||||||
|
let title =
|
||||||
|
$('meta[property="misskey:title"]').attr('content') ||
|
||||||
|
$('meta[property="og:title"]').attr('content') ||
|
||||||
|
$('meta[property="twitter:title"]').attr('content') ||
|
||||||
|
$('title').text();
|
||||||
|
|
||||||
|
if (title == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
title = clip(entities.decode(title), 100);
|
||||||
|
|
||||||
|
const lang: string = $('html').attr('lang');
|
||||||
|
|
||||||
|
const type =
|
||||||
|
$('meta[property="misskey:type"]').attr('content') ||
|
||||||
|
$('meta[property="og:type"]').attr('content');
|
||||||
|
|
||||||
|
let image =
|
||||||
|
$('meta[property="misskey:image"]').attr('content') ||
|
||||||
|
$('meta[property="og:image"]').attr('content') ||
|
||||||
|
$('meta[property="twitter:image"]').attr('content') ||
|
||||||
|
$('link[rel="image_src"]').attr('href') ||
|
||||||
|
$('link[rel="apple-touch-icon"]').attr('href') ||
|
||||||
|
$('link[rel="apple-touch-icon image_src"]').attr('href');
|
||||||
|
|
||||||
|
image = image ? proxy(URL.resolve(url.href, image)) : null;
|
||||||
|
|
||||||
|
let description =
|
||||||
|
$('meta[property="misskey:summary"]').attr('content') ||
|
||||||
|
$('meta[property="og:description"]').attr('content') ||
|
||||||
|
$('meta[property="twitter:description"]').attr('content') ||
|
||||||
|
$('meta[name="description"]').attr('content');
|
||||||
|
|
||||||
|
description = description
|
||||||
|
? clip(entities.decode(description), 300)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (title === description) {
|
||||||
|
description = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let siteName =
|
||||||
|
$('meta[property="misskey:site-name"]').attr('content') ||
|
||||||
|
$('meta[property="og:site_name"]').attr('content') ||
|
||||||
|
$('meta[name="application-name"]').attr('content');
|
||||||
|
|
||||||
|
siteName = siteName ? entities.decode(siteName) : null;
|
||||||
|
|
||||||
|
let icon =
|
||||||
|
$('meta[property="misskey:site-icon"]').attr('content') ||
|
||||||
|
$('link[rel="shortcut icon"]').attr('href') ||
|
||||||
|
$('link[rel="icon"]').attr('href') ||
|
||||||
|
'/favicon.ico';
|
||||||
|
|
||||||
|
icon = icon ? proxy(URL.resolve(url.href, icon)) : null;
|
||||||
|
|
||||||
|
return pug.renderFile(`${__dirname}/summary.pug`, {
|
||||||
|
url: url,
|
||||||
|
title: title,
|
||||||
|
icon: icon,
|
||||||
|
lang: lang,
|
||||||
|
description: description,
|
||||||
|
type: type,
|
||||||
|
image: image,
|
||||||
|
siteName: siteName
|
||||||
|
});
|
||||||
|
|
||||||
|
function proxy(url: string): string {
|
||||||
|
return `${opts.proxy}/${url}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function promisifyRequest(request: any): (x: any) => Promise<any> {
|
||||||
|
return (x: any) => {
|
||||||
|
return new Promise<any>((resolve) => {
|
||||||
|
request(x, (a: any, b: any, c: any) => {
|
||||||
|
resolve(c);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function nullOrEmpty(val: string): boolean {
|
||||||
|
if (val === undefined) {
|
||||||
|
return true;
|
||||||
|
} else if (val === null) {
|
||||||
|
return true;
|
||||||
|
} else if (val.trim() === '') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clip(s: string, max: number): string {
|
||||||
|
if (nullOrEmpty(s)) {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s.trim();
|
||||||
|
|
||||||
|
if (s.length > max) {
|
||||||
|
return s.substr(0, max) + '...';
|
||||||
|
} else {
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
16
src/general/summary.pug
Normal file
16
src/general/summary.pug
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
a(title= url.href, href= url.href, target='_blank')
|
||||||
|
aside(lang= lang, data-type= type)
|
||||||
|
if image
|
||||||
|
div.thumbnail(style={'background-image': 'url(' + image + ')'})
|
||||||
|
h1.title= title
|
||||||
|
if description
|
||||||
|
p.description= description
|
||||||
|
footer
|
||||||
|
p.hostname
|
||||||
|
if url.protocol == 'https:'
|
||||||
|
i.fa.fa-lock.secure
|
||||||
|
= url.hostname
|
||||||
|
if icon
|
||||||
|
img.icon(src= icon, alt='')
|
||||||
|
if siteName
|
||||||
|
p.site-name= siteName
|
24
src/index.ts
Normal file
24
src/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import * as URL from 'url';
|
||||||
|
import IPlugin from './iplugin';
|
||||||
|
import Options from './options';
|
||||||
|
import general from './general';
|
||||||
|
|
||||||
|
// Init babel
|
||||||
|
require('babel-core/register');
|
||||||
|
require('babel-polyfill');
|
||||||
|
|
||||||
|
const plugins: IPlugin[] = [
|
||||||
|
require('./plugins/wikipedia')
|
||||||
|
];
|
||||||
|
|
||||||
|
export default async (url: string, opts: Options): Promise<string> => {
|
||||||
|
const _url = URL.parse(url, true);
|
||||||
|
|
||||||
|
const plugin = plugins.filter(plugin => plugin.test(_url))[0];
|
||||||
|
|
||||||
|
if (plugin) {
|
||||||
|
return await plugin.compile(_url, opts);
|
||||||
|
} else {
|
||||||
|
return await general(_url, opts);
|
||||||
|
}
|
||||||
|
}
|
9
src/iplugin.ts
Normal file
9
src/iplugin.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import * as URL from 'url';
|
||||||
|
import Options from './options';
|
||||||
|
|
||||||
|
interface IPlugin {
|
||||||
|
test: (url: URL.Url) => boolean;
|
||||||
|
compile: (url: URL.Url, opts: Options) => Promise<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IPlugin;
|
5
src/options.ts
Normal file
5
src/options.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
interface Options {
|
||||||
|
proxy: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Options;
|
31
src/plugins/wikipedia/index.ts
Normal file
31
src/plugins/wikipedia/index.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import * as URL from 'url';
|
||||||
|
const pug = require('pug');
|
||||||
|
import Options from '../../options';
|
||||||
|
|
||||||
|
const client = require('cheerio-httpcli');
|
||||||
|
client.referer = false;
|
||||||
|
client.timeout = 10000;
|
||||||
|
|
||||||
|
exports.test = (url: URL.Url) => {
|
||||||
|
return /\.wikipedia\.org$/.test(url.hostname);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.compile = async (url: URL.Url, opts: Options) => {
|
||||||
|
const res = await client.fetch(url.href);
|
||||||
|
const $: any = res.$;
|
||||||
|
|
||||||
|
const lang = url.hostname.substr(0, url.hostname.indexOf('.'));
|
||||||
|
const isDesktop = !/\.m\.wikipedia\.org$/.test(url.hostname);
|
||||||
|
const text: string = isDesktop
|
||||||
|
? $('#mw-content-text > p:first-of-type').text()
|
||||||
|
: $('#bodyContent > div:first-of-type > p:first-of-type').text();
|
||||||
|
|
||||||
|
return pug.renderFile(`${__dirname}/../../general/summary.pug`, {
|
||||||
|
url: url,
|
||||||
|
title: decodeURI(url.pathname.split('/')[2]),
|
||||||
|
icon: 'https://wikipedia.org/static/favicon/wikipedia.ico',
|
||||||
|
description: text,
|
||||||
|
image: `https://wikipedia.org/static/images/project-logos/${lang}wiki.png`,
|
||||||
|
siteName: 'Wikipedia'
|
||||||
|
});
|
||||||
|
};
|
34
tsconfig.json
Normal file
34
tsconfig.json
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmitOnError": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"declaration": true,
|
||||||
|
"sourceMap": false,
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"removeComments": false,
|
||||||
|
"noLib": false,
|
||||||
|
"outDir": "built",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"compileOnSave": false,
|
||||||
|
"atom": {
|
||||||
|
"rewriteTsconfig": true
|
||||||
|
},
|
||||||
|
"filesGlob": [
|
||||||
|
"./node_modules/typescript/lib/lib.es6.d.ts",
|
||||||
|
"./typings/bundle.d.ts",
|
||||||
|
"./src/**/*.ts"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"./node_modules/typescript/lib/lib.es6.d.ts",
|
||||||
|
"./typings/bundle.d.ts",
|
||||||
|
"./src/index.ts",
|
||||||
|
"./src/iplugin.ts",
|
||||||
|
"./src/options.ts",
|
||||||
|
"./src/general/index.ts",
|
||||||
|
"./src/plugins/wikipedia/index.ts"
|
||||||
|
]
|
||||||
|
}
|
86
tslint.json
Normal file
86
tslint.json
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"rules": {
|
||||||
|
"align": [true,
|
||||||
|
"parameters",
|
||||||
|
"statements"
|
||||||
|
],
|
||||||
|
"ban": false,
|
||||||
|
"class-name": true,
|
||||||
|
"comment-format": [true,
|
||||||
|
"check-upper-case"
|
||||||
|
],
|
||||||
|
"curly": true,
|
||||||
|
"eofline": true,
|
||||||
|
"forin": false,
|
||||||
|
"indent": [true, "tabs"],
|
||||||
|
"interface-name": false,
|
||||||
|
"jsdoc-format": true,
|
||||||
|
"label-position": true,
|
||||||
|
"label-undefined": true,
|
||||||
|
"max-line-length": [true, 140],
|
||||||
|
"member-access": false,
|
||||||
|
"member-ordering": [true,
|
||||||
|
"static-before-instance",
|
||||||
|
"variables-before-functions"
|
||||||
|
],
|
||||||
|
"no-any": false,
|
||||||
|
"no-arg": true,
|
||||||
|
"no-bitwise": true,
|
||||||
|
"no-console": [true,
|
||||||
|
"debug",
|
||||||
|
"info",
|
||||||
|
"time",
|
||||||
|
"timeEnd",
|
||||||
|
"trace"
|
||||||
|
],
|
||||||
|
"no-consecutive-blank-lines": true,
|
||||||
|
"no-construct": true,
|
||||||
|
"no-constructor-vars": true,
|
||||||
|
"no-debugger": true,
|
||||||
|
"no-duplicate-key": true,
|
||||||
|
"no-shadowed-variable": false,
|
||||||
|
"no-duplicate-variable": true,
|
||||||
|
"no-empty": true,
|
||||||
|
"no-eval": true,
|
||||||
|
"no-internal-module": true,
|
||||||
|
"no-require-imports": false,
|
||||||
|
"no-string-literal": false,
|
||||||
|
"no-switch-case-fall-through": true,
|
||||||
|
"no-trailing-whitespace": true,
|
||||||
|
"no-unreachable": true,
|
||||||
|
"no-unused-expression": true,
|
||||||
|
"no-unused-variable": true,
|
||||||
|
"no-use-before-declare": true,
|
||||||
|
"no-var-keyword": true,
|
||||||
|
"no-var-requires": false,
|
||||||
|
"one-line": [true,
|
||||||
|
"check-catch",
|
||||||
|
"check-whitespace"
|
||||||
|
],
|
||||||
|
"quotemark": false,
|
||||||
|
"radix": true,
|
||||||
|
"semicolon": true,
|
||||||
|
"switch-default": false,
|
||||||
|
"triple-equals": true,
|
||||||
|
"typedef": [true,
|
||||||
|
"call-signature",
|
||||||
|
"property-declaration"
|
||||||
|
],
|
||||||
|
"typedef-whitespace": [true, {
|
||||||
|
"call-signature": "nospace",
|
||||||
|
"index-signature": "nospace",
|
||||||
|
"parameter": "nospace",
|
||||||
|
"property-declaration": "nospace",
|
||||||
|
"variable-declaration": "nospace"
|
||||||
|
}],
|
||||||
|
"use-strict": false,
|
||||||
|
"variable-name": false,
|
||||||
|
"whitespace": [true,
|
||||||
|
"check-branch",
|
||||||
|
"check-decl",
|
||||||
|
"check-operator",
|
||||||
|
"check-separator",
|
||||||
|
"check-type"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user