apptvty 0.2.5 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{chunk-UEMFLSQL.mjs → chunk-DFSZAE6R.mjs} +60 -6
- package/dist/chunk-DFSZAE6R.mjs.map +1 -0
- package/dist/{chunk-WBHRLAUR.mjs → chunk-EZYPAN7G.mjs} +85 -101
- package/dist/chunk-EZYPAN7G.mjs.map +1 -0
- package/dist/chunk-RNZYGUMF.mjs +257 -0
- package/dist/chunk-RNZYGUMF.mjs.map +1 -0
- package/dist/cli.js +27 -6
- package/dist/index.d.mts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +229 -126
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3 -3
- package/dist/middleware/express.d.mts +1 -1
- package/dist/middleware/express.d.ts +1 -1
- package/dist/middleware/express.js +83 -39
- package/dist/middleware/express.js.map +1 -1
- package/dist/middleware/express.mjs +2 -2
- package/dist/middleware/nextjs.d.mts +3 -3
- package/dist/middleware/nextjs.d.ts +3 -3
- package/dist/middleware/nextjs.js +213 -138
- package/dist/middleware/nextjs.js.map +1 -1
- package/dist/middleware/nextjs.mjs +2 -2
- package/dist/setup.d.mts +1 -1
- package/dist/setup.d.ts +1 -1
- package/dist/{types-CwI3lNay.d.mts → types-DwAWwqqD.d.mts} +11 -11
- package/dist/{types-CwI3lNay.d.ts → types-DwAWwqqD.d.ts} +11 -11
- package/package.json +8 -3
- package/dist/chunk-BO7AVDLZ.mjs +0 -179
- package/dist/chunk-BO7AVDLZ.mjs.map +0 -1
- package/dist/chunk-UEMFLSQL.mjs.map +0 -1
- package/dist/chunk-WBHRLAUR.mjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
5
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
8
|
var __export = (target, all) => {
|
|
7
9
|
for (var name in all)
|
|
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
17
|
}
|
|
16
18
|
return to;
|
|
17
19
|
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
18
28
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
29
|
|
|
20
30
|
// src/index.ts
|
|
@@ -361,7 +371,7 @@ var ApptvtyClient = class {
|
|
|
361
371
|
this.log("X402 challenge auto-paid from wallet, retrying request");
|
|
362
372
|
return this.post(path, body);
|
|
363
373
|
}
|
|
364
|
-
let dashboardUrl = "https://dashboard.apptvty.com/login";
|
|
374
|
+
let dashboardUrl = (typeof process !== "undefined" ? process.env?.DASHBOARD_URL : void 0) ?? "https://dashboard.apptvty.com/login";
|
|
365
375
|
try {
|
|
366
376
|
const json = JSON.parse(text);
|
|
367
377
|
dashboardUrl = json?.error?.details?.dashboard_url ?? dashboardUrl;
|
|
@@ -413,7 +423,7 @@ var RequestLogger = class {
|
|
|
413
423
|
this.client = client;
|
|
414
424
|
this.queue = [];
|
|
415
425
|
this.timer = null;
|
|
416
|
-
this.
|
|
426
|
+
this.activeFlushPromise = null;
|
|
417
427
|
this.batchSize = config.batchSize ?? 50;
|
|
418
428
|
this.debug = config.debug ?? false;
|
|
419
429
|
const interval = config.flushInterval ?? 5e3;
|
|
@@ -438,21 +448,31 @@ var RequestLogger = class {
|
|
|
438
448
|
/** Enqueue a single log entry. Non-blocking. */
|
|
439
449
|
enqueue(entry) {
|
|
440
450
|
this.queue.push(entry);
|
|
441
|
-
|
|
442
|
-
void this.flush();
|
|
443
|
-
}
|
|
451
|
+
void this.flush();
|
|
444
452
|
}
|
|
445
|
-
/** Flush the current queue to the API. */
|
|
453
|
+
/** Flush the current queue to the API. Returns the active Promise so Edge runtimes can wait. */
|
|
446
454
|
async flush() {
|
|
447
|
-
if (this.
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
await this.
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
this.flushing = false;
|
|
455
|
+
if (this.queue.length === 0) {
|
|
456
|
+
return this.activeFlushPromise || Promise.resolve();
|
|
457
|
+
}
|
|
458
|
+
if (this.activeFlushPromise) {
|
|
459
|
+
await this.activeFlushPromise;
|
|
460
|
+
if (this.queue.length > 0) return this.flush();
|
|
461
|
+
return Promise.resolve();
|
|
455
462
|
}
|
|
463
|
+
const batch = this.queue.splice(0, this.batchSize);
|
|
464
|
+
this.activeFlushPromise = (async () => {
|
|
465
|
+
try {
|
|
466
|
+
await this.client.sendLogs(batch);
|
|
467
|
+
} catch {
|
|
468
|
+
} finally {
|
|
469
|
+
this.activeFlushPromise = null;
|
|
470
|
+
if (this.queue.length > 0) {
|
|
471
|
+
void this.flush();
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
})();
|
|
475
|
+
return this.activeFlushPromise;
|
|
456
476
|
}
|
|
457
477
|
/**
|
|
458
478
|
* Synchronous-ish flush for process shutdown.
|
|
@@ -676,7 +696,7 @@ function createQueryHandler(client, config) {
|
|
|
676
696
|
if (trimmedQuery.length > 500) {
|
|
677
697
|
return errorResponse(400, "QUERY_TOO_LONG", "Query must be 500 characters or fewer");
|
|
678
698
|
}
|
|
679
|
-
const requestId = crypto.randomUUID();
|
|
699
|
+
const requestId = globalThis.crypto.randomUUID();
|
|
680
700
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
681
701
|
const startMs = Date.now();
|
|
682
702
|
const surfaceAds = req.surface_ads !== false;
|
|
@@ -704,16 +724,21 @@ function createQueryHandler(client, config) {
|
|
|
704
724
|
return errorResponse(502, "UPSTREAM_ERROR", "Could not retrieve an answer at this time");
|
|
705
725
|
}
|
|
706
726
|
const responseTimeMs = Date.now() - startMs;
|
|
707
|
-
const ads = backendResponse.
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
727
|
+
const ads = backendResponse.related_resources ? Array.isArray(backendResponse.related_resources) ? backendResponse.related_resources : [backendResponse.related_resources] : [];
|
|
728
|
+
if (ads.length > 0) {
|
|
729
|
+
await Promise.all(
|
|
730
|
+
ads.map(
|
|
731
|
+
(ad) => client.logImpression({
|
|
732
|
+
impression_id: ad.impression_id,
|
|
733
|
+
site_id: config.siteId,
|
|
734
|
+
query: trimmedQuery,
|
|
735
|
+
agent_ua: req.userAgent,
|
|
736
|
+
agent_ip: req.ipAddress,
|
|
737
|
+
timestamp
|
|
738
|
+
}).catch(() => {
|
|
739
|
+
})
|
|
740
|
+
)
|
|
741
|
+
);
|
|
717
742
|
}
|
|
718
743
|
const agentResponse = {
|
|
719
744
|
success: true,
|
|
@@ -722,7 +747,7 @@ function createQueryHandler(client, config) {
|
|
|
722
747
|
answer: backendResponse.answer,
|
|
723
748
|
sources: backendResponse.sources,
|
|
724
749
|
confidence: backendResponse.confidence,
|
|
725
|
-
...backendResponse.
|
|
750
|
+
...backendResponse.related_resources && { related_resources: backendResponse.related_resources },
|
|
726
751
|
metadata: {
|
|
727
752
|
request_id: requestId,
|
|
728
753
|
response_time_ms: responseTimeMs,
|
|
@@ -740,7 +765,7 @@ function errorResponse(status, code, message) {
|
|
|
740
765
|
error: {
|
|
741
766
|
code,
|
|
742
767
|
message,
|
|
743
|
-
request_id: crypto.randomUUID(),
|
|
768
|
+
request_id: globalThis.crypto.randomUUID(),
|
|
744
769
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
745
770
|
}
|
|
746
771
|
};
|
|
@@ -758,61 +783,58 @@ function getOrigin(url) {
|
|
|
758
783
|
// src/middleware/nextjs.ts
|
|
759
784
|
var import_server = require("next/server");
|
|
760
785
|
|
|
761
|
-
// src/
|
|
762
|
-
var
|
|
763
|
-
function
|
|
764
|
-
if (!html
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
786
|
+
// src/markdown.ts
|
|
787
|
+
var cheerio = __toESM(require("cheerio"));
|
|
788
|
+
function convertHtmlToMarkdown(html) {
|
|
789
|
+
if (!html) return "";
|
|
790
|
+
const $ = cheerio.load(html);
|
|
791
|
+
$("script, style, nav, footer, header, aside, svg, .ad, .sponsor, noscript").remove();
|
|
792
|
+
const main = $('main, article, [role="main"], #content, .content').first();
|
|
793
|
+
const root = main.length ? main : $("body");
|
|
794
|
+
let markdown = "";
|
|
795
|
+
root.find("h1, h2, h3, h4, h5, h6, p, ul, ol").each((_, el) => {
|
|
796
|
+
const $el = $(el);
|
|
797
|
+
const tagName = el.tagName.toLowerCase();
|
|
798
|
+
if (tagName === "ul" || tagName === "ol") {
|
|
799
|
+
$el.find("li").each((_2, li) => {
|
|
800
|
+
const text2 = cleanText($(li).text());
|
|
801
|
+
if (text2) markdown += `- ${text2}
|
|
802
|
+
`;
|
|
803
|
+
});
|
|
804
|
+
markdown += "\n";
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const text = cleanText($el.text());
|
|
808
|
+
if (!text) return;
|
|
809
|
+
if (tagName === "h1") markdown += `# ${text}
|
|
810
|
+
|
|
811
|
+
`;
|
|
812
|
+
else if (tagName === "h2") markdown += `## ${text}
|
|
813
|
+
|
|
814
|
+
`;
|
|
815
|
+
else if (tagName === "h3") markdown += `### ${text}
|
|
816
|
+
|
|
817
|
+
`;
|
|
818
|
+
else if (tagName === "h4") markdown += `#### ${text}
|
|
819
|
+
|
|
820
|
+
`;
|
|
821
|
+
else if (tagName === "h5") markdown += `##### ${text}
|
|
822
|
+
|
|
823
|
+
`;
|
|
824
|
+
else if (tagName === "h6") markdown += `###### ${text}
|
|
825
|
+
|
|
826
|
+
`;
|
|
827
|
+
else if (tagName === "p") markdown += `${text}
|
|
828
|
+
|
|
829
|
+
`;
|
|
830
|
+
});
|
|
831
|
+
if (markdown.trim().length < 50) {
|
|
832
|
+
markdown = cleanText(root.text()) + "\n\n";
|
|
782
833
|
}
|
|
783
|
-
return
|
|
784
|
-
}
|
|
785
|
-
function buildSponsoredHeader(ads) {
|
|
786
|
-
return JSON.stringify(
|
|
787
|
-
ads.map((ad) => ({ text: ad.text, url: ad.url, advertiser: ad.advertiser }))
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
function buildContentStreamBlock(ads) {
|
|
791
|
-
const paragraphs = ads.map(
|
|
792
|
-
(ad) => `<p data-apptvty-sponsored="${escapeAttr(ad.impression_id)}"><strong>[Sponsored]</strong> <a href="${escapeAttr(ad.url)}" rel="sponsored noopener">${escapeHtml(ad.text)}</a> \u2014 <span>${escapeHtml(ad.advertiser)}</span></p>`
|
|
793
|
-
).join("\n");
|
|
794
|
-
return `${AD_INJECTION_MARKER}
|
|
795
|
-
${paragraphs}`;
|
|
834
|
+
return markdown.trim();
|
|
796
835
|
}
|
|
797
|
-
function
|
|
798
|
-
|
|
799
|
-
"@context": "https://schema.org",
|
|
800
|
-
"@type": "WPAdBlock",
|
|
801
|
-
sponsor: {
|
|
802
|
-
"@type": "Organization",
|
|
803
|
-
name: ad.advertiser,
|
|
804
|
-
url: ad.url
|
|
805
|
-
},
|
|
806
|
-
description: ad.text
|
|
807
|
-
}));
|
|
808
|
-
const ld = entries.length === 1 ? entries[0] : entries;
|
|
809
|
-
return `<script type="application/ld+json">${JSON.stringify(ld)}</script>`;
|
|
810
|
-
}
|
|
811
|
-
function escapeHtml(s) {
|
|
812
|
-
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
813
|
-
}
|
|
814
|
-
function escapeAttr(s) {
|
|
815
|
-
return s.replace(/"/g, """).replace(/'/g, "'");
|
|
836
|
+
function cleanText(text) {
|
|
837
|
+
return text.trim().replace(/\s+/g, " ");
|
|
816
838
|
}
|
|
817
839
|
|
|
818
840
|
// src/middleware/nextjs.ts
|
|
@@ -833,16 +855,38 @@ function getInstance(config) {
|
|
|
833
855
|
function withApptvty(config, next) {
|
|
834
856
|
const { client, logger } = getInstance(config);
|
|
835
857
|
const queryPath = config.queryPath ?? "/query";
|
|
836
|
-
return async function apptvtyMiddleware(request) {
|
|
858
|
+
return async function apptvtyMiddleware(request, event) {
|
|
837
859
|
const startMs = Date.now();
|
|
838
860
|
const userAgent = request.headers.get("user-agent") ?? "";
|
|
839
861
|
const crawlerInfo = detectCrawler(userAgent);
|
|
840
862
|
const scraperService = detectScraperService(userAgent);
|
|
841
863
|
const aiCrawlerParam = parseBoolParam(request.nextUrl.searchParams.get("ai_crawler"), false);
|
|
842
864
|
const isCrawler = crawlerInfo.isAi || aiCrawlerParam || scraperService.isScraperService;
|
|
865
|
+
if (request.nextUrl.pathname === "/api/apptvty/verify") {
|
|
866
|
+
const challenge = request.nextUrl.searchParams.get("challenge");
|
|
867
|
+
if (challenge) {
|
|
868
|
+
const encoder = new TextEncoder();
|
|
869
|
+
const keyData = encoder.encode(config.apiKey);
|
|
870
|
+
const cryptoKey = await globalThis.crypto.subtle.importKey(
|
|
871
|
+
"raw",
|
|
872
|
+
keyData,
|
|
873
|
+
{ name: "HMAC", hash: "SHA-256" },
|
|
874
|
+
false,
|
|
875
|
+
["sign"]
|
|
876
|
+
);
|
|
877
|
+
const prefixedChallenge = `apptvty_verify_challenge:${challenge}`;
|
|
878
|
+
const signatureBuffer = await globalThis.crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(prefixedChallenge));
|
|
879
|
+
const signature = Array.from(new Uint8Array(signatureBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
880
|
+
return import_server.NextResponse.json({
|
|
881
|
+
site_id: config.siteId,
|
|
882
|
+
verified: true,
|
|
883
|
+
signature
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
}
|
|
843
887
|
let response;
|
|
844
888
|
try {
|
|
845
|
-
response = next ? await next(request) : import_server.NextResponse.next();
|
|
889
|
+
response = next ? await next(request, event) : import_server.NextResponse.next();
|
|
846
890
|
} catch (err) {
|
|
847
891
|
throw err;
|
|
848
892
|
}
|
|
@@ -868,24 +912,53 @@ function withApptvty(config, next) {
|
|
|
868
912
|
confidence_score: crawlerInfo.confidence,
|
|
869
913
|
scraper_service: scraperService.name
|
|
870
914
|
};
|
|
871
|
-
|
|
872
|
-
if (
|
|
873
|
-
|
|
874
|
-
if (
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
915
|
+
const isInternalRequest = request.headers.get("x-apptvty-internal") === "true";
|
|
916
|
+
if (!isInternalRequest && !pathname.startsWith(queryPath)) {
|
|
917
|
+
logger.enqueue(entry);
|
|
918
|
+
if (event && typeof event.waitUntil === "function") {
|
|
919
|
+
event.waitUntil(logger.flush());
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
if (isCrawler && !isInternalRequest && !pathname.startsWith(queryPath)) {
|
|
923
|
+
try {
|
|
924
|
+
const proxyReq = new Request(request.url, {
|
|
925
|
+
headers: new Headers(request.headers)
|
|
926
|
+
});
|
|
927
|
+
proxyReq.headers.set("x-apptvty-internal", "true");
|
|
928
|
+
const res = await fetch(proxyReq);
|
|
929
|
+
const contentType = res.headers.get("content-type") ?? "";
|
|
930
|
+
if (contentType.includes("text/html")) {
|
|
931
|
+
const html = await res.text();
|
|
932
|
+
let markdown = convertHtmlToMarkdown(html);
|
|
933
|
+
const pageAds = await client.getAdsForPage({ site_id: config.siteId, page_path: pathname });
|
|
934
|
+
if (pageAds.ads && pageAds.ads.length > 0) {
|
|
935
|
+
const ad = pageAds.ads[0];
|
|
936
|
+
markdown += `
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
> **Sponsored:** [${ad.text}](${ad.url}) - ${ad.advertiser}
|
|
940
|
+
`;
|
|
941
|
+
client.logImpression({
|
|
942
|
+
impression_id: ad.impression_id,
|
|
943
|
+
site_id: config.siteId,
|
|
944
|
+
page_path: pathname,
|
|
945
|
+
agent_ua: userAgent,
|
|
946
|
+
agent_ip: getClientIp(headers),
|
|
947
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
948
|
+
}).catch(() => {
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
return new import_server.NextResponse(markdown, {
|
|
952
|
+
status: res.status,
|
|
953
|
+
headers: {
|
|
954
|
+
"Content-Type": "text/markdown",
|
|
955
|
+
"X-Apptvty-AEO": "true"
|
|
956
|
+
}
|
|
957
|
+
});
|
|
888
958
|
}
|
|
959
|
+
return res;
|
|
960
|
+
} catch (err) {
|
|
961
|
+
if (config.debug) console.warn("[apptvty] Markdown proxy failed:", err);
|
|
889
962
|
}
|
|
890
963
|
}
|
|
891
964
|
return response;
|
|
@@ -917,33 +990,6 @@ function createNextjsQueryHandler(config) {
|
|
|
917
990
|
});
|
|
918
991
|
};
|
|
919
992
|
}
|
|
920
|
-
async function injectAdsIntoResponse(response, client, config, pathname, userAgent, ipAddress, isScraperService) {
|
|
921
|
-
const html = await response.text();
|
|
922
|
-
if (!html) return null;
|
|
923
|
-
const pageAds = await client.getAdsForPage({ site_id: config.siteId, page_path: pathname });
|
|
924
|
-
if (!pageAds.ads || pageAds.ads.length === 0) return null;
|
|
925
|
-
const modified = injectIntoHtml(html, pageAds.ads, isScraperService);
|
|
926
|
-
if (modified === html) return null;
|
|
927
|
-
const newHeaders = new Headers(response.headers);
|
|
928
|
-
newHeaders.set("X-Sponsored-Content", buildSponsoredHeader(pageAds.ads));
|
|
929
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
930
|
-
for (const ad of pageAds.ads) {
|
|
931
|
-
client.logImpression({
|
|
932
|
-
impression_id: ad.impression_id,
|
|
933
|
-
site_id: config.siteId,
|
|
934
|
-
page_path: pathname,
|
|
935
|
-
agent_ua: userAgent,
|
|
936
|
-
agent_ip: ipAddress,
|
|
937
|
-
timestamp
|
|
938
|
-
}).catch(() => {
|
|
939
|
-
});
|
|
940
|
-
}
|
|
941
|
-
return new import_server.NextResponse(modified, {
|
|
942
|
-
status: response.status,
|
|
943
|
-
statusText: response.statusText,
|
|
944
|
-
headers: newHeaders
|
|
945
|
-
});
|
|
946
|
-
}
|
|
947
993
|
function parseBoolParam(value, defaultValue) {
|
|
948
994
|
if (value === null) return defaultValue;
|
|
949
995
|
return value === "1" || value === "true" || value === "yes";
|
|
@@ -952,6 +998,63 @@ function shouldSkip(pathname) {
|
|
|
952
998
|
return pathname.startsWith("/_next/") || pathname.startsWith("/api/_") || pathname === "/favicon.ico" || /\.(svg|png|jpg|jpeg|gif|webp|ico|woff2?|ttf|css|js\.map)$/.test(pathname);
|
|
953
999
|
}
|
|
954
1000
|
|
|
1001
|
+
// src/ad-injection.ts
|
|
1002
|
+
var AD_INJECTION_MARKER = "<!-- apptvty-sponsored -->";
|
|
1003
|
+
function injectIntoHtml(html, ads, isScraperService) {
|
|
1004
|
+
if (!html || ads.length === 0) return html;
|
|
1005
|
+
if (html.includes(AD_INJECTION_MARKER)) return html;
|
|
1006
|
+
let modified = html;
|
|
1007
|
+
const contentBlock = buildContentStreamBlock(ads);
|
|
1008
|
+
if (modified.includes("</article>")) {
|
|
1009
|
+
modified = modified.replace("</article>", `${contentBlock}
|
|
1010
|
+
</article>`);
|
|
1011
|
+
} else if (modified.includes("</main>")) {
|
|
1012
|
+
modified = modified.replace("</main>", `${contentBlock}
|
|
1013
|
+
</main>`);
|
|
1014
|
+
} else if (!isScraperService && modified.includes("</body>")) {
|
|
1015
|
+
modified = modified.replace("</body>", `${contentBlock}
|
|
1016
|
+
</body>`);
|
|
1017
|
+
}
|
|
1018
|
+
if (!isScraperService && modified.includes("</head>")) {
|
|
1019
|
+
const jsonLdBlock = buildJsonLdBlock(ads);
|
|
1020
|
+
modified = modified.replace("</head>", `${jsonLdBlock}
|
|
1021
|
+
</head>`);
|
|
1022
|
+
}
|
|
1023
|
+
return modified;
|
|
1024
|
+
}
|
|
1025
|
+
function buildSponsoredHeader(ads) {
|
|
1026
|
+
return JSON.stringify(
|
|
1027
|
+
ads.map((ad) => ({ text: ad.text, url: ad.url, advertiser: ad.advertiser }))
|
|
1028
|
+
);
|
|
1029
|
+
}
|
|
1030
|
+
function buildContentStreamBlock(ads) {
|
|
1031
|
+
const paragraphs = ads.map(
|
|
1032
|
+
(ad) => `<p data-apptvty-sponsored="${escapeAttr(ad.impression_id)}"><strong>[Sponsored]</strong> <a href="${escapeAttr(ad.url)}" rel="sponsored noopener">${escapeHtml(ad.text)}</a> \u2014 <span>${escapeHtml(ad.advertiser)}</span></p>`
|
|
1033
|
+
).join("\n");
|
|
1034
|
+
return `${AD_INJECTION_MARKER}
|
|
1035
|
+
${paragraphs}`;
|
|
1036
|
+
}
|
|
1037
|
+
function buildJsonLdBlock(ads) {
|
|
1038
|
+
const entries = ads.map((ad) => ({
|
|
1039
|
+
"@context": "https://schema.org",
|
|
1040
|
+
"@type": "WPAdBlock",
|
|
1041
|
+
sponsor: {
|
|
1042
|
+
"@type": "Organization",
|
|
1043
|
+
name: ad.advertiser,
|
|
1044
|
+
url: ad.url
|
|
1045
|
+
},
|
|
1046
|
+
description: ad.text
|
|
1047
|
+
}));
|
|
1048
|
+
const ld = entries.length === 1 ? entries[0] : entries;
|
|
1049
|
+
return `<script type="application/ld+json">${JSON.stringify(ld)}</script>`;
|
|
1050
|
+
}
|
|
1051
|
+
function escapeHtml(s) {
|
|
1052
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1053
|
+
}
|
|
1054
|
+
function escapeAttr(s) {
|
|
1055
|
+
return s.replace(/"/g, """).replace(/'/g, "'");
|
|
1056
|
+
}
|
|
1057
|
+
|
|
955
1058
|
// src/middleware/express.ts
|
|
956
1059
|
var instances2 = /* @__PURE__ */ new Map();
|
|
957
1060
|
function getInstance2(config) {
|