Puppeteer.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import puppeteer, { Browser, PuppeteerLaunchOptions } from 'puppeteer';
  2. import art from 'art-template';
  3. import * as fs from 'fs';
  4. import botlogger from './logger.js';
  5. export class HtmlImg {
  6. private browser: Browser | null = null;
  7. async init() {
  8. if (!this.browser) {
  9. const options: PuppeteerLaunchOptions = {
  10. headless: true, // 无头模式,可根据需要设置为false,
  11. args: [
  12. '--no-sandbox',
  13. '--disable-setuid-sandbox',
  14. '--allow-file-access-from-files' // 新增参数
  15. ]
  16. };
  17. this.browser = await puppeteer.launch(options);
  18. }
  19. }
  20. async render(options: {
  21. url?: string;
  22. template: string;
  23. templateIsPath?: boolean;
  24. data: any;
  25. width?: number;
  26. height?: number;
  27. type?: string;
  28. quality?: number;
  29. fullPage?: boolean;
  30. background?: boolean;
  31. }) {
  32. try {
  33. await this.init();
  34. const {
  35. url,
  36. template,
  37. templateIsPath = true,
  38. data,
  39. width = -1,
  40. height = -1,
  41. type = 'png',
  42. quality = 100,
  43. fullPage = false,
  44. background = true
  45. } = options;
  46. // 读取模板
  47. const templateContent = templateIsPath ? fs.readFileSync(template, 'utf-8') : template;
  48. // 渲染HTML
  49. let html = '';
  50. if (templateContent) {
  51. html = art.render(templateContent, data);
  52. }
  53. // 计算高度
  54. // 创建页面
  55. const page = await this.browser!.newPage();
  56. await page.setViewport({ width, height });
  57. // 设置页面内容
  58. if (url) {
  59. await page.goto(url);
  60. await page.emulate({
  61. viewport: { width: 375, height: 667 },
  62. userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
  63. });
  64. await page.waitForNetworkIdle();
  65. if(data.selector){
  66. const element = await page.$(data.selector);
  67. if (element) {
  68. const image = await element.screenshot({ type: type as 'png' | 'jpeg', quality: type === 'jpeg'? quality : undefined });
  69. return image;
  70. }
  71. }
  72. //加载额外的js
  73. page.evaluate(data.urlJs)
  74. }else{
  75. await page.setContent(html, { waitUntil: 'networkidle0' });
  76. }
  77. // 获取document.body.scrollHeight
  78. const bodyheight = await page.evaluate(() => document.body.scrollHeight) as number;
  79. const bodywidth = await page.evaluate(() => document.body.scrollWidth) as number;
  80. botlogger.info(`获取body高度${bodyheight}和宽度${bodywidth}`)
  81. if(-1 != height && -1 != width){
  82. await page.setViewport({ width: bodywidth, height: bodyheight });
  83. await page.waitForNetworkIdle();
  84. }
  85. // 截图
  86. const image = await page.screenshot({
  87. type: type as 'png' | 'jpeg',
  88. quality: type === 'jpeg' ? quality : undefined,
  89. fullPage,
  90. omitBackground: !background
  91. });
  92. await page.close();
  93. return image;
  94. } catch (error) {
  95. botlogger.error('渲染图片失败:', error);
  96. throw error;
  97. }
  98. }
  99. async close() {
  100. if (this.browser) {
  101. await this.browser.close();
  102. this.browser = null;
  103. }
  104. }
  105. }