open_graph.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. 'use strict';
  2. const urlFn = require('url');
  3. const moment = require('moment');
  4. const util = require('hexo-util');
  5. const htmlTag = util.htmlTag;
  6. const stripHTML = util.stripHTML;
  7. const escapeHTML = util.escapeHTML;
  8. let cheerio;
  9. function meta(name, content, escape) {
  10. if (escape !== false && typeof content === 'string') {
  11. content = escapeHTML(content);
  12. }
  13. return `${htmlTag('meta', {
  14. name,
  15. content
  16. })}\n`;
  17. }
  18. function og(name, content, escape) {
  19. if (escape !== false && typeof content === 'string') {
  20. content = escapeHTML(content);
  21. }
  22. return `${htmlTag('meta', {
  23. property: name,
  24. content
  25. })}\n`;
  26. }
  27. function openGraphHelper(options = {}) {
  28. if (!cheerio) cheerio = require('cheerio');
  29. const page = this.page;
  30. const config = this.config;
  31. const content = page.content;
  32. let images = options.image || options.images || page.photos || [];
  33. let description = options.description || page.description || page.excerpt || content || config.description;
  34. const keywords = page.keywords || (page.tags && page.tags.length ? page.tags : undefined) || config.keywords;
  35. const title = options.title || page.title || config.title;
  36. const type = options.type || (this.is_post() ? 'article' : 'website');
  37. const url = options.url || this.url;
  38. const siteName = options.site_name || config.title;
  39. const twitterCard = options.twitter_card || 'summary';
  40. const updated = options.updated !== false ? options.updated || page.updated : false;
  41. const language = options.language || page.lang || page.language || config.language;
  42. let result = '';
  43. if (!Array.isArray(images)) images = [images];
  44. if (description) {
  45. description = stripHTML(description).substring(0, 200)
  46. .trim() // Remove prefixing/trailing spaces
  47. .replace(/</g, '&lt;')
  48. .replace(/>/g, '&gt;')
  49. .replace(/&/g, '&amp;')
  50. .replace(/"/g, '&quot;')
  51. .replace(/'/g, '&apos;')
  52. .replace(/\n/g, ' '); // Replace new lines by spaces
  53. }
  54. if (!images.length && content) {
  55. images = images.slice();
  56. const $ = cheerio.load(content);
  57. $('img').each(function() {
  58. const src = $(this).attr('src');
  59. if (src) images.push(src);
  60. });
  61. }
  62. if (description) {
  63. result += meta('description', description, false);
  64. }
  65. if (keywords) {
  66. if (typeof keywords === 'string') {
  67. result += meta('keywords', keywords);
  68. } else if (keywords.length) {
  69. result += meta('keywords', keywords.map(tag => {
  70. return tag.name ? tag.name : tag;
  71. }).filter(keyword => !!keyword).join());
  72. }
  73. }
  74. result += og('og:type', type);
  75. result += og('og:title', title);
  76. result += og('og:url', url, false);
  77. result += og('og:site_name', siteName);
  78. if (description) {
  79. result += og('og:description', description, false);
  80. }
  81. if (language) {
  82. result += og('og:locale', language, false);
  83. }
  84. images = images.map(path => {
  85. if (!urlFn.parse(path).host) {
  86. // resolve `path`'s absolute path relative to current page's url
  87. // `path` can be both absolute (starts with `/`) or relative.
  88. return urlFn.resolve(url || config.url, path);
  89. }
  90. return path;
  91. });
  92. images.forEach(path => {
  93. result += og('og:image', path, false);
  94. });
  95. if (updated) {
  96. if ((moment.isMoment(updated) || moment.isDate(updated)) && !isNaN(updated.valueOf())) {
  97. result += og('og:updated_time', updated.toISOString());
  98. }
  99. }
  100. result += meta('twitter:card', twitterCard);
  101. result += meta('twitter:title', title);
  102. if (description) {
  103. result += meta('twitter:description', description, false);
  104. }
  105. if (images.length) {
  106. result += meta('twitter:image', images[0], false);
  107. }
  108. if (options.twitter_id) {
  109. let twitterId = options.twitter_id;
  110. if (twitterId[0] !== '@') twitterId = `@${twitterId}`;
  111. result += meta('twitter:creator', twitterId);
  112. }
  113. if (options.twitter_site) {
  114. result += meta('twitter:site', options.twitter_site, false);
  115. }
  116. if (options.google_plus) {
  117. result += `${htmlTag('link', {rel: 'publisher', href: options.google_plus})}\n`;
  118. }
  119. if (options.fb_admins) {
  120. result += og('fb:admins', options.fb_admins);
  121. }
  122. if (options.fb_app_id) {
  123. result += og('fb:app_id', options.fb_app_id);
  124. }
  125. return result.trim();
  126. }
  127. module.exports = openGraphHelper;