@aikeytake/social-automation 2.0.4 → 2.0.6
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/package.json +2 -1
- package/src/index.js +96 -77
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aikeytake/social-automation",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.6",
|
|
4
4
|
"description": "Content research and aggregation tool for AI agents",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"author": "aikeytake",
|
|
25
25
|
"license": "MIT",
|
|
26
26
|
"dependencies": {
|
|
27
|
+
"@aikeytake/social-automation": "2.0.5",
|
|
27
28
|
"@supabase/supabase-js": "^2.47.0",
|
|
28
29
|
"axios": "^1.7.9",
|
|
29
30
|
"cheerio": "^1.0.0",
|
package/src/index.js
CHANGED
|
@@ -7,13 +7,10 @@ import apiFetch from './fetchers/api.js';
|
|
|
7
7
|
import fs from 'fs';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
-
import
|
|
10
|
+
import defaultConfig from '../config/sources.json';
|
|
11
11
|
|
|
12
12
|
dotenv.config();
|
|
13
13
|
|
|
14
|
-
// Use require.resolve to find package root (works correctly through pnpm symlinks)
|
|
15
|
-
const require = createRequire(import.meta.url);
|
|
16
|
-
const PKG_ROOT = path.dirname(require.resolve('@aikeytake/social-automation/package.json'));
|
|
17
14
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
15
|
const logger = createLogger('Main');
|
|
19
16
|
|
|
@@ -126,10 +123,9 @@ async function scrape(options = {}) {
|
|
|
126
123
|
return results;
|
|
127
124
|
}
|
|
128
125
|
|
|
129
|
-
function loadConfig() {
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
return JSON.parse(content);
|
|
126
|
+
function loadConfig(optionsConfig) {
|
|
127
|
+
// Allow config override (for testing or custom sources), otherwise use bundled default
|
|
128
|
+
return optionsConfig || defaultConfig;
|
|
133
129
|
}
|
|
134
130
|
|
|
135
131
|
function getDateString() {
|
|
@@ -137,20 +133,26 @@ function getDateString() {
|
|
|
137
133
|
}
|
|
138
134
|
|
|
139
135
|
async function saveSourceData(source, items, today) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
fs.
|
|
136
|
+
try {
|
|
137
|
+
const todayFolder = path.join(__dirname, '../data', today);
|
|
138
|
+
if (!fs.existsSync(todayFolder)) {
|
|
139
|
+
fs.mkdirSync(todayFolder, { recursive: true });
|
|
140
|
+
}
|
|
141
|
+
const filePath = path.join(todayFolder, `${source}.json`);
|
|
142
|
+
const data = {
|
|
143
|
+
date: today,
|
|
144
|
+
source: source,
|
|
145
|
+
total_items: items.length,
|
|
146
|
+
scraped_at: new Date().toISOString(),
|
|
147
|
+
items: items
|
|
148
|
+
};
|
|
149
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
150
|
+
} catch (err) {
|
|
151
|
+
// Vercel ephemeral filesystem - ignore write errors when saving to filesystem
|
|
152
|
+
if (err.code !== 'ENOENT' && err.code !== 'EACCES') {
|
|
153
|
+
logger.warn(`saveSourceData skipped: ${err.message}`);
|
|
154
|
+
}
|
|
143
155
|
}
|
|
144
|
-
|
|
145
|
-
const filePath = path.join(todayFolder, `${source}.json`);
|
|
146
|
-
const data = {
|
|
147
|
-
date: today,
|
|
148
|
-
source: source,
|
|
149
|
-
total_items: items.length,
|
|
150
|
-
scraped_at: new Date().toISOString(),
|
|
151
|
-
items: items
|
|
152
|
-
};
|
|
153
|
-
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
async function saveToSupabase(supabase, items, date) {
|
|
@@ -181,66 +183,83 @@ async function saveToSupabase(supabase, items, date) {
|
|
|
181
183
|
}
|
|
182
184
|
|
|
183
185
|
async function generateCombinedFiles(results, today) {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
// Load all source files
|
|
187
|
-
const allItems = [];
|
|
188
|
-
const sourceFiles = fs.readdirSync(todayFolder)
|
|
189
|
-
.filter(f => f.endsWith('.json') && f !== 'all.json' && f !== 'trending.json');
|
|
190
|
-
for (const file of sourceFiles) {
|
|
191
|
-
const filePath = path.join(todayFolder, file);
|
|
192
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
193
|
-
const data = JSON.parse(content);
|
|
194
|
-
allItems.push(...(data.items || []));
|
|
195
|
-
}
|
|
186
|
+
try {
|
|
187
|
+
const todayFolder = path.join(__dirname, '../data', today);
|
|
196
188
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
for (const item of scoredItems) {
|
|
217
|
-
const source = item.source || 'unknown';
|
|
218
|
-
if (!trendingBySource[source]) trendingBySource[source] = 0;
|
|
219
|
-
if (trendingBySource[source] < 5) {
|
|
220
|
-
trendingBySource[source]++;
|
|
221
|
-
finalTrending.push(item);
|
|
189
|
+
// Load all source files
|
|
190
|
+
const allItems = [];
|
|
191
|
+
let sourceFiles = [];
|
|
192
|
+
try {
|
|
193
|
+
sourceFiles = fs.readdirSync(todayFolder)
|
|
194
|
+
.filter(f => f.endsWith('.json') && f !== 'all.json' && f !== 'trending.json');
|
|
195
|
+
} catch {
|
|
196
|
+
// Folder may not exist - skip loading
|
|
197
|
+
sourceFiles = [];
|
|
198
|
+
}
|
|
199
|
+
for (const file of sourceFiles) {
|
|
200
|
+
try {
|
|
201
|
+
const filePath = path.join(todayFolder, file);
|
|
202
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
203
|
+
const data = JSON.parse(content);
|
|
204
|
+
allItems.push(...(data.items || []));
|
|
205
|
+
} catch {
|
|
206
|
+
// Skip unreadable files
|
|
207
|
+
}
|
|
222
208
|
}
|
|
223
|
-
if (finalTrending.length >= 20) break;
|
|
224
|
-
}
|
|
225
209
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
210
|
+
// Save all.json
|
|
211
|
+
const allData = {
|
|
212
|
+
date: today,
|
|
213
|
+
generated_at: new Date().toISOString(),
|
|
214
|
+
total_items: allItems.length,
|
|
215
|
+
sources: results.sources,
|
|
216
|
+
items: allItems
|
|
217
|
+
};
|
|
218
|
+
fs.writeFileSync(path.join(todayFolder, 'all.json'), JSON.stringify(allData, null, 2));
|
|
219
|
+
|
|
220
|
+
// Generate trending.json (top 20 by score with source diversity)
|
|
221
|
+
const scoredItems = allItems
|
|
222
|
+
.filter(item => item.metadata?.score || item.engagement?.upvotes || item.engagement?.points || 0)
|
|
223
|
+
.map(item => ({ ...item, combined_score: calculateScore(item) }))
|
|
224
|
+
.sort((a, b) => b.combined_score - a.combined_score);
|
|
225
|
+
|
|
226
|
+
// Apply source diversity: max 5 items per source
|
|
227
|
+
const trendingBySource = {};
|
|
228
|
+
const finalTrending = [];
|
|
229
|
+
for (const item of scoredItems) {
|
|
230
|
+
const source = item.source || 'unknown';
|
|
231
|
+
if (!trendingBySource[source]) trendingBySource[source] = 0;
|
|
232
|
+
if (trendingBySource[source] < 5) {
|
|
233
|
+
trendingBySource[source]++;
|
|
234
|
+
finalTrending.push(item);
|
|
235
|
+
}
|
|
236
|
+
if (finalTrending.length >= 20) break;
|
|
237
|
+
}
|
|
241
238
|
|
|
242
|
-
|
|
243
|
-
|
|
239
|
+
const trendingData = {
|
|
240
|
+
date: today,
|
|
241
|
+
generated_at: new Date().toISOString(),
|
|
242
|
+
total_items: finalTrending.length,
|
|
243
|
+
items: finalTrending.map((item, index) => ({
|
|
244
|
+
rank: index + 1,
|
|
245
|
+
score: item.combined_score,
|
|
246
|
+
sources: getItemSources(item),
|
|
247
|
+
title: item.title,
|
|
248
|
+
url: item.url || item.link,
|
|
249
|
+
summary: extractSummary(item),
|
|
250
|
+
keywords: extractKeywords(item),
|
|
251
|
+
engagement: item.engagement || item.metadata || {}
|
|
252
|
+
}))
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
fs.writeFileSync(path.join(todayFolder, 'trending.json'), JSON.stringify(trendingData, null, 2));
|
|
256
|
+
logger.success(`✅ Generated: trending.json (${finalTrending.length} items)`);
|
|
257
|
+
} catch (err) {
|
|
258
|
+
// Vercel ephemeral filesystem - ignore write errors
|
|
259
|
+
if (err.code !== 'ENOENT' && err.code !== 'EACCES') {
|
|
260
|
+
logger.warn(`generateCombinedFiles skipped: ${err.message}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
244
263
|
}
|
|
245
264
|
|
|
246
265
|
function calculateScore(item) {
|