-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #2 from h-sugawara/feature/dev-app
アプリケーションコードと単体テストコードを追加
- Loading branch information
Showing
14 changed files
with
931 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
'use strict'; | ||
|
||
const ogs = require('open-graph-scraper'); | ||
|
||
const getConfig = require('./lib/configure'); | ||
const getParameters = require('./lib/parameters'); | ||
const generate = require('./lib/generator'); | ||
|
||
hexo.extend.tag.register( | ||
'link_preview', | ||
(args, content) => { | ||
return generate(ogs, getParameters(args, content, getConfig(hexo.config))) | ||
.then(tag => tag) | ||
.catch(error => { | ||
console.log('generate error:', error); | ||
return ''; | ||
}); | ||
}, | ||
{ | ||
async: true, | ||
ends: true, | ||
} | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
'use strict'; | ||
|
||
function hasProperty(obj, key) { | ||
return obj && Object.hasOwnProperty.call(obj, key); | ||
} | ||
|
||
function stringLength(text) { | ||
if (typeof text !== 'string') { | ||
throw new Error('text is not string type.'); | ||
} | ||
|
||
const segmentation = new Intl.Segmenter('ja', { granularity: 'grapheme' }); | ||
return [...segmentation.segment(text)].length; | ||
} | ||
|
||
function stringSlice(text, start, end) { | ||
if (typeof text !== 'string') { | ||
throw new Error('text is not string type.'); | ||
} | ||
|
||
const strLength = stringLength(text); | ||
|
||
let startIndex = typeof start !== 'number' || isNaN(Number(start)) ? 0 : Number(start); | ||
if (startIndex < 0) { | ||
startIndex = Math.max(startIndex + strLength, 0); | ||
} | ||
|
||
let endIndex = typeof end !== 'number' || isNaN(Number(end)) ? strLength : Number(end); | ||
if (endIndex >= strLength) { | ||
endIndex = strLength; | ||
} else if (endIndex < 0) { | ||
endIndex = Math.max(endIndex + strLength, 0); | ||
} | ||
|
||
let strings = ''; | ||
|
||
if (startIndex >= strLength || endIndex <= startIndex) { | ||
return strings; | ||
} | ||
const segmentation = new Intl.Segmenter('ja', { granularity: 'grapheme' }); | ||
[...segmentation.segment(text)] | ||
.forEach((value, index) => { | ||
if (startIndex <= index && index < endIndex) { | ||
strings += value.segment; | ||
} | ||
}); | ||
return strings; | ||
} | ||
|
||
module.exports = { | ||
hasProperty, | ||
stringLength, | ||
stringSlice, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
'use strict'; | ||
|
||
const { hasProperty } = require('./common'); | ||
|
||
const ANCHOR_LINK_CLASS_NAME = 'link-preview'; | ||
const DESCRIPTION_LENGTH = 140; | ||
const DISGUISE_CRAWLER = true; | ||
|
||
module.exports = hexoCfg => { | ||
const config = { | ||
class_name: { | ||
anchor_link: ANCHOR_LINK_CLASS_NAME, | ||
}, | ||
description_length: DESCRIPTION_LENGTH, | ||
disguise_crawler: DISGUISE_CRAWLER, | ||
}; | ||
|
||
if (!hasProperty(hexoCfg, 'link_preview')) { | ||
return config; | ||
} | ||
|
||
const hexoCfgLinkPreview = hexoCfg.link_preview; | ||
|
||
if (hasProperty(hexoCfgLinkPreview, 'class_name')) { | ||
const hexoCfgLinkPreviewClassName = hexoCfgLinkPreview.class_name; | ||
|
||
if (typeof hexoCfgLinkPreviewClassName === 'string') { | ||
config.class_name.anchor_link = hexoCfgLinkPreviewClassName || config.class_name.anchor_link; | ||
} else if (typeof hexoCfgLinkPreviewClassName === 'object') { | ||
if (hasProperty(hexoCfgLinkPreviewClassName, 'anchor_link') && typeof hexoCfgLinkPreviewClassName.anchor_link === 'string') { | ||
config.class_name.anchor_link = hexoCfgLinkPreviewClassName.anchor_link || config.class_name.anchor_link; | ||
} | ||
|
||
if (hasProperty(hexoCfgLinkPreviewClassName, 'image') && hexoCfgLinkPreviewClassName.image !== '') { | ||
config.class_name.image = hexoCfgLinkPreviewClassName.image; | ||
} | ||
} | ||
} | ||
|
||
if (hasProperty(hexoCfgLinkPreview, 'description_length') && typeof hexoCfgLinkPreview.description_length === 'number') { | ||
config.description_length = hexoCfgLinkPreview.description_length || config.description_length; | ||
} | ||
|
||
if (hasProperty(hexoCfgLinkPreview, 'disguise_crawler') && typeof hexoCfgLinkPreview.disguise_crawler === 'boolean') { | ||
config.disguise_crawler = hexoCfgLinkPreview.disguise_crawler; | ||
} | ||
|
||
return config; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
'use strict'; | ||
|
||
const { getOgTitle, getOgDescription, getOgImage } = require('./opengraph'); | ||
const { newHtmlDivTag, newHtmlAnchorTag, newHtmlImgTag } = require('./htmltag'); | ||
|
||
module.exports = (scraper, params) => { | ||
return scraper(params.scrape) | ||
.then(data => data.result) | ||
.then(ogp => { | ||
const { valid: isTitleValid, title: escapedTitle } = getOgTitle(ogp); | ||
const { valid: isDescValid, description: escapedDesc } = getOgDescription(ogp, params.generate.descriptionLength); | ||
const { valid: isImageValid, image: imageUrl } = getOgImage(ogp); | ||
|
||
if (!isTitleValid || !isDescValid) { | ||
return newHtmlAnchorTag(params.scrape.url, params.generate); | ||
} | ||
|
||
const title = newHtmlDivTag('og-title', escapedTitle); | ||
const desc = newHtmlDivTag('og-description', escapedDesc); | ||
const descriptions = newHtmlDivTag('descriptions', title + desc); | ||
const image = isImageValid | ||
? newHtmlDivTag('og-image', newHtmlImgTag(imageUrl, params.generate)) : ''; | ||
const content = image + descriptions; | ||
|
||
return newHtmlAnchorTag(params.scrape.url, params.generate, content); | ||
}) | ||
.catch(error => { | ||
throw error; | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
'use strict'; | ||
|
||
const util = require('hexo-util'); | ||
const { hasProperty } = require('./common'); | ||
|
||
function newHtmlDivTag(className, content) { | ||
return util.htmlTag('div', { class: className }, content, false); | ||
} | ||
|
||
function newHtmlAnchorTag(url, config, content) { | ||
const tagAttrs = { href: url, target: config.target, rel: config.rel }; | ||
|
||
if (typeof content === 'string' && content !== '') { | ||
tagAttrs.class = config.className.anchor_link; | ||
return util.htmlTag('a', tagAttrs, content, false); | ||
} else if (hasProperty(config, 'fallbackText') && typeof config.fallbackText === 'string' && config.fallbackText !== '') { | ||
return util.htmlTag('a', tagAttrs, config.fallbackText); | ||
} | ||
|
||
throw new Error('failed to generate a new anchor tag.'); | ||
} | ||
|
||
function newHtmlImgTag(url, config) { | ||
const tagAttrs = { src: url }; | ||
|
||
if (hasProperty(config.className, 'image') && typeof config.className.image === 'string' && config.className.image !== '') { | ||
tagAttrs.class = config.className.image; | ||
} | ||
|
||
return util.htmlTag('img', tagAttrs, ''); | ||
} | ||
|
||
module.exports = { | ||
newHtmlDivTag, | ||
newHtmlAnchorTag, | ||
newHtmlImgTag, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
'use strict'; | ||
|
||
const util = require('hexo-util'); | ||
const { hasProperty, stringLength, stringSlice } = require('./common'); | ||
|
||
function getOgTitle(ogp) { | ||
const result = { valid: false, title: '' }; | ||
|
||
if (!hasProperty(ogp, 'ogTitle')) { | ||
return result; | ||
} | ||
|
||
const escapedTitle = util.escapeHTML(ogp.ogTitle); | ||
if (typeof escapedTitle === 'string' && escapedTitle !== '') { | ||
result.valid = true; | ||
result.title = escapedTitle; | ||
} | ||
return result; | ||
} | ||
|
||
function getOgDescription(ogp, maxLength) { | ||
const result = { valid: false, description: '' }; | ||
|
||
if (!hasProperty(ogp, 'ogDescription')) { | ||
return result; | ||
} | ||
|
||
const escapedDescription = util.escapeHTML(ogp.ogDescription); | ||
const descriptionText = maxLength && stringLength(escapedDescription) > maxLength | ||
? stringSlice(escapedDescription, 0, maxLength) + '...' : escapedDescription; | ||
if (typeof descriptionText === 'string' && descriptionText !== '') { | ||
result.valid = true; | ||
result.description = descriptionText; | ||
} | ||
return result; | ||
} | ||
|
||
function getOgImage(ogp, selectIndex = 0) { | ||
if (!hasProperty(ogp, 'ogImage') || ogp.ogImage.length === 0) { | ||
return { valid: false, image: '' }; | ||
} | ||
|
||
const index = selectIndex >= ogp.ogImage.length ? ogp.ogImage.length - 1 : 0; | ||
|
||
return { valid: true, image: ogp.ogImage[index].url }; | ||
} | ||
|
||
module.exports = { | ||
getOgTitle, | ||
getOgDescription, | ||
getOgImage, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
'use strict'; | ||
|
||
const urlRegex = /^(http|https):\/\//g; | ||
const targetKeywords = ['_self', '_blank', '_parent', '_top']; | ||
const relKeywords = ['external', 'nofollow', 'noopener', 'noreferrer', 'opener']; | ||
|
||
const CRAWLER_USER_AGENT = 'Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +http://www.google.com/bot.html) Chrome/112.0.0.0 Safari/537.36'; | ||
|
||
function parseArgs(args) { | ||
const urls = args.filter(arg => arg.search(urlRegex) === 0); | ||
if (urls.length < 1) { | ||
throw new Error('Scraping target url is not contains.'); | ||
} | ||
|
||
const targets = args.filter(arg => targetKeywords.includes(arg)); | ||
const relationships = args.filter(arg => relKeywords.includes(arg)); | ||
|
||
return { | ||
url: urls[0], | ||
target: targets[0] || '_blank', | ||
rel: relationships[0] || 'nofollow', | ||
}; | ||
} | ||
|
||
function getFetchOptions(isCrawler) { | ||
const fetchOptions = {}; | ||
const headers = {}; | ||
|
||
if (typeof isCrawler === 'boolean' && isCrawler) { | ||
headers['user-agent'] = CRAWLER_USER_AGENT; | ||
} | ||
|
||
if (Object.keys(headers).length !== 0) { | ||
fetchOptions.headers = headers; | ||
} | ||
|
||
return fetchOptions; | ||
} | ||
|
||
module.exports = (args, content, config) => { | ||
const { url, target, rel } = parseArgs(args); | ||
const { class_name: className, description_length: descriptionLength, disguise_crawler: isCrawler } = config; | ||
const fetchOptions = getFetchOptions(isCrawler); | ||
|
||
return { | ||
scrape: { url, fetchOptions }, | ||
generate: { target, rel, descriptionLength, className, fallbackText: content }, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
'use strict'; | ||
|
||
const { hasProperty, stringLength, stringSlice } = require('../lib/common'); | ||
|
||
describe('common', () => { | ||
it('Has a property at object', () => { | ||
const obj = { 'test_key': 'test_value' }; | ||
|
||
expect(hasProperty(obj, 'test_key')).toBeTruthy(); | ||
}); | ||
|
||
it('Has not a property at object', () => { | ||
const obj = { 'test_key': 'test_value' }; | ||
|
||
expect(hasProperty(obj, 'not_test_key')).toBeFalsy(); | ||
}); | ||
|
||
it('Calculate a length of string', () => { | ||
expect(stringLength('aBあア亞 19%+👨🏻💻🇯🇵🍎')).toEqual(14); | ||
}); | ||
|
||
it('Cannot calculate a length of except for string', () => { | ||
expect(() => stringLength(0)).toThrow(new Error('text is not string type.')); | ||
}); | ||
|
||
it('Slice a string', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', 0, 3)).toEqual('aBあ'); | ||
}); | ||
|
||
it('Cannot slice a value except for string', () => { | ||
expect(() => stringSlice(0)).toThrow(new Error('text is not string type.')); | ||
}); | ||
|
||
it('Slice a string (start is not number type)', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', 'hoge', 5)).toEqual('aBあア亞'); | ||
}); | ||
|
||
it('Slice a string (start is smaller than zero)', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', -5, 13)).toEqual('%+👨🏻💻🇯🇵'); | ||
}); | ||
|
||
it('Slice a string (end calculate automatically)', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', 7)).toEqual('19%+👨🏻💻🇯🇵🍎'); | ||
}); | ||
|
||
it('Slice a string (end is smaller than zero)', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', 7, -4)).toEqual('19%'); | ||
}); | ||
|
||
it('Slice a string (end is smaller than start)', () => { | ||
expect(stringSlice('aBあア亞 19%+👨🏻💻🇯🇵🍎', -1, -2)).toEqual(''); | ||
}); | ||
}); |
Oops, something went wrong.