132 lines
4.2 KiB
JavaScript
132 lines
4.2 KiB
JavaScript
const { request } = require("undici");
|
|
const { DateTime } = require("luxon");
|
|
|
|
async function fetchGoAccessJson(url) {
|
|
const res = await request(url, { method: "GET" });
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
throw new Error(`HTTP ${res.statusCode}`);
|
|
}
|
|
return res.body.json();
|
|
}
|
|
|
|
function crawlerRatios(data) {
|
|
const browsers = data.browsers?.data || [];
|
|
const crawler = browsers.find((entry) => entry.data === "Crawlers");
|
|
if (!crawler) return { hits: 0, visitors: 0 };
|
|
|
|
const totalHits = (browsers.reduce((sum, entry) => sum + (entry.hits?.count || 0), 0)) || 0;
|
|
const totalVisitors = (browsers.reduce((sum, entry) => sum + (entry.visitors?.count || 0), 0)) || 0;
|
|
|
|
const hitRatio = totalHits > 0 ? Math.min(1, (crawler.hits?.count || 0) / totalHits) : 0;
|
|
const visitorRatio = totalVisitors > 0 ? Math.min(1, (crawler.visitors?.count || 0) / totalVisitors) : 0;
|
|
|
|
return { hits: hitRatio, visitors: visitorRatio };
|
|
}
|
|
|
|
function groupVisitsByMonth(data, { adjustCrawlers = true } = {}) {
|
|
const entries = data.visitors?.data || [];
|
|
const ratios = adjustCrawlers ? crawlerRatios(data) : { hits: 0, visitors: 0 };
|
|
const months = new Map();
|
|
|
|
for (const entry of entries) {
|
|
const dateStr = entry.data;
|
|
if (!/^[0-9]{8}$/.test(dateStr)) continue;
|
|
const year = dateStr.slice(0, 4);
|
|
const month = dateStr.slice(4, 6);
|
|
const day = dateStr.slice(6, 8);
|
|
const key = `${year}-${month}`;
|
|
|
|
const hits = entry.hits?.count || 0;
|
|
const visitors = entry.visitors?.count || 0;
|
|
|
|
const current = months.get(key) || { hits: 0, visitors: 0, from: null, to: null };
|
|
const isoDate = `${year}-${month}-${day}`;
|
|
|
|
current.hits += hits;
|
|
current.visitors += visitors;
|
|
if (!current.from || isoDate < current.from) current.from = isoDate;
|
|
if (!current.to || isoDate > current.to) current.to = isoDate;
|
|
|
|
months.set(key, current);
|
|
}
|
|
|
|
const adjust = (value, ratio) => {
|
|
if (!adjustCrawlers) return value;
|
|
const scaled = value * (1 - ratio);
|
|
return Math.max(0, Math.round(scaled));
|
|
};
|
|
|
|
const sorted = Array.from(months.entries())
|
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
.map(([key, value]) => ({
|
|
month: key,
|
|
from: value.from,
|
|
to: value.to,
|
|
hits: adjust(value.hits, ratios.hits),
|
|
visitors: adjust(value.visitors, ratios.visitors),
|
|
}));
|
|
|
|
return sorted;
|
|
}
|
|
|
|
function aggregateLastNDays(data, days = 30, { adjustCrawlers = true } = {}) {
|
|
const entries = data.visitors?.data || [];
|
|
if (!entries.length || days <= 0) {
|
|
return { from: null, to: null, hits: 0, visitors: 0 };
|
|
}
|
|
|
|
const valid = entries.filter((entry) => /^[0-9]{8}$/.test(entry.data));
|
|
if (valid.length === 0) {
|
|
return { from: null, to: null, hits: 0, visitors: 0 };
|
|
}
|
|
|
|
const sorted = valid.slice().sort((a, b) => a.data.localeCompare(b.data));
|
|
const last = sorted[sorted.length - 1];
|
|
const end = DateTime.fromFormat(last.data, "yyyyLLdd", { zone: "UTC" });
|
|
if (!end.isValid) {
|
|
return { from: null, to: null, hits: 0, visitors: 0 };
|
|
}
|
|
|
|
const start = end.minus({ days: days - 1 });
|
|
|
|
let from = null;
|
|
let to = null;
|
|
let hits = 0;
|
|
let visitors = 0;
|
|
|
|
for (const entry of sorted) {
|
|
const current = DateTime.fromFormat(entry.data, "yyyyLLdd", { zone: "UTC" });
|
|
if (!current.isValid) continue;
|
|
if (current < start || current > end) continue;
|
|
|
|
const iso = current.toISODate();
|
|
if (!from || iso < from) from = iso;
|
|
if (!to || iso > to) to = iso;
|
|
|
|
hits += entry.hits?.count || 0;
|
|
visitors += entry.visitors?.count || 0;
|
|
}
|
|
|
|
const ratios = adjustCrawlers ? crawlerRatios(data) : { hits: 0, visitors: 0 };
|
|
|
|
const adjust = (value, ratio) => {
|
|
if (!adjustCrawlers) return value;
|
|
const scaled = value * (1 - ratio);
|
|
return Math.max(0, Math.round(scaled));
|
|
};
|
|
|
|
return {
|
|
from,
|
|
to,
|
|
hits: adjust(hits, ratios.hits),
|
|
visitors: adjust(visitors, ratios.visitors),
|
|
};
|
|
}
|
|
|
|
module.exports = {
|
|
fetchGoAccessJson,
|
|
groupVisitsByMonth,
|
|
aggregateLastNDays,
|
|
crawlerRatios,
|
|
};
|