obsidian-cyber-utils
Version:
Utility functions for developing Obsidian plugins, focused on cyber security note-taking.
1,512 lines (1,492 loc) • 74.6 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var index_exports = {};
__export(index_exports, {
BaseOcrProcessor: () => BaseOcrProcessor,
CENSYS_SEARCH: () => CENSYS_SEARCH,
CodeListModal: () => CodeListModal,
CodeModal: () => CodeModal,
CyberPlugin: () => CyberPlugin,
DDG_SEARCH: () => DDG_SEARCH,
DOMAIN_EXCLUSIONS: () => DOMAIN_EXCLUSIONS,
DOMAIN_REGEX: () => DOMAIN_REGEX,
ErrorModal: () => ErrorModal,
FILE_REGEX: () => FILE_REGEX,
GOOGLE_SEARCH: () => GOOGLE_SEARCH,
GeminiClient: () => GeminiClient,
GeminiOcrProcessor: () => GeminiOcrProcessor,
HASH_REGEX: () => HASH_REGEX,
IPDB_SEARCH: () => IPDB_SEARCH,
IP_EXCLUSIONS: () => IP_EXCLUSIONS,
IP_REGEX: () => IP_REGEX,
IPv4_REGEX: () => IP_REGEX,
IPv6_REGEX: () => IPv6_REGEX,
IndicatorSource: () => IndicatorSource,
IndicatorType: () => IndicatorType,
InputModal: () => InputModal,
LOCAL_IP_REGEX: () => LOCAL_IP_REGEX,
MACRO_REGEX: () => MACRO_REGEX,
MD5_REGEX: () => MD5_REGEX,
Matcher: () => Matcher,
OcrProvider: () => OcrProvider,
OllamaClient: () => OllamaClient,
OpenAICompatibleClient: () => OpenAICompatibleClient,
PATTERN_KEYS: () => PATTERN_KEYS,
SHA1_REGEX: () => SHA1_REGEX,
SHA256_REGEX: () => SHA256_REGEX,
SHODAN_SEARCH: () => SHODAN_SEARCH,
SPUR_SEARCH: () => SPUR_SEARCH,
ScriptObject: () => ScriptObject,
TLD_URL: () => TLD_URL,
TesseractOcrProcessor: () => TesseractOcrProcessor,
URLSCAN_SEARCH: () => URLSCAN_SEARCH,
VT_SEARCH: () => VT_SEARCH,
addButtonContainer: () => addButtonContainer,
addButtonToContainer: () => addButtonToContainer,
addUniqueValuesToArray: () => addUniqueValuesToArray,
appendToEnd: () => appendToEnd,
censysSearch: () => censysSearch,
checkFolderExistsRecursive: () => checkFolderExistsRecursive,
constructMacroRegex: () => constructMacroRegex,
convertTime: () => convertTime,
createFolderIfNotExists: () => createFolderIfNotExists,
createNote: () => createNote,
datePickerSettingEl: () => datePickerSettingEl,
dateTimeRegex: () => dateTimeRegex,
ddgSearch: () => ddgSearch,
decryptString: () => decryptString,
defangDomain: () => defangDomain,
defangEmail: () => defangEmail,
defangIp: () => defangIp,
defaultSites: () => defaultSites,
encodeImageFile: () => encodeImageFile,
encryptString: () => encryptString,
extractMacros: () => extractMacros,
extractMatches: () => extractMatches,
filterExclusions: () => filterExclusions,
findFirstByRegex: () => findFirstByRegex,
friendlyDatetime: () => friendlyDatetime,
getActiveNoteContent: () => getActiveNoteContent,
getAttachmentFiles: () => getAttachmentFiles,
getAttachments: () => getAttachments,
getBacklinks: () => getBacklinks,
getIndicatorMatches: () => getIndicatorMatches,
getIocType: () => getIocType,
getMatches: () => getMatches,
getValidTld: () => getValidTld,
googleSearch: () => googleSearch,
groupIndicators: () => groupIndicators,
initializeWorker: () => initializeWorker,
ipdbSearch: () => ipdbSearch,
isLocalIpv4: () => isLocalIpv4,
loadPrivateData: () => loadPrivateData,
localDateTime: () => localDateTime,
lowerMd5: () => lowerMd5,
lowerSha256: () => lowerSha256,
macroSeparator: () => macroSeparator,
macroValue: () => macroValue,
noteAppend: () => noteAppend,
noteReplace: () => noteReplace,
ocr: () => ocr,
ocrMultiple: () => ocrMultiple,
openDetails: () => openDetails,
openNote: () => openNote,
parseCodeBlocks: () => parseCodeBlocks,
processExclusions: () => processExclusions,
readImageFile: () => readImageFile,
refangIoc: () => refangIoc,
removeArrayDuplicates: () => removeArrayDuplicates,
removeDotObsidian: () => removeDotObsidian,
removeElements: () => removeElements,
replaceMacros: () => replaceMacros,
replaceTemplateText: () => replaceTemplateText,
savePrivateData: () => savePrivateData,
shodanSearch: () => shodanSearch,
spurSearch: () => spurSearch,
supportedMacros: () => supportedMacros,
todayFolderStructure: () => todayFolderStructure,
todayLocalDate: () => todayLocalDate,
transformSelectedText: () => transformSelectedText,
urlscanSearch: () => urlscanSearch,
validateDomain: () => validateDomain,
validateDomains: () => validateDomains,
virusTotalRequest: () => virusTotalRequest,
vtSearch: () => vtSearch
});
module.exports = __toCommonJS(index_exports);
// src/cyberPlugin.ts
var import_obsidian3 = require("obsidian");
var import_events2 = require("events");
// src/vaultUtils.ts
var import_obsidian = require("obsidian");
async function checkFolderExistsRecursive(vault, folderName) {
async function searchFolder(rootPath) {
const checkVal = rootPath + "/" + folderName;
const folderExists = await vault.adapter.exists(checkVal, true);
if (folderExists) return folderName;
const subFolders = (await vault.adapter.list(rootPath)).folders;
const i = subFolders.indexOf(".obsidian");
if (i > -1) {
subFolders.splice(i, 1);
}
for (const subFolder of subFolders) {
const isSubFolder = await vault.adapter.exists(subFolder, true);
if (isSubFolder) {
const found = await searchFolder(subFolder);
if (found && !found.startsWith(subFolder)) {
return `${subFolder}/${found}`;
} else if (found) return found;
}
}
return "";
}
return await searchFolder("");
}
function removeDotObsidian(files) {
const removals = [".obsidian", ".DS_Store"];
removals.forEach((value) => {
const i = files.indexOf(value);
if (i > -1) {
files.splice(i, 1);
}
});
return files;
}
async function createFolderIfNotExists(vault, folderName) {
const folder = await checkFolderExistsRecursive(vault, folderName);
if (!folder) {
await vault.createFolder(folderName);
}
}
async function createNote(vault, folderName, noteTitle) {
return await vault.create(`${folderName}/${noteTitle}.md`, "");
}
function getBacklinks(notePath, app, resolved = false) {
let backlinks = null;
if (resolved) {
backlinks = app.metadataCache.resolvedLinks[notePath];
} else {
backlinks = app.metadataCache.unresolvedLinks[notePath];
}
const retval = [];
for (const i in backlinks) {
retval.push(i);
}
return retval;
}
function getAttachments(notePath, app) {
const links = getBacklinks(notePath, app, true);
const attachments = /* @__PURE__ */ new Set();
links.forEach((link) => {
const file = app.vault.getAbstractFileByPath(link);
if (file && file instanceof import_obsidian.TFile && file.extension !== "md") {
attachments.add(file);
}
});
return Array.from(attachments);
}
function getAttachmentFiles(note, app) {
const links = getBacklinks(note.path, app, true);
const attachments = /* @__PURE__ */ new Set();
links.forEach((link) => {
const file = app.vault.getAbstractFileByPath(link);
if (file && file instanceof import_obsidian.TFile && file.extension !== "md") {
attachments.add(file);
}
});
return Array.from(attachments);
}
function noteAppend(vault, note, content) {
return vault.process(note, (data) => {
return data + content;
});
}
function noteReplace(vault, note, regex, content) {
return vault.process(note, (data) => {
return data.replace(regex, content);
});
}
function openNote(app, note) {
if (!note || !app) return;
const view = app.workspace.getLeaf();
view.openFile(note);
}
// src/regex.ts
var ipv4Octet = "(?:25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])";
var ipv6Octet = "[0-9a-fA-F]{1,4}";
var IP_REGEX = new RegExp(
// match a possibly url-encoded character preceding, or
// a word boundary
String.raw`(?:%[0-9a-fA-F]{2})?(?=\b|^)(` + `(?:${ipv4Octet + possiblyDefangedVal(String.raw`\.`)}){3}` + ipv4Octet + ")",
"g"
// flags
);
var IPv6_REGEX = new RegExp(
`((?:${ipv6Octet}${possiblyDefangedVal(":")}){7}${ipv6Octet}|(?:(?:${ipv6Octet}${possiblyDefangedVal(":")})*${ipv6Octet})?${possiblyDefangedVal("::")}(?:(?:${ipv6Octet}${possiblyDefangedVal(":")})*${ipv6Octet})?)`,
// zero or more segments
"gi"
// flags
);
var LOCAL_IP_REGEX = /^((127\.)|(10\.)|(172\.1[6-9]\.)|(172\.2[0-9]\.)|(172\.3[0-1]\.)|(192\.168\.))/g;
var MACRO_REGEX = /({{([^}]+)}})/g;
var DOMAIN_REGEX = new RegExp(
String.raw`(?:%[0-9a-f]{2})?((?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?` + possiblyDefangedVal(String.raw`\.`) + `)+` + String.raw`[a-z][a-z0-9-]{0,61}[a-z](?=\.?)\b)`,
"gi"
// flags
);
var hash_start = "(?:%[0-9a-f]{2})?(?<=^|[^a-f0-9]+)";
var hash_end = "(?=$|[^a-f0-9]+)";
var HASH_REGEX = new RegExp(
hash_start + "([a-f0-9]{64}|[a-f0-9]{40}|[a-f0-9]{32})" + // standard hash length (SHA256, MD5, SHA1)
hash_end,
"gi"
// flags
);
var SHA256_REGEX = new RegExp(
hash_start + "([a-f0-9]{64})" + // SHA256 hash length
hash_end,
"gi"
// flags
);
var MD5_REGEX = new RegExp(
hash_start + "([a-f0-9]{32})" + // SHA256 hash length
hash_end,
"gi"
// flags
);
var SHA1_REGEX = new RegExp(
hash_start + "([a-f0-9]{40})" + // SHA256 hash length
hash_end,
"gi"
// flags
);
var FILE_REGEX = new RegExp(
String.raw`(?:%[0-9a-f]{2})?(?<=^|\s|")` + // beginning of string, space, or open quote
"(" + String.raw`(?:\w:\\|[\\/])` + // drive letter or leading slash
String.raw`(?:[^\\/:][\\/]?)+` + //
String.raw`[^\\/\n"|]+\.\w+` + // filename with extension
")",
"gi"
);
function possiblyDefangedVal(val) {
return String.raw`[\[\(\\]?${val}[\]\)]?`;
}
// src/iocParser.ts
var IndicatorType = /* @__PURE__ */ ((IndicatorType2) => {
IndicatorType2["IPv4"] = "ipv4";
IndicatorType2["PRIVATE_IPv4"] = "private_ipv4";
IndicatorType2["IPv6"] = "ipv6";
IndicatorType2["PRIVATE_IPv6"] = "private_ipv6";
IndicatorType2["DOMAIN"] = "domain";
IndicatorType2["HASH"] = "hash";
IndicatorType2["EMAIL"] = "email";
return IndicatorType2;
})(IndicatorType || {});
var IndicatorSource = /* @__PURE__ */ ((IndicatorSource2) => {
IndicatorSource2["TEXT"] = "text";
IndicatorSource2["OCR"] = "ocr";
return IndicatorSource2;
})(IndicatorSource || {});
function filterExclusions(items, exclusions) {
return items.filter(
(item) => !exclusions.some(
(exclusion) => typeof exclusion === "string" ? item === exclusion : exclusion.test(item)
)
);
}
function groupIndicators(indicators) {
const grouped = {
["text" /* TEXT */]: {
["ipv4" /* IPv4 */]: [],
["private_ipv4" /* PRIVATE_IPv4 */]: [],
["ipv6" /* IPv6 */]: [],
["private_ipv6" /* PRIVATE_IPv6 */]: [],
["domain" /* DOMAIN */]: [],
["hash" /* HASH */]: [],
["email" /* EMAIL */]: []
},
["ocr" /* OCR */]: {
["ipv4" /* IPv4 */]: [],
["private_ipv4" /* PRIVATE_IPv4 */]: [],
["ipv6" /* IPv6 */]: [],
["private_ipv6" /* PRIVATE_IPv6 */]: [],
["domain" /* DOMAIN */]: [],
["hash" /* HASH */]: [],
["email" /* EMAIL */]: []
}
};
const seenIndicators = /* @__PURE__ */ new Set();
for (const indicator of indicators) {
const source = indicator.source || "text" /* TEXT */;
const uniqueKey = `${source}:${indicator.value}`;
if (seenIndicators.has(uniqueKey)) {
continue;
}
seenIndicators.add(uniqueKey);
grouped[source][indicator.type].push(indicator);
}
return grouped;
}
// src/textUtils.ts
var import_obsidian2 = require("obsidian");
var TLD_URL = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt";
async function getValidTld() {
const tldParams = { url: "https://data.iana.org/TLD/tlds-alpha-by-domain.txt", throw: true };
try {
const data = await (0, import_obsidian2.request)(tldParams);
const tlds = data.split("\n");
if (tlds[0].startsWith("#")) tlds.shift();
if (!tlds.slice(-1)[0]) tlds.pop();
return tlds;
} catch (e) {
console.error("failed to get valid TLDs");
console.error(e);
return null;
}
}
function todayLocalDate() {
const tzoffset = (/* @__PURE__ */ new Date()).getTimezoneOffset() * 6e4;
const date = new Date(Date.now() - tzoffset).toISOString().slice(0, 10);
return date;
}
function localDateTime() {
return `${todayLocalDate()} ${new Date(Date.now()).toString().slice(16, 21)}`;
}
function todayFolderStructure(prefs) {
const date = todayLocalDate();
const year = date.slice(0, 4);
const month = Number(date.slice(5, 7));
const yearMonth = date.slice(0, 7);
const currentQuarter = Math.floor((month + 2) / 3);
const folderArray = [];
if (prefs.year) folderArray.push(year);
if (prefs.quarter) folderArray.push(`${year}-Q${currentQuarter}`);
if (prefs.month) folderArray.push(yearMonth);
if (prefs.day) folderArray.push(date);
return folderArray;
}
function defangIp(text) {
return text.replaceAll(/(\d{1,3}\.\d{1,3}\.\d{1,3})\.(\d{1,3})/g, "$1[.]$2");
}
function defangDomain(text) {
const httpString = /http(s?):\/\//gi;
const anyDomain = /(([\w-]\.?)+)\.((xn--)?([a-z][a-z0-9-]{1,60}|[a-z][a-z0-9-]{1,29}\.[a-z]{2,}))/gi;
let retval = text.replaceAll(httpString, "hxxp$1[://]");
retval = retval.replaceAll(anyDomain, "$1[.]$3");
return retval;
}
function defangEmail(text) {
const emailString = /([^\s]+)@([^\s]+)\.([^\s]+)/gi;
const retval = text.replaceAll(emailString, "$1[@]$2[.]$3");
return retval;
}
function refangIoc(text) {
let retval = text.replaceAll("[.]", ".");
retval = retval.replaceAll("(.)", ".");
retval = retval.replaceAll(String.raw`\.`, ".");
retval = retval.replaceAll("[/]", "/");
retval = retval.replaceAll("[//]", "/");
retval = retval.replaceAll("[@]", "@");
retval = retval.replaceAll("[at]", "@");
retval = retval.replaceAll("hxxp", "http");
retval = retval.replaceAll("[:]", ":");
retval = retval.replaceAll("[::]", "::");
retval = retval.replaceAll("[://]", "://");
retval = retval.toLowerCase();
return retval;
}
function lowerSha256(hash) {
return hash.replace(/([0-9a-fA-F]{64})/g, function(match) {
return match.toLowerCase();
});
}
function lowerMd5(text) {
return text.replace(/([0-9a-fA-F]{32})/g, function(match) {
return match.toLowerCase();
});
}
var dateTimeRegex = /(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}\s+UTC)/g;
function friendlyDatetime(text) {
return text.replace(dateTimeRegex, "$1 at $2");
}
function findFirstByRegex(text, regex) {
const result = regex.exec(text);
if (!result) {
return null;
} else {
return result[1];
}
}
function replaceTemplateText(template, content, note, contentMacro = "{{content}}") {
let template_replaced = template.replaceAll("{{title}}", note.name.slice(0, -3));
const dateTime = localDateTime().split(" ");
template_replaced = template_replaced.replaceAll("{{date}}", dateTime[0]);
template_replaced = template_replaced.replaceAll("{{time}}", dateTime[1]);
template_replaced = template_replaced.replaceAll(contentMacro, content);
return template_replaced;
}
function extractMacros(text) {
const regexTest = new RegExp(MACRO_REGEX.source, MACRO_REGEX.flags);
const matches = text.matchAll(regexTest);
return addUniqueValuesToArray([], matches);
}
function extractMatches(text, pattern) {
if (Array.isArray(pattern)) {
const matches = [];
pattern.forEach((value) => {
addUniqueValuesToArray(matches, text.matchAll(value));
});
return matches;
} else {
const matches = text.matchAll(pattern);
return addUniqueValuesToArray([], matches);
}
}
function replaceMacros(text, replacements) {
let retval = text;
replacements.forEach((value, key) => {
retval = retval.replaceAll(key, value);
});
return retval;
}
function addUniqueValuesToArray(array, values) {
const valueArray = [...values];
valueArray.forEach((match) => {
if (!array.includes(match[1])) {
array.push(match[1]);
}
});
return array;
}
function parseCodeBlocks(content) {
const retval = /* @__PURE__ */ new Map();
const codeBlockRegex = /#+\s+(.+)$\n+```([\w-_\s]*)\n(((?!^```\n).|\n)*)\n^```$/gm;
const matches = content.matchAll(codeBlockRegex);
const matchArray = [...matches];
matchArray.forEach((match) => {
if (!retval.has(match[1])) {
const code = {
content: match[3],
lang: match[2]
};
retval.set(match[1], code);
}
});
return retval;
}
var macroSeparator = "(?:\\s*[:=]\\s*|\\s+)";
var macroValue = "(((?:[^}\\s]*\\w[^}\\s]*)+))";
function constructMacroRegex(macroRegex) {
if (macroRegex instanceof RegExp) macroRegex = macroRegex.source;
const retval = new RegExp(macroRegex + macroSeparator + macroValue, "gi");
return retval;
}
function validateDomain(domain, validTld) {
let tld = domain.split(".").pop()?.toUpperCase();
if (tld && validTld.includes(tld)) return true;
tld = domain.split("[.]").pop()?.toUpperCase();
if (tld && validTld.includes(tld)) return true;
return false;
}
function removeArrayDuplicates(array) {
return array.filter((item, index) => {
return array.indexOf(item) === index;
});
}
function convertTime(timeString) {
return Date.parse(timeString);
}
function validateDomains(domains, validTld) {
let index = domains.length - 1;
while (index >= 0) {
const domain = domains[index];
if (!validateDomain(domain, validTld)) {
domains.splice(index, 1);
}
index -= 1;
}
return domains;
}
function isLocalIpv4(ip) {
const localIpTest = new RegExp(LOCAL_IP_REGEX.source, LOCAL_IP_REGEX.flags);
if (localIpTest.exec(ip)) return true;
else return false;
}
function getIocType(val) {
val = val.trim().toLowerCase();
const ipTest = new RegExp(IP_REGEX.source, IP_REGEX.flags);
if (ipTest.exec(val)) return "ip";
const ipv6Test = new RegExp(IPv6_REGEX.source, IPv6_REGEX.flags);
if (ipv6Test.exec(val)) return "ip";
const domainTest = new RegExp(DOMAIN_REGEX.source, DOMAIN_REGEX.flags);
if (domainTest.exec(val)) return "domain";
const hashTest = new RegExp(HASH_REGEX.source, HASH_REGEX.flags);
if (hashTest.exec(val)) return "hash";
return null;
}
// src/matcher.ts
var PATTERN_KEYS = ["IPv6", "IP", "IPv4", "LocalIP", "Domain", "SHA256", "MD5", "SHA1", "File"];
var Matcher = class _Matcher {
static Patterns = {
IPv6: IPv6_REGEX.source,
IPv4: IP_REGEX.source,
IP: IP_REGEX.source,
LocalIP: LOCAL_IP_REGEX.source,
LocalIPv4: LOCAL_IP_REGEX.source,
Domain: DOMAIN_REGEX.source,
SHA256: SHA256_REGEX.source,
MD5: MD5_REGEX.source,
SHA1: SHA1_REGEX.source,
File: FILE_REGEX.source,
Macro: MACRO_REGEX.source
};
static getAvailablePatterns() {
return PATTERN_KEYS;
}
static getExactMatcher(pattern) {
return new RegExp(`^${_Matcher.Patterns[pattern]}$`, "i");
}
static getGlobalMatcher(pattern) {
return new RegExp(_Matcher.Patterns[pattern], "gi");
}
static findAll(text, pattern) {
return Array.from(text.matchAll(this.getGlobalMatcher(pattern)), (m) => m[0]);
}
static isMatch(text, pattern) {
return this.getExactMatcher(pattern).test(text);
}
static findFirst(text, pattern) {
const match = text.match(_Matcher.Patterns[pattern]);
return match ? match[0] : null;
}
};
function getMatches(fileContent) {
if (!fileContent) return [];
const ips = {
title: "IPs",
items: Matcher.findAll(fileContent, "IPv4")
};
const domains = {
title: "Domains",
items: Matcher.findAll(fileContent, "Domain")
};
const hashes = {
title: "Hashes",
items: Matcher.findAll(fileContent, "SHA256")
};
const privateIps = {
title: "IPs (Private)",
items: []
};
const ipv6 = {
title: "IPv6",
items: Matcher.findAll(fileContent, "IPv6")
};
ips.title = "IPs (Public)";
for (let i = 0; i < ips.items.length; i++) {
const item = ips.items[i];
if (isLocalIpv4(item)) {
ips.items.splice(i, 1);
i--;
privateIps.items.push(item);
}
}
const retval = [ips, privateIps, domains, hashes, ipv6];
retval.forEach((iocList, index, array) => {
const refangedItems = iocList.items.map((x) => refangIoc(x));
iocList.items = [...new Set(refangedItems)];
array[index] = iocList;
});
return retval;
}
function getIndicatorMatches(fileContent, source = "text" /* TEXT */, metadata) {
const indicators = [];
if (!fileContent) return indicators;
const ipv4Addresses = Matcher.findAll(fileContent, "IPv4");
for (const ip of ipv4Addresses) {
const refangedIp = refangIoc(ip);
if (isLocalIpv4(refangedIp)) {
indicators.push({
value: refangedIp,
type: "private_ipv4" /* PRIVATE_IPv4 */,
source,
metadata
});
} else {
indicators.push({
value: refangedIp,
type: "ipv4" /* IPv4 */,
source,
metadata
});
}
}
const ipv6Addresses = Matcher.findAll(fileContent, "IPv6");
for (const ip of ipv6Addresses) {
indicators.push({
value: refangIoc(ip),
type: "ipv6" /* IPv6 */,
source,
metadata
});
}
const domains = Matcher.findAll(fileContent, "Domain");
for (const domain of domains) {
indicators.push({
value: refangIoc(domain),
type: "domain" /* DOMAIN */,
source,
metadata
});
}
const sha256Hashes = Matcher.findAll(fileContent, "SHA256");
for (const hash of sha256Hashes) {
indicators.push({
value: hash,
type: "hash" /* HASH */,
source,
metadata
});
}
return indicators;
}
function processExclusions(iocs, plugin) {
if (!iocs || !plugin) return iocs;
const exclusions = plugin.getIocExclusions();
return iocs.map((indicatorList) => {
const processed = { ...indicatorList };
switch (processed.title) {
case "IPs":
case "IPs (Public)":
case "IPs (Private)":
processed.exclusions = exclusions.ipv4Exclusions || [];
break;
case "IPv6":
processed.exclusions = exclusions.ipv6Exclusions || [];
break;
case "Domains":
processed.exclusions = exclusions.domainExclusions || [];
break;
case "Hashes":
processed.exclusions = exclusions.hashExclusions || [];
break;
default:
processed.exclusions = [];
break;
}
if (processed.exclusions && processed.exclusions.length > 0) {
processed.items = filterExclusions(processed.items, processed.exclusions);
}
return processed;
});
}
// src/ocr.ts
var import_tesseract = require("tesseract.js");
// src/ocr/utils.ts
async function readImageFile(app, file) {
const arrBuff = await app.vault.readBinary(file);
const buffer = Buffer.from(arrBuff);
return buffer;
}
async function encodeImageFile(app, file) {
return (await readImageFile(app, file)).toString("base64");
}
// src/ocr.ts
var OcrQueue = class {
worker;
queue;
processing;
processingLock;
constructor(worker) {
this.worker = worker;
this.queue = [];
this.processing = false;
this.processingLock = null;
}
async addToQueue(image) {
return new Promise((resolve, reject) => {
this.queue.push({ image, resolve, reject });
if (!this.processing) {
this.processQueue();
}
});
}
async processQueue() {
if (this.processing) {
return;
}
this.processing = true;
while (this.queue.length > 0) {
const queueItem = this.queue[0];
if (!queueItem) continue;
const { image, resolve, reject } = queueItem;
try {
const { data: { text } } = await this.worker.recognize(image);
resolve(text);
this.queue.shift();
} catch (error) {
reject(error);
this.queue.shift();
}
}
this.processing = false;
}
};
async function ocr(app, file, worker) {
if (!worker) {
console.error("OCR worker is not initialized");
return null;
}
if (!file) return null;
const buffer = await readImageFile(app, file);
if (!buffer) return null;
const ret = await worker.recognize(buffer);
return ret.data.text;
}
async function initializeWorker() {
const worker = await (0, import_tesseract.createWorker)();
await worker.loadLanguage("eng");
await worker.initialize("eng");
return worker;
}
async function ocrMultiple(app, files, worker) {
const resultsMap = /* @__PURE__ */ new Map();
if (!worker) {
console.error("No worker to complete OCR.");
return null;
}
if (!files) {
console.error("No files passed to OCR function");
return null;
}
const ocrQueue = new OcrQueue(worker);
for (let file of files) {
if (typeof file === "string") {
resultsMap.set(file, "");
const fileObj = app.vault.getFileByPath(file);
if (fileObj) file = fileObj;
else {
console.error(`couldn't find file ${file}`);
continue;
}
;
} else {
resultsMap.set(file.path, "");
}
const arrBuff = await readImageFile(app, file);
if (!arrBuff) {
console.error(`failed to convert ${file.path} to buffer for OCR`);
continue;
}
const buffer = Buffer.from(arrBuff);
try {
const text = await ocrQueue.addToQueue(buffer);
resultsMap.set(file.path, text);
} catch (error) {
let message = error instanceof Error ? error.message : String(error);
console.error(message);
resultsMap.set(file.path, message);
}
}
return resultsMap;
}
// src/ocr/ocrProvider.ts
var import_events = require("events");
var OcrProvider = class {
emitter;
activeJobs = /* @__PURE__ */ new Set();
isDebugging = false;
processors;
// Shared emitter instance for communication between processors and this provider
processorEventEmitter;
cacheChecker;
constructor(processors, cacheChecker, enableDebug = false) {
this.processors = processors;
this.isDebugging = enableDebug;
this.emitter = new import_events.EventEmitter();
this.processorEventEmitter = new import_events.EventEmitter();
this.cacheChecker = cacheChecker;
this.processors.forEach((processor) => {
if (typeof processor.setEventEmitter === "function") {
processor.setEventEmitter(this.processorEventEmitter);
}
});
this.setupEventListeners();
}
setupEventListeners() {
this.processorEventEmitter.on("ocr-progress", (payload) => {
this.emitter.emit("ocr-progress", payload);
});
this.processorEventEmitter.on("ocr-complete", (payload) => {
const jobId = this.getJobId(payload.fileId, payload.processorId);
this.activeJobs.delete(jobId);
this.debug(`Completed ${jobId}. Active jobs: ${this.activeJobs.size}`);
this.emitter.emit("ocr-complete", payload);
});
this.processorEventEmitter.on("ocr-error", (payload) => {
const jobId = this.getJobId(payload.fileId, payload.processorId);
this.activeJobs.delete(jobId);
this.debug(`Errored ${jobId}. Active jobs: ${this.activeJobs.size}`);
this.emitter.emit("ocr-error", payload);
});
}
async processAttachments(parentNoteFileId, attachments) {
this.debug(`Starting processing for ${attachments.length} attachments for note '${parentNoteFileId}'.`);
const allProcessingPromises = [];
for (const attachmentJob of attachments) {
this.debug(`Processing attachment: ${attachmentJob.fileId}`);
for (const processor of this.processors) {
const jobId = this.getJobId(attachmentJob.fileId, processor.id);
if (this.cacheChecker(attachmentJob.fileId, processor.id)) {
this.debug(`Skipping ${jobId} due to cache hit.`);
continue;
}
if (this.activeJobs.has(jobId)) {
this.debug(`Skipping ${jobId} - already active/queued.`);
continue;
}
this.activeJobs.add(jobId);
this.debug(`Queuing ${jobId}. Active jobs: ${this.activeJobs.size}`);
this.processorEventEmitter.emit("ocr-progress", {
fileId: attachmentJob.fileId,
processorId: processor.id,
status: "queued",
message: `Queued for ${processor.id}`
});
allProcessingPromises.push(
processor.processImage(attachmentJob).catch((err) => {
console.error(`Error starting ${processor.id} for ${attachmentJob.fileId}:`, err);
this.processorEventEmitter.emit("ocr-error", {
fileId: attachmentJob.fileId,
processorId: processor.id,
error: `Failed to initiate processing: ${err.message}`,
canRetry: false
});
})
);
this.debug(`queued ${allProcessingPromises.length}`);
}
}
if (this.activeJobs.size > 0) {
this.debug(`Finished initiating all jobs for note ${parentNoteFileId}. Active jobs: ${this.activeJobs.size}. Waiting for results via events.`);
} else {
this.debug(`No active jobs for ${parentNoteFileId}.`);
}
}
addProcessor(processor) {
this.processors.push(processor);
if (typeof processor.setEventEmitter === "function") {
processor.setEventEmitter(this.processorEventEmitter);
}
}
setDebugging(enabled) {
const changed = this.isDebugging !== enabled;
this.isDebugging = enabled;
if (changed) {
this.debug(`Debugging ${enabled ? "enabled" : "disabled"}`);
}
}
debug(...args) {
if (this.isDebugging) {
console.log(`[OcrProvider]`, ...args);
}
}
getJobId(fileId, processorId) {
return `${fileId}-${processorId}`;
}
};
// src/ocr/baseProcessor.ts
var BaseOcrProcessor = class {
id;
isDebugging = false;
emitter = null;
jobQueue = [];
activeTasks = 0;
maxConcurrency;
constructor(id, maxConcurrency = 4, enableDebug = false) {
this.id = id;
this.maxConcurrency = Math.max(1, maxConcurrency);
this.isDebugging = enableDebug;
}
setEventEmitter(emitter) {
this.emitter = emitter;
this.debug(`Event emitter set.`);
}
async processImage(job) {
if (!this.emitter) {
console.error(`[${this.id}] Cannot queue job for ${job.fileId} - event emitter not configured.`);
throw new Error(`[${this.id}] Event emitter not configured.`);
}
this.debug(`Queuing job for ${job.fileId}`);
this.jobQueue.push(job);
this.emitProgress(job.fileId, "queued", void 0, `Queued (${this.jobQueue.length} in queue)`);
this._tryProcessNext();
}
_tryProcessNext() {
if (!this.emitter) {
console.warn(`[${this.id}] Cannot process queue, emitter not set.`);
return;
}
while (this.activeTasks < this.maxConcurrency && this.jobQueue.length > 0) {
const job = this.jobQueue.shift();
if (!job) continue;
this.activeTasks++;
this.debug(`Starting task for ${job.fileId}. Active: ${this.activeTasks}/${this.maxConcurrency}`);
this.emitProgress(job.fileId, "processing", 0, "Starting OCR task");
(async () => {
try {
const extractedText = await this._performOcr(job);
this.debug(`Successfully processed ${job.fileId}`);
this.emitter?.emit("ocr-complete", {
fileId: job.fileId,
processorId: this.id,
extractedText: extractedText ?? ""
});
} catch (error) {
console.error(`[${this.id}] Error processing ${job.fileId}`, error);
this.emitter?.emit("ocr-error", {
fileId: job.fileId,
processorId: this.id,
error: error.message || "OCR processing failed",
canRetry: false
});
} finally {
this.activeTasks--;
this.debug(`Finished task for ${job.fileId}. Active: ${this.activeTasks}/${this.maxConcurrency}`);
this._tryProcessNext();
}
})();
}
}
emitProgress(fileId, status, progressPercent, message) {
if (this.emitter) {
this.emitter.emit("ocr-progress", {
fileId,
processorId: this.id,
status,
progressPercent,
message
});
} else {
console.warn(`[${this.id}] Cannot emit progress for ${fileId}, emitter not initialized.`);
}
}
setDebugging(enabled) {
const changed = this.isDebugging !== enabled;
this.isDebugging = enabled;
if (changed) {
this.debug(`Debugging ${enabled ? "enabled" : "disabled"}.`);
}
}
debug(...args) {
if (this.isDebugging) {
console.log(`[${this.id}]`, ...args);
}
}
async shutdown() {
this.debug(`Base shutdown called.`);
}
};
// src/ocr/tesseractProcessor.ts
var TesseractOcrProcessor = class extends BaseOcrProcessor {
worker;
constructor(worker, enableDebug = false) {
super("tesseract", 1, enableDebug);
this.worker = worker;
this.debug(`[${this.id}] Processor initialized (waiting for emitter).`);
}
async _performOcr(job) {
const { fileId, imageData } = job;
this.debug(`[${this.id}] Worker starting recognize for ${fileId}`);
const result = await this.worker.recognize(imageData);
return result.data.text;
}
async shutdown() {
await super.shutdown();
if (this.worker) {
this.debug(`[${this.id}] Terminating tesseract worker.`);
await this.worker.terminate();
}
}
};
// src/cyberPlugin.ts
var CyberPlugin = class extends import_obsidian3.Plugin {
isDebugging = false;
settings;
validTld;
sidebarContainers;
emitter;
indicators;
ocrIndicators = null;
ocrCache = /* @__PURE__ */ new Map();
exclusions;
fileOpenRef = null;
fileModifyRef = null;
vaultCacheRef = null;
activeFile = null;
activeFileContent = null;
activeFileAttachments = null;
ocrProvider = null;
ocrProcessingEmitter;
uiNotifier;
ocrProgressRef;
ocrCompleteRef;
ocrErrorRef;
worker = null;
// private fileProgressRef: () => void;
// private fileCompleteRef: () => void;
constructor(app, manifest, enableDebug = false) {
super(app, manifest);
this.isDebugging = enableDebug;
this.emitter = new import_events2.EventEmitter();
this.indicators = [];
this.exclusions = {
ipv4Exclusions: [],
ipv6Exclusions: [],
hashExclusions: [],
domainExclusions: []
};
this.uiNotifier = new import_events2.EventEmitter();
this.ocrProcessingEmitter = new import_events2.EventEmitter();
const cacheChecker = this.hasCachedOcrResult.bind(this);
this.ocrProvider = new OcrProvider([], cacheChecker);
this.registerObsidianListeners();
}
async onload() {
if (!this.ocrProvider) {
await this.initializeOcrSystem();
}
this.worker = await initializeWorker();
this.ocrProvider?.addProcessor(new TesseractOcrProcessor(this.worker));
this.setupOcrEventListeners();
}
async refreshIndicators() {
if (this.activeFile && this.activeFileContent) {
this.indicators = getIndicatorMatches(this.activeFileContent, "text" /* TEXT */);
this.debug(`Refreshed ${this.indicators.length} indicators from Markdown text.`);
} else {
this.indicators = [];
}
const collectedOcrIndicators = [];
if (this.activeFileAttachments && this.activeFileAttachments.length > 0) {
this.debug(`Checking OCR cache for ${this.activeFileAttachments.length} attachments.`);
for (const att of this.activeFileAttachments) {
const fileCache = this.ocrCache.get(att.path);
if (fileCache) {
for (const ind of fileCache.values()) {
collectedOcrIndicators.push(...ind);
}
this.debug(`Found cached OCR indicators for ${att.path}`);
} else {
this.debug(`No cached OCR indicators for ${att.path}`);
}
}
}
this.ocrIndicators = collectedOcrIndicators;
this.emitter.emit("indicators-changed");
}
getFileContent() {
return this.activeFileContent;
}
getFileAttachments() {
return this.activeFileAttachments ?? [];
}
getIocExclusions() {
return this.exclusions;
}
/**
* Compare attachments for the current file against the plugin's attachment list.
* @param file the file to evaluate
* @returns true if attachments are unchanged, false if attachments have changed
*/
compareAttachments(file) {
const currentAttachments = getAttachments(file.path, this.app);
const existingAttachments = this.activeFileAttachments ?? [];
const currentPaths = new Set(currentAttachments.map((f) => f.path));
const existingPaths = new Set(existingAttachments.map((f) => f.path));
const unchanged = currentPaths.size === existingPaths.size && [...currentPaths].every((path) => existingPaths.has(path));
if (!unchanged) {
this.debug(`Attachments changed for ${file.path}. New count: ${currentPaths.size}, Old count: ${existingPaths.size}`);
this.activeFileAttachments = currentAttachments;
this.emitter.emit("attachments-changed");
}
return unchanged;
}
registerObsidianListeners() {
this.fileOpenRef = this.app.workspace.on("file-open", this.handleFileOpen.bind(this));
this.fileModifyRef = this.app.vault.on("modify", this.handleFileModify.bind(this));
this.vaultCacheRef = this.app.metadataCache.on("resolve", this.handleMetadataResolve.bind(this));
}
async handleFileOpen(file) {
if (file && file instanceof import_obsidian3.TFile) {
this.activeFile = file;
this.activeFileContent = await this.app.vault.cachedRead(file);
this.compareAttachments(file);
this.emitter.emit("file-opened");
await this.refreshIndicators();
this.triggerOcrProcessing();
} else {
this.activeFile = null;
this.activeFileContent = null;
this.activeFileAttachments = null;
this.indicators = [];
this.ocrIndicators = [];
await this.refreshIndicators();
this.debug("Active file closed or is not a TFile, indicators cleared.");
}
}
async handleFileModify(file) {
if (file === this.activeFile && file instanceof import_obsidian3.TFile) {
this.activeFileContent = await this.app.vault.cachedRead(file);
const attachmentsChanged = this.compareAttachments(file);
this.emitter.emit("file-modified");
await this.refreshIndicators();
if (attachmentsChanged) {
this.debug("Attachments changed during file modify, re-triggering OCR processing.");
this.triggerOcrProcessing();
}
}
}
async handleMetadataResolve(file) {
if (file === this.activeFile) {
const attachmentsChanged = this.compareAttachments(file);
await this.refreshIndicators();
if (attachmentsChanged) {
this.debug("Attachments changed during metadata resolve, re-triggering OCR processing.");
this.triggerOcrProcessing();
}
}
}
async triggerOcrProcessing() {
if (!this.ocrProvider || !this.activeFileAttachments || this.activeFileAttachments.length === 0) {
return;
}
this.debug(`Triggering OCR for ${this.activeFileAttachments.length} new attachment(s)...`);
const ocrJobs = [];
for (const att of this.activeFileAttachments) {
try {
const content = await readImageFile(this.app, att);
ocrJobs.push({
fileId: att.path,
imageData: content
});
this.debug(`Added Job for ${att.path}`);
} catch (error) {
console.error(`Failed to read or encode attachment ${att.path}`, error);
this.handleOcrError({
fileId: att.path,
processorId: "plugin",
error: `Failed to read file: ${error}`,
canRetry: false
});
}
}
if (ocrJobs.length > 0) {
this.ocrProvider.processAttachments(this.activeFile?.path ?? "unknown", ocrJobs).catch((error) => {
console.error(`Error occurred during OCR Provider processAttachments call:`, error);
});
}
}
/**
* Add search sites to a set of ParsedIndicators.
*/
applySearchSites(indicators) {
if (!this.settings?.searchSites) return indicators;
return indicators.map((indicator) => {
const indicatorCopy = { ...indicator };
switch (indicator.title) {
case "IPs (Public)":
case "IPs (Private)":
case "IPv6":
indicatorCopy.sites = this.settings?.searchSites.filter(
(x) => x.enabled && x.ip
);
break;
case "Domains":
indicatorCopy.sites = this.settings?.searchSites.filter(
(x) => x.enabled && x.domain
);
break;
case "Hashes":
indicatorCopy.sites = this.settings?.searchSites.filter(
(x) => x.enabled && x.hash
);
default:
indicatorCopy.sites = [];
}
return indicatorCopy;
});
}
/**
* Validate that domains end with a valid TLD
*/
validateDomains(indicators) {
if (!this.validTld) return indicators;
return indicators.map((indicator) => {
const indicatorCopy = { ...indicator };
if (this.validTld && indicator.title === "Domain" && indicator.items.length > 0) {
indicatorCopy.items = validateDomains(indicator.items, this.validTld);
}
return indicatorCopy;
});
}
processExclusions(indicators) {
return indicators.map((iocs) => {
const processed = { ...iocs };
switch (processed.title) {
case "IPs":
case "IPs (Public)":
case "IPs (Private)":
processed.exclusions = this.exclusions.ipv4Exclusions;
break;
case "IPv6":
processed.exclusions = this.exclusions.ipv6Exclusions;
break;
case "Domains":
processed.exclusions = this.exclusions.domainExclusions;
break;
case "Hashes":
processed.exclusions = this.exclusions.hashExclusions;
break;
default:
processed.exclusions = [];
break;
}
if (processed.exclusions.length > 0) {
processed.items = filterExclusions(processed.items, processed.exclusions);
}
return processed;
});
}
async initializeOcrSystem() {
const processors = [];
if (processors.length === 0) {
console.warn("No OCR processors were successfully initialized.");
return;
}
const cacheChecker = this.hasCachedOcrResult.bind(this);
this.ocrProvider = new OcrProvider(processors, cacheChecker);
this.debug(`OCR Provider initialized with ${processors.length} processors`);
}
hasCachedOcrResult(fileId, processorId) {
const hasResult = this.ocrCache.get(fileId)?.has(processorId) ?? false;
if (hasResult) {
this.debug(`[Cache Check] Cache hit for ${fileId} using ${processorId}.`);
}
return hasResult;
}
setupOcrEventListeners() {
if (!this.ocrProvider) return;
this.ocrCompleteRef = this.handleOcrComplete.bind(this);
this.ocrErrorRef = this.handleOcrError.bind(this);
this.ocrProgressRef = this.handleOcrProgress.bind(this);
this.ocrProvider.emitter.on("ocr-complete", this.ocrCompleteRef);
this.ocrProvider.emitter.on("ocr-error", this.ocrErrorRef);
this.ocrProvider.emitter.on("ocr-progress", this.ocrProgressRef);
}
async handleOcrComplete(payload) {
this.debug(`OCR Complete: ${payload.fileId} by ${payload.processorId}`);
const isRelevantAttachment = this.activeFileAttachments?.some((att) => att.path === payload.fileId);
if (!isRelevantAttachment) {
this.debug(`Received OCR result for ${payload.fileId} which is not an attachment of the active file`);
}
const indicators = getIndicatorMatches(payload.extractedText, "ocr" /* OCR */, { "processor": payload.processorId });
this.debug(`Extracted ${indicators.length} indicators via OCR from ${payload.fileId}.`);
if (!this.ocrCache.has(payload.fileId)) {
this.ocrCache.set(payload.fileId, /* @__PURE__ */ new Map());
}
const fileCache = this.ocrCache.get(payload.fileId);
if (fileCache) {
fileCache.set(payload.processorId, indicators);
this.debug(`Cached OCR results from ${payload.processorId} for ${payload.fileId}`);
}
if (isRelevantAttachment) {
this.debug(`OCR result is for a relevant attachment (${payload.fileId}), refreshing indicators.`);
await this.refreshIndicators();
}
}
handleOcrError(payload) {
console.error(`OCR error: ${payload.fileId} by ${payload.processorId}:`, payload.error);
}
handleOcrProgress(payload) {
this.debug(`OCR Progress: ${payload.fileId} by ${payload.processorId}: ${payload.status} ${payload.progressPercent ?? ""}% ${payload.message ?? ""}`);
}
/**
* Activate a view of the given type in the right sidebar.
* @param type a view type
*/
async activateView(type) {
const { workspace } = this.app;
let leaf = await workspace.ensureSideLeaf(type, "right", { active: true });
this.sidebarContainers?.set(type, leaf);
}
on(event, callback) {
this.emitter.on(event, callback);
return () => this.emitter.off(event, callback);
}
async saveSettings() {
await this.saveData(this.settings);
this.emitter.emit("settings-change");
}
async onunload() {
if (this.fileOpenRef) this.app.workspace.offref(this.fileOpenRef);
if (this.fileModifyRef) this.app.vault.offref(this.fileModifyRef);
if (this.vaultCacheRef) this.app.metadataCache.offref(this.vaultCacheRef);
this.worker?.terminate();
}
setDebugging(enabled) {
const changed = this.isDebugging !== enabled;
this.isDebugging = enabled;
if (changed) {
this.debug(`Debugging ${enabled ? "enabled" : "disabled"}.`);
}
}
debug(...args) {
if (this.isDebugging) {
console.log(`[${this.manifest.name}]`, ...args);
}
}
};
// src/domUtils.ts
var import_obsidian4 = require("obsidian");
function removeElements(els) {
if (els && els.length > 0) {
Array.from(els).forEach((el, index, array) => {
try {
el.parentNode?.removeChild(el);
} catch {
}
});
}
}
function openDetails(els) {
if (els && els.length > 0) {
Array.from(els).forEach((el) => {
try {
el.open = true;
} catch {
}
});
}
}
function datePickerSettingEl(parentEl, value, name) {
value = value || new Date(Date.now()).toISOString();
name = name || "Date Picker";
const fromDate = new import_obsidian4.Setting(parentEl).setName(name).settingEl;
const fromDateEl = document.createElement("input");
fromDateEl.setAttribute("type", "datetime-local");
fromDateEl.setAttribute("value", value);
fromDate.appendChild(fromDateEl);
return fromDateEl;
}
// src/editorUtils.ts
var import_obsidian5 = require("obsidian");
function transformSelectedText(editor, func) {
const selection = editor.getSelection();
const transformed = func(selection);
editor.replaceSelection(transformed);
return transformed;
}
function appendToEnd(app, file, text) {
if (!app) return;
const vault = app.vault;
if (!vault || !file) return;
vault.append(file, `
${text}`);
const view = app.workspace.getActiveViewOfType(import_obsidian5.MarkdownView);
if (!view) return;
const editor = view.editor;
if (!editor) return;
let lastLine = editor.lastLine();
if (!lastLine) return;
lastLine = lastLine - 1;
const lastLineLen = editor.getLine(lastLine).length;
if (!lastLineLen) return;
const lastLinePos = { line: lastLine, ch: lastLineLen };
editor.setCursor(lastLinePos);
editor.scrollIntoView({ from: lastLinePos, to: lastLinePos }, true);
editor.focus();
}
// src/modal.ts
var import_obsidian7 = require("obsidian");
// src/workspaceUtils.ts
var import_obsidian6 = require("obsidian");
function addButtonContainer(workspace, file, className, rootFolder) {
const view = workspace.getActiveViewOfType(import_obsidian6.MarkdownView);
if (!view) return;
const container = view.containerEl;
if (!container) return;
const els = container.getElementsByClassName(className);
if (els && els.length > 0) {
Array.from(els).forEach((el, index, array) => {
container.removeChild(el);
});
}
if (rootFolder && !file.path.includes(rootFolder)) {
return;
}
const header = container.querySelector(".view-header");
if (!header) return;
const newDiv = document.createElement("div");
newDiv.className = className;
container.insertAfter(newDiv, header);
return newDiv;
}
function addButtonToContainer(el, buttonText) {
const button = new import_obsidian6.ButtonComponent(el).setButtonText(buttonText);
return button;
}
async function getActiveNoteContent(app) {
const file = app.workspace.getActiveFile();
if (!file) return null;
return await app.vault.cachedRead(file);
}
// src/modal.ts
var userMacro = {
name: "User",
macroRegex: [constructMacroRegex(/user\.?(?:\s*named?)?/)],
valueRegex: /user(name)?/gi
};
var hostNameMacro = {
name: "Hostname",
macroRegex: [constructMacroRegex(/(?:host|computer|comp)\.?\s*(?:named?)?/)],
valueRegex: /(host|computer|comp)(name)?/gi
};
var hashMacro = {
name: "Hash",
macroRegex: [constructMacroRegex(/(?:hash|sha\s*256|sha)/)],
valueRegex: /(hash|sha256|sha)/gi
};
var fileMacro = {
name: "Filename",
macroRegex: [FILE_REGEX, constructMacroRegex(/(?:(?:file\s*(?:path)?|path|attachments?)\.?\s*(?:name)?)/)],
valueRegex: /(file(path)?|path)(name)?/gi
};
var supportedMacros = [userMacro, hostNameMacro, hashMacro, fileMacro];
var ScriptObject = class {
code;
dated;
baseTimestamp;
fromTime;
toTime;
macros;
replacedCode;
constructor(code, dated) {
this.code = code;