[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/magicoflolis/Userscript-Plus/userscript/tools/userscript.js [Back]  [Original]

import { URL } from 'node:url';
import fs from 'node:fs';
import dotenv from 'dotenv';
import watch from 'node-watch';
import { loadLanguages } from './languageLoader.js';

/**
 * @typedef { import('../package.json') } CFG
 */

const replaceTemplate = /\[\[(.*?)\]\]/g;
const metaStr = '[[metadata]]';

const log = (...msg) => {
  console.log('[LOG]', ...msg);
};
const err = (...msg) => {
  console.error('[ERROR]', ...msg);
};
/**
 * Object is typeof `object` / JSON Object
 * @template O
 * @param { O } obj
 * @returns { boolean }
 */
const isObj = (obj) => {
  /** @type { string } */
  const s = Object.prototype.toString.call(obj);
  return s.includes('Object');
};
/**
 * Object is `null` or `undefined`
 * @template O
 * @param { O } obj
 * @returns { boolean }
 */
const isNull = (obj) => {
  return Object.is(obj, null) || Object.is(obj, undefined);
};
/**
 * Object is Blank
 * @template O
 * @param { O } obj
 * @returns { boolean }
 */
const isBlank = (obj) => {
  return (
    (typeof obj === 'string' && Object.is(obj.trim(), '')) ||
    ((obj instanceof Set || obj instanceof Map) && Object.is(obj.size, 0)) ||
    (Array.isArray(obj) && Object.is(obj.length, 0)) ||
    (isObj(obj) && Object.is(Object.keys(obj).length, 0))
  );
};
/**
 * Object is Empty
 * @template O
 * @param { O } obj
 * @returns { boolean }
 */
const isEmpty = (obj) => {
  return isNull(obj) || isBlank(obj);
};
const nano = (template, data) => {
  return template.replace(replaceTemplate, (_match, key) => {
    const keys = key.split('.');
    let v = data[keys.shift()];
    for (const i in keys.length) v = v[keys[i]];
    return isEmpty(v) ? '' : v;
  });
};
const canAccess = async (filePath, encoding = 'utf-8') => {
  const testAccess = await fs.promises.access(
    filePath,
    fs.promises.constants.R_OK | fs.promises.constants.W_OK
  );
  if (isNull(testAccess)) {
    const data = await fs.promises.readFile(filePath, encoding);
    return data.toString(encoding);
  }
  return {
    msg: `Cannot access provided filePath: ${filePath}`
  };
};
const fileToJSON = async (filePath, encoding = 'utf-8') => {
  const testAccess = await canAccess(filePath, encoding);
  if (isObj(testAccess)) {
    throw new Error(testAccess.msg);
  }
  return JSON.parse(testAccess);
};
const writeUserJS = async (destinationFilePath, data) => {
  return await fs.promises.writeFile(destinationFilePath, data);
};
const toTime = () => {
  return new Intl.DateTimeFormat('default', {
    hour: 'numeric',
    minute: 'numeric',
    second: 'numeric',
    fractionalSecondDigits: 3
  }).format(new Date());
};
const dataMap = new Map();
const compareArrays = (a, b) =>
  a.length === b.length && a.every((element, index) => element === b[index]);
const addTo = (key, ...values) => {
  if (values.length === 0) {
    return '';
  }
  if (dataMap.has(key)) {
    if (compareArrays(dataMap.get(key), values)) {
      return dataMap.get(key);
    }
  }
  dataMap.set(key, values);
  return dataMap.get(key);
};
const filterData = [
  'name',
  'description',
  'author',
  'icon',
  'version',
  'url',
  'homepage',
  'bugs',
  'license'
];

(async () => {
  /** @type { dotenv.DotenvConfigOutput } */
  let result = {};
  try {
    // const args = process.argv.filter((val, i) => i >= 2);

    // if ('USERJS_META' in process.env) {
    //   args.push(process.env.USERJS_META);
    // }

    // if (Object.is(args.length, 0)) {
    //   args.push('./package.json');
    // }

    /**
     * @type { CFG }
     */
    const jsonData = await fileToJSON('./package.json');
    if (!isObj(jsonData)) {
      throw new Error('"jsonData" must be an object.');
    }
    if (!jsonData.userJS) {
      throw new Error('Missing "userJS" key');
    }
    const userJS = jsonData.userJS;
    const { build } = userJS;

    result = dotenv.config({
      path: isEmpty(process.env.JS_ENV) ? build.paths.dev.env : build.paths.public.env
    });
    if (result.error) {
      throw result.error;
    }
    if (isNull(result.parsed.JS_ENV)) {
      dotenv.populate(
        result.parsed,
        {
          JS_ENV: 'development'
        },
        { override: true, debug: true }
      );
    }
    const env = result.parsed;
    const js_env = env.JS_ENV === 'development';
    const dp = js_env ? 'dev' : 'public';
    const compileMetadata = () => {
      const metaData = [];
      for (const [key, value] of Object.entries(userJS.metadata)) {
        if (Array.isArray(value)) {
          for (const v of value) {
            metaData.push(`// @${key}     ${v}`);
          }
        } else if (isObj(value)) {
          for (const [k, v] of Object.entries(value)) {
            metaData.push(`// @${key}     ${k} ${v}`);
          }
        } else if (typeof value === 'boolean') {
          if (value === true) {
            metaData.push(`// @${key}`);
          }
        } else {
          metaData.push(`// @${key}     ${value}`);
        }
      }
      return metaData.join('\n');
    };
    const buildUserJS = async () => {
      try {
        const lngList = await loadLanguages(new URL('../src/_locales', import.meta.url));
        const transformLanguages = () => {
          const resp = {};
          for (const obj of lngList) {
            for (const [k, v] of Object.entries(obj)) {
              const o = {};
              for (const [key, value] of Object.entries(v)) {
                if (key.startsWith('ext')) {
                  continue;
                }
                if (/userjs_(name|description)/i.test(key)) {
                  continue;
                }
                if (isEmpty(value.message)) {
                  continue;
                }
                o[key] = value.message;
              }
              resp[k] = o;
            }
          }
          return JSON.stringify(resp);
        };
        const compileLanguage = (type = 'userjs_name') => {
          const resp = [];
          for (const obj of lngList) {
            for (const [k, v] of Object.entries(obj)) {
              if (v[type]) {
                if (isEmpty(v[type].message)) {
                  continue;
                }
                if (k.startsWith('en')) {
                  continue;
                }
                const t = type.toLowerCase().replace('userjs_', '');
                if (type === 'userjs_name') {
                  resp.push(
                    `// @${t}:${k.replace('_', '-')}      ${js_env ? '[Dev] ' : ''}${v[type].message}`
                  );
                } else {
                  resp.push(`// @${t}:${k.replace('_', '-')}      ${v[type].message}`);
                }
              }
            }
          }
          return resp;
        };
        const getData = () => {
          for (const [k, v] of Object.entries(userJS)) {
            if (typeof v !== 'string') {
              continue;
            }
            if (k === 'name') {
              addTo(
                k,
                `// @${k}         ${js_env ? '[Dev] ' : ''}${v}`,
                ...compileLanguage('userjs_name')
              );
            } else if (k === 'description') {
              addTo(k, `// @${k}  ${v}`, ...compileLanguage('userjs_description'));
            } else if (k === 'author') {
              addTo(k, `// @${k}       ${v}`);
            } else if (k === 'icon') {
              const buff = new Buffer.from(fs.readFileSync(v));
              const base64data = buff.toString('base64');
              if (v.endsWith('.png')) {
                addTo(k, `// @${k}         data:image/png;base64,${base64data}`);
              } else if (v.endsWith('.svg')) {
                addTo(k, `// @${k}         data:image/svg+xml;base64,${base64data}`);
              }
            } else if (k === 'downloadURL') {
              addTo(k, `// @downloadURL  ${v}`);
            } else if (k === 'updateURL') {
              addTo(k, `// @updateURL    ${v}`);
            } else if (k === 'url') {
              addTo(k, `// @downloadURL  ${v}`, `// @updateURL    ${v}`);
            } else if (k === 'version') {
              addTo(k, `// @${k}      ${js_env ? +new Date() : v}`);
            } else if (k === 'homepage') {
              addTo(k, `// @namespace    ${v}`, `// @homepageURL  ${v}`);
            } else if (k === 'bugs') {
              addTo(k, `// @supportURL   ${v}`);
            } else if (k === 'license') {
              addTo(k, `// @${k}      ${v}`);
            } else {
              addTo(k, v);
            }
          }
          for (const [k, v] of Object.entries(jsonData)) {
            if (typeof v !== 'string') {
              continue;
            }
            if (!filterData.includes(k)) {
              continue;
            }
            if (dataMap.has(k)) {
              continue;
            }
            if (k === 'license') {
              addTo(k, `// @${k}      ${v}`);
            }
          }
          if (userJS.metadata) {
            addTo('metaData', compileMetadata());
          }
          return [...dataMap.values()].flat().join('\n');
        };
        const userJSHeader = `// ==UserScript==\n${getData()}\n// ==/UserScript==`;
        const headerFile = await canAccess(build.source.head);
        const mainFile = await canAccess(build.source.body);
        const nanoCFG = {
          metadata: userJSHeader,
          languageList: transformLanguages(),
          code: mainFile
        };
        if (build.source.extras) {
          for (const [k, v] of Object.entries(build.source.extras)) {
            const extraFile = await canAccess(v);
            if (typeof extraFile === 'string') {
              nanoCFG[k] = extraFile;
            }
          }
        }
        const outFile = `${build.paths[dp].dir}/${build.paths[dp].fileName}.user.js`;
        await writeUserJS(outFile, nano(headerFile, nanoCFG));
        log('UserJS Build:', {
          path: outFile,
          time: toTime()
        });
        if (!js_env) {
          const outMeta = `${build.paths[dp].dir}/${build.paths[dp].fileName}.meta.js`;
          await writeUserJS(outMeta, nano(metaStr, { metadata: userJSHeader }));
          log('UserJS Metadata:', {
            path: outMeta,
            time: toTime()
          });
        }
      } catch (ex) {
        err(ex);
      }
    };
    const watcher = watch(build.watchDirs, {
      recursive: true,
      delay: 2000,
      filter: /\.(js|[s]css)$/
    });
    //#region Start Process
    log(`Node ENV: ${env.JS_ENV}`);

    if (js_env) {
      watcher.on('change', buildUserJS);
      watcher.on('error', (ex) => {
        err(ex);
        watcher.close();
      });
      watcher.on('ready', buildUserJS);
      return;
    }
    await buildUserJS();

    process.exit(0);
    //#endregion
  } catch (ex) {
    err(ex);
  }
})();

Web Proxy Viewer  |  New URL  |  Original Page