-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathjob.js
173 lines (149 loc) · 5.19 KB
/
job.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
const sqlite3 = require("sqlite3").verbose();
const dotenv = require("dotenv");
// Load environment variables from .env file
dotenv.config();
// Define the path to the Safari history database
const historyDbPath = `${process.env.HOME}/Library/Safari/History.db`;
// Track whether it's the first run
let isFirstRun = true;
// Function to read SQLite databases
function readSqliteDatabase(filePath) {
return new Promise((resolve, reject) => {
const db = new sqlite3.Database(filePath, sqlite3.OPEN_READONLY, (err) => {
if (err) {
console.warn(
`Warning: Could not read SQLite database ${filePath}.`,
err.message,
);
resolve(null);
return;
}
db.all(
"SELECT name FROM sqlite_master WHERE type='table'",
(err, tables) => {
if (err) {
reject(err);
return;
}
const data = {};
let tablesProcessed = 0;
tables.forEach((table) => {
db.all(`SELECT * FROM ${table.name}`, (err, rows) => {
if (err) {
reject(err);
return;
}
data[table.name] = rows;
tablesProcessed++;
if (tablesProcessed === tables.length) {
db.close();
resolve(data);
}
});
});
},
);
});
});
}
// Function to convert CFAbsoluteTime (Apple timestamp) to Unix timestamp in milliseconds
function convertAppleTimeToUnixMillis(appleTime) {
const appleEpoch = Date.UTC(2001, 0, 1, 0, 0, 0, 0); // January 1, 2001, 00:00:00 UTC in milliseconds
return Math.round(appleEpoch + appleTime * 1000);
}
// Function to fetch Safari history and filter items based on the first run or last hour
async function fetchHistory(includeAllHistory) {
const historyData = await readSqliteDatabase(historyDbPath);
// Transform history_items into a mapped object { [id: string]: item }
const historyItemsMap = {};
if (historyData && historyData.history_items) {
historyData.history_items.forEach((item) => {
historyItemsMap[item.id] = item;
});
}
// Prepare the data for JSON output
const jsonData = [];
// Enhance history_visits to include the URL and title from history_items and convert visit_time to Unix timestamp in milliseconds
if (historyData && historyData.history_visits) {
historyData.history_visits.forEach((visit) => {
const historyItem = historyItemsMap[visit.history_item];
if (historyItem) {
jsonData.push({
url: historyItem.url,
visitedAt: convertAppleTimeToUnixMillis(visit.visit_time),
nextVisitedAt: null, // Will be populated later
title: visit.title || "",
datetime: new Date(
convertAppleTimeToUnixMillis(visit.visit_time),
).toISOString(),
duration: null, // Will be populated later
});
}
});
}
// Sort the data by visitedAt in reverse chronological order
jsonData.sort((a, b) => b.visitedAt - a.visitedAt);
// Calculate nextVisitedAt and duration
for (let i = 0; i < jsonData.length; i++) {
if (i > 0) {
jsonData[i].nextVisitedAt = jsonData[i - 1]?.visitedAt;
const duration = Math.floor(
(jsonData[i].nextVisitedAt - jsonData[i].visitedAt) / 1000,
);
jsonData[i].duration = Math.min(duration, 300); // Cap duration at 300 seconds
} else {
jsonData[i].nextVisitedAt = null;
jsonData[i].duration = null;
}
}
// Filter items based on the first run or last hour
if (!includeAllHistory) {
const oneHourAgo = Date.now() - 60 * 60 * 1000;
return jsonData.filter((item) => item.visitedAt >= oneHourAgo);
}
return jsonData; // Return all history on the first run
}
// Function to submit history data to the Cloudflare Worker endpoint
async function submitHistoryToEndpoint(history) {
const domain = process.env.DOMAIN;
if (!domain) {
throw new Error("DOMAIN environment variable is not set.");
}
const dataString = JSON.stringify(history);
console.log("going to submit", dataString.length);
const response = await fetch(`${domain}/insert`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: dataString,
});
if (!response.ok) {
throw new Error(`Failed to submit history: ${await response.text()}`);
}
console.log("History submitted successfully:", await response.json());
}
// Main function to fetch and submit history
async function main() {
try {
console.log(
isFirstRun
? "Fetching all history (first run)..."
: "Fetching history from the last hour...",
);
const history = await fetchHistory(isFirstRun);
console.log(`Found ${history.length} items to submit.`);
if (history.length > 0) {
await submitHistoryToEndpoint(history);
} else {
console.log("No new items to submit.");
}
// After the first run, set isFirstRun to false
if (isFirstRun) {
isFirstRun = false;
}
} catch (error) {
console.error("Error in main function:", error);
}
}
// Run the main function and schedule it to run every hour
main().catch(console.error);
setInterval(main, 60 * 60 * 1000); // Run every hour