@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.
Files changed (2) hide show
  1. package/package.json +2 -1
  2. 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.4",
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 { createRequire } from 'module';
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
- const configPath = path.join(PKG_ROOT, 'config/sources.json');
131
- const content = fs.readFileSync(configPath, 'utf-8');
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
- const todayFolder = path.join(__dirname, '../data', today);
141
- if (!fs.existsSync(todayFolder)) {
142
- fs.mkdirSync(todayFolder, { recursive: true });
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
- const todayFolder = path.join(__dirname, '../data', today);
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
- // Save all.json
198
- const allData = {
199
- date: today,
200
- generated_at: new Date().toISOString(),
201
- total_items: allItems.length,
202
- sources: results.sources,
203
- items: allItems
204
- };
205
- fs.writeFileSync(path.join(todayFolder, 'all.json'), JSON.stringify(allData, null, 2));
206
-
207
- // Generate trending.json (top 20 by score with source diversity)
208
- const scoredItems = allItems
209
- .filter(item => item.metadata?.score || item.engagement?.upvotes || item.engagement?.points || 0)
210
- .map(item => ({ ...item, combined_score: calculateScore(item) }))
211
- .sort((a, b) => b.combined_score - a.combined_score);
212
-
213
- // Apply source diversity: max 5 items per source
214
- const trendingBySource = {};
215
- const finalTrending = [];
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
- const trendingData = {
227
- date: today,
228
- generated_at: new Date().toISOString(),
229
- total_items: finalTrending.length,
230
- items: finalTrending.map((item, index) => ({
231
- rank: index + 1,
232
- score: item.combined_score,
233
- sources: getItemSources(item),
234
- title: item.title,
235
- url: item.url || item.link,
236
- summary: extractSummary(item),
237
- keywords: extractKeywords(item),
238
- engagement: item.engagement || item.metadata || {}
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
- fs.writeFileSync(path.join(todayFolder, 'trending.json'), JSON.stringify(trendingData, null, 2));
243
- logger.success(`✅ Generated: trending.json (${finalTrending.length} items)`);
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) {