linkpress 0.1.0 → 0.2.0
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 +15 -0
- package/dist/ai.d.ts +3 -28
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +12 -421
- package/dist/ai.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/scraper.d.ts +2 -10
- package/dist/scraper.d.ts.map +1 -1
- package/dist/scraper.js +2 -158
- package/dist/scraper.js.map +1 -1
- package/dist/slack/client.d.ts +1 -44
- package/dist/slack/client.d.ts.map +1 -1
- package/dist/slack/client.js +1 -97
- package/dist/slack/client.js.map +1 -1
- package/dist/slack/sync.d.ts.map +1 -1
- package/dist/slack/sync.js +2 -90
- package/dist/slack/sync.js.map +1 -1
- package/dist/types.d.ts +4 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/utils.d.ts +1 -2
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -14
- package/dist/utils.js.map +1 -1
- package/package.json +2 -6
package/README.md
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
<strong>Turn your Slack links into a personal tech magazine</strong>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/linkpress"><img src="https://img.shields.io/npm/v/linkpress.svg" alt="npm version"></a>
|
|
11
|
+
<img src="https://img.shields.io/github/license/mindori/linkpress" alt="license">
|
|
12
|
+
</p>
|
|
13
|
+
|
|
9
14
|
<p align="center">
|
|
10
15
|
<img src="assets/serve.gif" alt="LinkPress Demo" width="800">
|
|
11
16
|
</p>
|
|
@@ -62,8 +67,18 @@ linkpress source add slack
|
|
|
62
67
|
<img src="assets/add.gif" alt="Connect Slack" width="800">
|
|
63
68
|
</p>
|
|
64
69
|
|
|
70
|
+
> **⚠️ Important:** When the browser opens, **DO NOT open in the Slack desktop app**.
|
|
71
|
+
> You must click **"Use Slack in your browser"** to continue.
|
|
72
|
+
> The automatic token extraction only works in the browser, not in the desktop app.
|
|
73
|
+
|
|
74
|
+
> **📝 Note:** We recommend the **Automatic** method (default). If automatic extraction fails,
|
|
75
|
+
> you can select **Manual** mode and paste tokens from your browser's DevTools.
|
|
76
|
+
> The CLI will guide you through the process.
|
|
77
|
+
|
|
65
78
|
Select the channels you want to watch. LinkPress will collect all shared links from these channels.
|
|
66
79
|
|
|
80
|
+
> **💡 Tip:** Just press Enter without selecting anything — your **Saved Messages (DM to self)** is pre-selected by default. This is the easiest way to curate links: just forward interesting articles to yourself in Slack!
|
|
81
|
+
|
|
67
82
|
|
|
68
83
|
## Step 2: Sync Links
|
|
69
84
|
|
package/dist/ai.d.ts
CHANGED
|
@@ -1,31 +1,6 @@
|
|
|
1
|
-
import type
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
tldr: string;
|
|
5
|
-
keyPoints: string[];
|
|
6
|
-
whyItMatters: string;
|
|
7
|
-
keyQuote?: string;
|
|
8
|
-
tags: string[];
|
|
9
|
-
difficulty: 'beginner' | 'intermediate' | 'advanced';
|
|
10
|
-
}
|
|
11
|
-
export type ContentType = 'article' | 'announcement' | 'discussion' | 'reference' | 'social' | 'media' | 'internal' | 'other';
|
|
12
|
-
export type TechnicalDepth = 'none' | 'shallow' | 'moderate' | 'deep' | 'expert' | 'unknown';
|
|
13
|
-
export type Actionability = 'none' | 'awareness' | 'applicable' | 'reference';
|
|
14
|
-
export interface ContentClassification {
|
|
15
|
-
contentType: ContentType;
|
|
16
|
-
technicalDepth: TechnicalDepth;
|
|
17
|
-
actionability: Actionability;
|
|
18
|
-
shouldCollect: boolean;
|
|
19
|
-
reasoning: string;
|
|
20
|
-
}
|
|
21
|
-
export interface ModelInfo {
|
|
22
|
-
id: string;
|
|
23
|
-
name: string;
|
|
24
|
-
}
|
|
25
|
-
export declare const FALLBACK_MODELS: Record<AIProvider, ModelInfo[]>;
|
|
26
|
-
export declare function fetchModels(provider: AIProvider, apiKey: string): Promise<ModelInfo[]>;
|
|
27
|
-
export declare function serializeSummary(summary: ArticleSummary): string;
|
|
28
|
-
export declare function parseSummary(summaryStr: string | undefined): ArticleSummary | null;
|
|
1
|
+
import { serializeSummary, parseSummary, fetchModels, FALLBACK_MODELS, type ArticleSummary, type ContentClassification, type ModelInfo } from '@linkpress/core';
|
|
2
|
+
export { serializeSummary, parseSummary, fetchModels, FALLBACK_MODELS };
|
|
3
|
+
export type { ArticleSummary, ContentClassification, ModelInfo };
|
|
29
4
|
export declare function summarizeArticle(title: string, content: string, url: string): Promise<ArticleSummary>;
|
|
30
5
|
export declare function classifyContent(messageText: string, url: string, title: string, description: string): Promise<ContentClassification>;
|
|
31
6
|
//# sourceMappingURL=ai.d.ts.map
|
package/dist/ai.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ai.d.ts","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,KAAK,SAAS,EAEf,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;AACxE,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC;AAYjE,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,cAAc,CAAC,CAEzB;AAED,wBAAsB,eAAe,CACnC,WAAW,EAAE,MAAM,EACnB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,qBAAqB,CAAC,CAEhC"}
|
package/dist/ai.js
CHANGED
|
@@ -1,428 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { GoogleGenerativeAI } from '@google/generative-ai';
|
|
3
|
-
import OpenAI from 'openai';
|
|
1
|
+
import { summarizeArticle as coreSummarize, classifyContent as coreClassify, serializeSummary, parseSummary, fetchModels, FALLBACK_MODELS, } from '@linkpress/core';
|
|
4
2
|
import { loadConfig } from './config.js';
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
{ id: 'claude-sonnet-4-5-20250929', name: 'Claude Sonnet 4.5' },
|
|
8
|
-
{ id: 'claude-haiku-4-5-20251001', name: 'Claude Haiku 4.5' },
|
|
9
|
-
{ id: 'claude-opus-4-5-20251101', name: 'Claude Opus 4.5' },
|
|
10
|
-
],
|
|
11
|
-
openai: [
|
|
12
|
-
{ id: 'gpt-4.1-mini', name: 'GPT-4.1 Mini' },
|
|
13
|
-
{ id: 'gpt-4.1-nano', name: 'GPT-4.1 Nano' },
|
|
14
|
-
{ id: 'gpt-4.1', name: 'GPT-4.1' },
|
|
15
|
-
{ id: 'gpt-4o-mini', name: 'GPT-4o Mini' },
|
|
16
|
-
{ id: 'gpt-4o', name: 'GPT-4o' },
|
|
17
|
-
],
|
|
18
|
-
gemini: [
|
|
19
|
-
{ id: 'gemini-3-flash-preview', name: 'Gemini 3 Flash' },
|
|
20
|
-
{ id: 'gemini-3-pro-preview', name: 'Gemini 3 Pro' },
|
|
21
|
-
{ id: 'gemini-2.5-flash', name: 'Gemini 2.5 Flash' },
|
|
22
|
-
{ id: 'gemini-2.5-pro', name: 'Gemini 2.5 Pro' },
|
|
23
|
-
],
|
|
24
|
-
};
|
|
25
|
-
export async function fetchModels(provider, apiKey) {
|
|
26
|
-
try {
|
|
27
|
-
switch (provider) {
|
|
28
|
-
case 'anthropic':
|
|
29
|
-
return await fetchAnthropicModels(apiKey);
|
|
30
|
-
case 'openai':
|
|
31
|
-
return await fetchOpenAIModels(apiKey);
|
|
32
|
-
case 'gemini':
|
|
33
|
-
return await fetchGeminiModels(apiKey);
|
|
34
|
-
default:
|
|
35
|
-
return [];
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
catch (error) {
|
|
39
|
-
console.error('Failed to fetch models:', error instanceof Error ? error.message : error);
|
|
40
|
-
return [];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
async function fetchAnthropicModels(apiKey) {
|
|
44
|
-
const response = await fetch('https://api.anthropic.com/v1/models', {
|
|
45
|
-
headers: {
|
|
46
|
-
'x-api-key': apiKey,
|
|
47
|
-
'anthropic-version': '2023-06-01',
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
if (!response.ok)
|
|
51
|
-
throw new Error(`HTTP ${response.status}`);
|
|
52
|
-
const data = await response.json();
|
|
53
|
-
return data.data
|
|
54
|
-
.filter(m => m.id.includes('claude') && !m.id.includes('instant'))
|
|
55
|
-
.map(m => ({ id: m.id, name: m.display_name || m.id }));
|
|
56
|
-
}
|
|
57
|
-
async function fetchOpenAIModels(apiKey) {
|
|
58
|
-
const response = await fetch('https://api.openai.com/v1/models', {
|
|
59
|
-
headers: {
|
|
60
|
-
'Authorization': `Bearer ${apiKey}`,
|
|
61
|
-
},
|
|
62
|
-
});
|
|
63
|
-
if (!response.ok)
|
|
64
|
-
throw new Error(`HTTP ${response.status}`);
|
|
65
|
-
const data = await response.json();
|
|
66
|
-
const validPrefixes = ['gpt-4', 'gpt-5', 'o1', 'o3', 'o4'];
|
|
67
|
-
const excludePatterns = ['realtime', 'audio', 'vision', 'instruct', 'turbo', 'preview'];
|
|
68
|
-
return data.data
|
|
69
|
-
.filter(m => {
|
|
70
|
-
const id = m.id.toLowerCase();
|
|
71
|
-
const hasValidPrefix = validPrefixes.some(p => id.startsWith(p));
|
|
72
|
-
const hasExcluded = excludePatterns.some(p => id.includes(p));
|
|
73
|
-
return hasValidPrefix && !hasExcluded;
|
|
74
|
-
})
|
|
75
|
-
.map(m => ({ id: m.id, name: m.id }))
|
|
76
|
-
.sort((a, b) => b.id.localeCompare(a.id));
|
|
77
|
-
}
|
|
78
|
-
async function fetchGeminiModels(apiKey) {
|
|
79
|
-
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models?key=${apiKey}`);
|
|
80
|
-
if (!response.ok)
|
|
81
|
-
throw new Error(`HTTP ${response.status}`);
|
|
82
|
-
const data = await response.json();
|
|
83
|
-
return data.models
|
|
84
|
-
.filter(m => m.supportedGenerationMethods?.includes('generateContent'))
|
|
85
|
-
.filter(m => m.name.includes('gemini'))
|
|
86
|
-
.map(m => ({
|
|
87
|
-
id: m.name.replace('models/', ''),
|
|
88
|
-
name: m.displayName || m.name.replace('models/', ''),
|
|
89
|
-
}))
|
|
90
|
-
.sort((a, b) => {
|
|
91
|
-
const aVersion = a.id.match(/\d+(\.\d+)?/)?.[0] || '0';
|
|
92
|
-
const bVersion = b.id.match(/\d+(\.\d+)?/)?.[0] || '0';
|
|
93
|
-
return parseFloat(bVersion) - parseFloat(aVersion);
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
export function serializeSummary(summary) {
|
|
97
|
-
return JSON.stringify(summary);
|
|
98
|
-
}
|
|
99
|
-
export function parseSummary(summaryStr) {
|
|
100
|
-
if (!summaryStr)
|
|
101
|
-
return null;
|
|
102
|
-
try {
|
|
103
|
-
const parsed = JSON.parse(summaryStr);
|
|
104
|
-
if (parsed.headline && parsed.tldr) {
|
|
105
|
-
return parsed;
|
|
106
|
-
}
|
|
107
|
-
return {
|
|
108
|
-
headline: parsed.hook || summaryStr,
|
|
109
|
-
tldr: parsed.summary || summaryStr,
|
|
110
|
-
keyPoints: [],
|
|
111
|
-
whyItMatters: '',
|
|
112
|
-
tags: parsed.tags || [],
|
|
113
|
-
difficulty: parsed.difficulty || 'intermediate',
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
return {
|
|
118
|
-
headline: summaryStr,
|
|
119
|
-
tldr: summaryStr,
|
|
120
|
-
keyPoints: [],
|
|
121
|
-
whyItMatters: '',
|
|
122
|
-
tags: [],
|
|
123
|
-
difficulty: 'intermediate',
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
function buildPrompt(title, content, url, language) {
|
|
128
|
-
const koreanRule = language === '한국어'
|
|
129
|
-
? '\n6. KOREAN ONLY: Use formal polite speech (존댓말/합쇼체) consistently. End sentences with -습니다, -입니다, -됩니다. NEVER use casual speech (반말).'
|
|
130
|
-
: '';
|
|
131
|
-
return `You are a SENIOR TECH JOURNALIST at a prestigious developer magazine.
|
|
132
|
-
Your job is to create compelling, newspaper-style briefings that developers actually want to read.
|
|
133
|
-
|
|
134
|
-
---
|
|
135
|
-
|
|
136
|
-
INPUT:
|
|
137
|
-
- Title: ${title}
|
|
138
|
-
- URL: ${url}
|
|
139
|
-
- Content: ${content.substring(0, 6000)}
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
TASK: Create a briefing in JSON format.
|
|
144
|
-
|
|
145
|
-
{
|
|
146
|
-
"headline": "Catchy, newspaper-style headline (max 15 words)",
|
|
147
|
-
"tldr": "One-sentence summary for busy readers",
|
|
148
|
-
"keyPoints": [
|
|
149
|
-
"First key point (one sentence)",
|
|
150
|
-
"Second key point (one sentence)",
|
|
151
|
-
"Third key point (one sentence)"
|
|
152
|
-
],
|
|
153
|
-
"whyItMatters": "Why this matters to developers/readers (1-2 sentences)",
|
|
154
|
-
"keyQuote": "Most impactful quote from the article (if any, otherwise empty string)",
|
|
155
|
-
"tags": ["tag1", "tag2", "tag3"],
|
|
156
|
-
"difficulty": "beginner|intermediate|advanced"
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
CRITICAL RULES:
|
|
162
|
-
1. WRITE EVERYTHING IN ${language}. This is NOT optional. The output MUST be in ${language}.
|
|
163
|
-
2. Headline should be ATTENTION-GRABBING but accurate—no clickbait lies.
|
|
164
|
-
3. Key points should be ACTIONABLE insights, not just descriptions.
|
|
165
|
-
4. Tags: use technical topics (frontend, backend, ai, devops, database, security, career, etc.)
|
|
166
|
-
5. Difficulty: beginner (anyone can understand), intermediate (some experience needed), advanced (experts only)${koreanRule}
|
|
167
|
-
|
|
168
|
-
OUTPUT: JSON only, no explanation outside JSON.`;
|
|
169
|
-
}
|
|
170
|
-
async function callAnthropic(apiKey, model, prompt) {
|
|
171
|
-
const client = new Anthropic({ apiKey });
|
|
172
|
-
const response = await client.messages.create({
|
|
173
|
-
model,
|
|
174
|
-
max_tokens: 800,
|
|
175
|
-
messages: [{ role: 'user', content: prompt }],
|
|
176
|
-
});
|
|
177
|
-
return response.content[0].type === 'text' ? response.content[0].text : '';
|
|
178
|
-
}
|
|
179
|
-
async function callOpenAI(apiKey, model, prompt) {
|
|
180
|
-
const client = new OpenAI({ apiKey });
|
|
181
|
-
const response = await client.chat.completions.create({
|
|
182
|
-
model,
|
|
183
|
-
max_tokens: 800,
|
|
184
|
-
messages: [{ role: 'user', content: prompt }],
|
|
185
|
-
});
|
|
186
|
-
return response.choices[0]?.message?.content || '';
|
|
187
|
-
}
|
|
188
|
-
async function callGemini(apiKey, model, prompt) {
|
|
189
|
-
const genAI = new GoogleGenerativeAI(apiKey);
|
|
190
|
-
const geminiModel = genAI.getGenerativeModel({ model });
|
|
191
|
-
const result = await geminiModel.generateContent(prompt);
|
|
192
|
-
return result.response.text();
|
|
193
|
-
}
|
|
194
|
-
export async function summarizeArticle(title, content, url) {
|
|
3
|
+
export { serializeSummary, parseSummary, fetchModels, FALLBACK_MODELS };
|
|
4
|
+
function getAIConfig() {
|
|
195
5
|
const config = loadConfig();
|
|
196
|
-
if (!config.ai.apiKey) {
|
|
197
|
-
return getDefaultSummary(title, url);
|
|
198
|
-
}
|
|
199
|
-
const provider = config.ai.provider;
|
|
200
|
-
const model = config.ai.model;
|
|
201
|
-
const language = config.ai.language || 'English';
|
|
202
|
-
const prompt = buildPrompt(title, content, url, language);
|
|
203
|
-
try {
|
|
204
|
-
let text = '';
|
|
205
|
-
switch (provider) {
|
|
206
|
-
case 'anthropic':
|
|
207
|
-
text = await callAnthropic(config.ai.apiKey, model, prompt);
|
|
208
|
-
break;
|
|
209
|
-
case 'openai':
|
|
210
|
-
text = await callOpenAI(config.ai.apiKey, model, prompt);
|
|
211
|
-
break;
|
|
212
|
-
case 'gemini':
|
|
213
|
-
text = await callGemini(config.ai.apiKey, model, prompt);
|
|
214
|
-
break;
|
|
215
|
-
default:
|
|
216
|
-
return getDefaultSummary(title, url);
|
|
217
|
-
}
|
|
218
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
219
|
-
if (!jsonMatch) {
|
|
220
|
-
return getDefaultSummary(title, url);
|
|
221
|
-
}
|
|
222
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
223
|
-
return {
|
|
224
|
-
headline: parsed.headline || title,
|
|
225
|
-
tldr: parsed.tldr || '',
|
|
226
|
-
keyPoints: Array.isArray(parsed.keyPoints) ? parsed.keyPoints.slice(0, 3) : [],
|
|
227
|
-
whyItMatters: parsed.whyItMatters || '',
|
|
228
|
-
keyQuote: parsed.keyQuote || '',
|
|
229
|
-
tags: Array.isArray(parsed.tags) ? parsed.tags.slice(0, 5) : [],
|
|
230
|
-
difficulty: ['beginner', 'intermediate', 'advanced'].includes(parsed.difficulty)
|
|
231
|
-
? parsed.difficulty
|
|
232
|
-
: 'intermediate',
|
|
233
|
-
};
|
|
234
|
-
}
|
|
235
|
-
catch (error) {
|
|
236
|
-
console.error('AI summarization failed:', error instanceof Error ? error.message : String(error));
|
|
237
|
-
return getDefaultSummary(title, url);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
function buildClassificationPrompt(messageText, url, title, description) {
|
|
241
|
-
return `You filter links for a tech newsletter. DEFAULT ACTION: COLLECT.
|
|
242
|
-
|
|
243
|
-
INPUT:
|
|
244
|
-
- URL: ${url}
|
|
245
|
-
- Context: ${messageText || '(none)'}
|
|
246
|
-
- Title: ${title || '(none)'}
|
|
247
|
-
- Description: ${description || '(none)'}
|
|
248
|
-
|
|
249
|
-
---
|
|
250
|
-
|
|
251
|
-
EXCLUDE ONLY these specific categories:
|
|
252
|
-
|
|
253
|
-
1. INTERNAL TOOLS (workspace/productivity apps, not public content):
|
|
254
|
-
- Google Docs/Sheets/Slides/Drive (docs.google.com, drive.google.com, share.google)
|
|
255
|
-
- Notion workspace pages (notion.so with private content)
|
|
256
|
-
- Figma files (figma.com)
|
|
257
|
-
- Jira/Confluence (atlassian.net)
|
|
258
|
-
- Canva designs (canva.com/design)
|
|
259
|
-
- Slack permalinks
|
|
260
|
-
|
|
261
|
-
2. VIDEO/AUDIO (reading-focused newsletter):
|
|
262
|
-
- YouTube (youtube.com, youtu.be)
|
|
263
|
-
- Vimeo, Twitch, podcasts
|
|
264
|
-
|
|
265
|
-
3. TWITTER/X ONLY (not scrapable):
|
|
266
|
-
- x.com, twitter.com
|
|
267
|
-
- NOTE: LinkedIn is NOT excluded. LinkedIn posts ARE scrapable.
|
|
268
|
-
|
|
269
|
-
4. AUTH/TRANSACTIONAL pages:
|
|
270
|
-
- Login pages, confirmation tokens, password resets
|
|
271
|
-
- URLs with "confirm", "token=", "verify", "unsubscribe"
|
|
272
|
-
|
|
273
|
-
5. OBVIOUS NON-CONTENT:
|
|
274
|
-
- Image files (.png, .jpg, .gif direct links)
|
|
275
|
-
- File downloads (.zip, .pdf direct links)
|
|
276
|
-
|
|
277
|
-
---
|
|
278
|
-
|
|
279
|
-
ALWAYS COLLECT (even without metadata):
|
|
280
|
-
|
|
281
|
-
- GitHub repos/gists (github.com, gist.github.com) - developers share code there
|
|
282
|
-
- LinkedIn posts (linkedin.com) - professionals share knowledge, IS scrapable
|
|
283
|
-
- Blog platforms (medium.com, dev.to, substack.com, brunch.co.kr, velog.io, tistory.com)
|
|
284
|
-
- Tech news (news.hada.io, news.ycombinator.com, techcrunch.com)
|
|
285
|
-
- Any unknown domain - might be interesting, we'll scrape and find out
|
|
286
|
-
- Product/tool pages - developers share useful tools
|
|
287
|
-
|
|
288
|
-
---
|
|
289
|
-
|
|
290
|
-
CRITICAL: Missing metadata (no title/description) is NOT a reason to exclude.
|
|
291
|
-
We will scrape the content later. If someone shared it, it's probably worth checking.
|
|
292
|
-
|
|
293
|
-
OUTPUT (JSON only):
|
|
294
|
-
{
|
|
295
|
-
"content_type": "article|social|reference|internal|media|other",
|
|
296
|
-
"technical_depth": "shallow|moderate|deep|unknown",
|
|
297
|
-
"should_collect": true|false,
|
|
298
|
-
"reasoning": "Brief reason"
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
When uncertain, set should_collect: true.`;
|
|
302
|
-
}
|
|
303
|
-
export async function classifyContent(messageText, url, title, description) {
|
|
304
|
-
const config = loadConfig();
|
|
305
|
-
if (!config.ai.apiKey) {
|
|
306
|
-
return getDefaultClassification(url);
|
|
307
|
-
}
|
|
308
|
-
const provider = config.ai.provider;
|
|
309
|
-
const model = config.ai.model;
|
|
310
|
-
const prompt = buildClassificationPrompt(messageText, url, title, description);
|
|
311
|
-
try {
|
|
312
|
-
let text = '';
|
|
313
|
-
switch (provider) {
|
|
314
|
-
case 'anthropic':
|
|
315
|
-
text = await callAnthropic(config.ai.apiKey, model, prompt);
|
|
316
|
-
break;
|
|
317
|
-
case 'openai':
|
|
318
|
-
text = await callOpenAI(config.ai.apiKey, model, prompt);
|
|
319
|
-
break;
|
|
320
|
-
case 'gemini':
|
|
321
|
-
text = await callGemini(config.ai.apiKey, model, prompt);
|
|
322
|
-
break;
|
|
323
|
-
default:
|
|
324
|
-
return getDefaultClassification(url);
|
|
325
|
-
}
|
|
326
|
-
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
327
|
-
if (!jsonMatch) {
|
|
328
|
-
return getDefaultClassification(url);
|
|
329
|
-
}
|
|
330
|
-
const parsed = JSON.parse(jsonMatch[0]);
|
|
331
|
-
return {
|
|
332
|
-
contentType: parsed.content_type,
|
|
333
|
-
technicalDepth: parsed.technical_depth,
|
|
334
|
-
actionability: parsed.actionability || 'awareness',
|
|
335
|
-
shouldCollect: parsed.should_collect === true,
|
|
336
|
-
reasoning: parsed.reasoning || '',
|
|
337
|
-
};
|
|
338
|
-
}
|
|
339
|
-
catch (error) {
|
|
340
|
-
console.error('AI classification failed:', error instanceof Error ? error.message : String(error));
|
|
341
|
-
return getDefaultClassification(url);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
function getDefaultClassification(url) {
|
|
345
|
-
const urlLower = url.toLowerCase();
|
|
346
|
-
const internalPatterns = [
|
|
347
|
-
'docs.google.com', 'drive.google.com', 'share.google',
|
|
348
|
-
'sheets.google.com', 'slides.google.com',
|
|
349
|
-
'notion.so', 'figma.com', 'canva.com/design',
|
|
350
|
-
'atlassian.net', 'jira', 'confluence',
|
|
351
|
-
'slack.com/archives',
|
|
352
|
-
];
|
|
353
|
-
if (internalPatterns.some(p => urlLower.includes(p))) {
|
|
354
|
-
return {
|
|
355
|
-
contentType: 'internal',
|
|
356
|
-
technicalDepth: 'none',
|
|
357
|
-
actionability: 'none',
|
|
358
|
-
shouldCollect: false,
|
|
359
|
-
reasoning: 'Internal workspace tool',
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
if (urlLower.includes('youtube.com') || urlLower.includes('youtu.be') || urlLower.includes('vimeo.com')) {
|
|
363
|
-
return {
|
|
364
|
-
contentType: 'media',
|
|
365
|
-
technicalDepth: 'moderate',
|
|
366
|
-
actionability: 'awareness',
|
|
367
|
-
shouldCollect: false,
|
|
368
|
-
reasoning: 'Video content excluded',
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
if (urlLower.includes('x.com') || urlLower.includes('twitter.com')) {
|
|
372
|
-
return {
|
|
373
|
-
contentType: 'social',
|
|
374
|
-
technicalDepth: 'unknown',
|
|
375
|
-
actionability: 'awareness',
|
|
376
|
-
shouldCollect: false,
|
|
377
|
-
reasoning: 'Twitter/X excluded - not scrapable',
|
|
378
|
-
};
|
|
379
|
-
}
|
|
380
|
-
if (urlLower.includes('/confirm') || urlLower.includes('token=') || urlLower.includes('/verify') || urlLower.includes('/unsubscribe')) {
|
|
381
|
-
return {
|
|
382
|
-
contentType: 'other',
|
|
383
|
-
technicalDepth: 'none',
|
|
384
|
-
actionability: 'none',
|
|
385
|
-
shouldCollect: false,
|
|
386
|
-
reasoning: 'Auth/transactional page',
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
6
|
return {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
reasoning: 'Default: collect and scrape',
|
|
7
|
+
provider: config.ai.provider,
|
|
8
|
+
apiKey: config.ai.apiKey || '',
|
|
9
|
+
model: config.ai.model,
|
|
10
|
+
language: config.ai.language,
|
|
395
11
|
};
|
|
396
12
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
catch {
|
|
403
|
-
hostname = 'unknown';
|
|
404
|
-
}
|
|
405
|
-
const tags = [];
|
|
406
|
-
const urlLower = url.toLowerCase();
|
|
407
|
-
if (urlLower.includes('github.com'))
|
|
408
|
-
tags.push('github');
|
|
409
|
-
if (urlLower.includes('medium.com'))
|
|
410
|
-
tags.push('blog');
|
|
411
|
-
if (urlLower.includes('dev.to'))
|
|
412
|
-
tags.push('blog');
|
|
413
|
-
if (urlLower.includes('youtube.com') || urlLower.includes('youtu.be'))
|
|
414
|
-
tags.push('video');
|
|
415
|
-
if (urlLower.includes('linkedin.com'))
|
|
416
|
-
tags.push('linkedin');
|
|
417
|
-
if (urlLower.includes('news.hada.io'))
|
|
418
|
-
tags.push('news');
|
|
419
|
-
return {
|
|
420
|
-
headline: title || `Article from ${hostname}`,
|
|
421
|
-
tldr: title || `Content from ${hostname}`,
|
|
422
|
-
keyPoints: [],
|
|
423
|
-
whyItMatters: '',
|
|
424
|
-
tags,
|
|
425
|
-
difficulty: 'intermediate',
|
|
426
|
-
};
|
|
13
|
+
export async function summarizeArticle(title, content, url) {
|
|
14
|
+
return coreSummarize(title, content, url, getAIConfig());
|
|
15
|
+
}
|
|
16
|
+
export async function classifyContent(messageText, url, title, description) {
|
|
17
|
+
return coreClassify(messageText, url, title, description, getAIConfig());
|
|
427
18
|
}
|
|
428
19
|
//# sourceMappingURL=ai.js.map
|
package/dist/ai.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ai.js","sourceRoot":"","sources":["../src/ai.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,IAAI,aAAa,EACjC,eAAe,IAAI,YAAY,EAC/B,gBAAgB,EAChB,YAAY,EACZ,WAAW,EACX,eAAe,GAKhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,WAAW,EAAE,eAAe,EAAE,CAAC;AAGxE,SAAS,WAAW;IAClB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,OAAO;QACL,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ;QAC5B,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,IAAI,EAAE;QAC9B,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,KAAK;QACtB,QAAQ,EAAE,MAAM,CAAC,EAAE,CAAC,QAAQ;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAa,EACb,OAAe,EACf,GAAW;IAEX,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,WAAmB,EACnB,GAAW,EACX,KAAa,EACb,WAAmB;IAEnB,OAAO,YAAY,CAAC,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,CAAC,CAAC;AAC3E,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,7 +4,7 @@ import { initCommand, addCommand, listCommand, sourceCommand, syncCommand, gener
|
|
|
4
4
|
program
|
|
5
5
|
.name('linkpress')
|
|
6
6
|
.description('Turn your Slack links into a personal tech magazine')
|
|
7
|
-
.version('0.
|
|
7
|
+
.version('0.2.0');
|
|
8
8
|
program.addCommand(initCommand);
|
|
9
9
|
program.addCommand(addCommand);
|
|
10
10
|
program.addCommand(listCommand);
|
package/dist/scraper.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
description: string;
|
|
4
|
-
content: string;
|
|
5
|
-
siteName?: string;
|
|
6
|
-
image?: string;
|
|
7
|
-
sourceLabel?: string;
|
|
8
|
-
}
|
|
9
|
-
export declare function scrapeUrl(url: string): Promise<ScrapedContent>;
|
|
10
|
-
export declare function estimateReadingTime(content: string): number;
|
|
1
|
+
export { scrapeUrl, type ScrapedContent } from '@linkpress/core';
|
|
2
|
+
export { estimateReadingTime } from '@linkpress/core';
|
|
11
3
|
//# sourceMappingURL=scraper.d.ts.map
|
package/dist/scraper.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scraper.d.ts","sourceRoot":"","sources":["../src/scraper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scraper.d.ts","sourceRoot":"","sources":["../src/scraper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/scraper.js
CHANGED
|
@@ -1,159 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
3
|
-
try {
|
|
4
|
-
const response = await fetch(url, {
|
|
5
|
-
headers: {
|
|
6
|
-
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
7
|
-
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
|
|
8
|
-
'Accept-Language': 'en-US,en;q=0.9,ko;q=0.8',
|
|
9
|
-
},
|
|
10
|
-
redirect: 'follow',
|
|
11
|
-
signal: AbortSignal.timeout(15000),
|
|
12
|
-
});
|
|
13
|
-
if (!response.ok) {
|
|
14
|
-
throw new Error(`HTTP ${response.status}`);
|
|
15
|
-
}
|
|
16
|
-
const html = await response.text();
|
|
17
|
-
return parseHtml(html, url);
|
|
18
|
-
}
|
|
19
|
-
catch (error) {
|
|
20
|
-
if (error instanceof Error) {
|
|
21
|
-
throw new Error(`Failed to scrape ${url}: ${error.message}`);
|
|
22
|
-
}
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
function parseHtml(html, url) {
|
|
27
|
-
const $ = cheerio.load(html);
|
|
28
|
-
$('script, style, nav, footer, header, aside, .ads, .advertisement, .sidebar').remove();
|
|
29
|
-
const title = $('meta[property="og:title"]').attr('content') ||
|
|
30
|
-
$('meta[name="twitter:title"]').attr('content') ||
|
|
31
|
-
$('title').text() ||
|
|
32
|
-
'';
|
|
33
|
-
const description = $('meta[property="og:description"]').attr('content') ||
|
|
34
|
-
$('meta[name="description"]').attr('content') ||
|
|
35
|
-
$('meta[name="twitter:description"]').attr('content') ||
|
|
36
|
-
'';
|
|
37
|
-
const siteName = $('meta[property="og:site_name"]').attr('content') ||
|
|
38
|
-
new URL(url).hostname.replace('www.', '');
|
|
39
|
-
const image = extractImage($, url);
|
|
40
|
-
const sourceLabel = detectSourceLabel(url);
|
|
41
|
-
let content = '';
|
|
42
|
-
const articleSelectors = [
|
|
43
|
-
'article',
|
|
44
|
-
'[role="main"]',
|
|
45
|
-
'main',
|
|
46
|
-
'.post-content',
|
|
47
|
-
'.article-content',
|
|
48
|
-
'.entry-content',
|
|
49
|
-
'.content',
|
|
50
|
-
'#content',
|
|
51
|
-
];
|
|
52
|
-
for (const selector of articleSelectors) {
|
|
53
|
-
const element = $(selector);
|
|
54
|
-
if (element.length > 0) {
|
|
55
|
-
content = element.text();
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
if (!content) {
|
|
60
|
-
content = $('body').text();
|
|
61
|
-
}
|
|
62
|
-
content = content
|
|
63
|
-
.replace(/\s+/g, ' ')
|
|
64
|
-
.replace(/\n\s*\n/g, '\n')
|
|
65
|
-
.trim()
|
|
66
|
-
.substring(0, 10000);
|
|
67
|
-
return {
|
|
68
|
-
title: title.trim(),
|
|
69
|
-
description: description.trim(),
|
|
70
|
-
content,
|
|
71
|
-
siteName,
|
|
72
|
-
image,
|
|
73
|
-
sourceLabel,
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
function extractImage($, url) {
|
|
77
|
-
const ogImage = $('meta[property="og:image"]').attr('content');
|
|
78
|
-
if (ogImage) {
|
|
79
|
-
return resolveUrl(ogImage, url);
|
|
80
|
-
}
|
|
81
|
-
const twitterImage = $('meta[name="twitter:image"]').attr('content');
|
|
82
|
-
if (twitterImage) {
|
|
83
|
-
return resolveUrl(twitterImage, url);
|
|
84
|
-
}
|
|
85
|
-
const articleSelectors = ['article', '[role="main"]', 'main', '.post-content', '.article-content'];
|
|
86
|
-
for (const selector of articleSelectors) {
|
|
87
|
-
const img = $(`${selector} img`).first();
|
|
88
|
-
const src = img.attr('src') || img.attr('data-src');
|
|
89
|
-
if (src && !isIconOrLogo(src)) {
|
|
90
|
-
return resolveUrl(src, url);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
94
|
-
}
|
|
95
|
-
function resolveUrl(imgUrl, baseUrl) {
|
|
96
|
-
if (imgUrl.startsWith('http://') || imgUrl.startsWith('https://')) {
|
|
97
|
-
return imgUrl;
|
|
98
|
-
}
|
|
99
|
-
if (imgUrl.startsWith('//')) {
|
|
100
|
-
return 'https:' + imgUrl;
|
|
101
|
-
}
|
|
102
|
-
try {
|
|
103
|
-
return new URL(imgUrl, baseUrl).href;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return imgUrl;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function isIconOrLogo(src) {
|
|
110
|
-
const lower = src.toLowerCase();
|
|
111
|
-
return lower.includes('logo') ||
|
|
112
|
-
lower.includes('icon') ||
|
|
113
|
-
lower.includes('avatar') ||
|
|
114
|
-
lower.includes('favicon') ||
|
|
115
|
-
lower.includes('badge') ||
|
|
116
|
-
lower.endsWith('.svg') ||
|
|
117
|
-
lower.includes('1x1') ||
|
|
118
|
-
lower.includes('pixel');
|
|
119
|
-
}
|
|
120
|
-
function detectSourceLabel(url) {
|
|
121
|
-
const hostname = new URL(url).hostname.toLowerCase();
|
|
122
|
-
const labelMap = {
|
|
123
|
-
'medium.com': 'Blog',
|
|
124
|
-
'dev.to': 'Blog',
|
|
125
|
-
'hashnode.dev': 'Blog',
|
|
126
|
-
'velog.io': 'Blog',
|
|
127
|
-
'tistory.com': 'Blog',
|
|
128
|
-
'brunch.co.kr': 'Blog',
|
|
129
|
-
'substack.com': 'Newsletter',
|
|
130
|
-
'github.com': 'GitHub',
|
|
131
|
-
'github.io': 'Blog',
|
|
132
|
-
'linkedin.com': 'LinkedIn',
|
|
133
|
-
'twitter.com': 'Twitter',
|
|
134
|
-
'x.com': 'Twitter',
|
|
135
|
-
'reddit.com': 'Reddit',
|
|
136
|
-
'news.ycombinator.com': 'HackerNews',
|
|
137
|
-
'stackoverflow.com': 'StackOverflow',
|
|
138
|
-
'youtube.com': 'YouTube',
|
|
139
|
-
'youtu.be': 'YouTube',
|
|
140
|
-
'notion.so': 'Notion',
|
|
141
|
-
'notion.site': 'Notion',
|
|
142
|
-
'news.hada.io': 'News',
|
|
143
|
-
};
|
|
144
|
-
for (const [domain, label] of Object.entries(labelMap)) {
|
|
145
|
-
if (hostname.includes(domain)) {
|
|
146
|
-
return label;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
if (hostname.includes('blog') || url.includes('/blog/')) {
|
|
150
|
-
return 'Blog';
|
|
151
|
-
}
|
|
152
|
-
return 'Article';
|
|
153
|
-
}
|
|
154
|
-
export function estimateReadingTime(content) {
|
|
155
|
-
const wordsPerMinute = 200;
|
|
156
|
-
const wordCount = content.split(/\s+/).length;
|
|
157
|
-
return Math.max(1, Math.ceil(wordCount / wordsPerMinute));
|
|
158
|
-
}
|
|
1
|
+
export { scrapeUrl } from '@linkpress/core';
|
|
2
|
+
export { estimateReadingTime } from '@linkpress/core';
|
|
159
3
|
//# sourceMappingURL=scraper.js.map
|
package/dist/scraper.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scraper.js","sourceRoot":"","sources":["../src/scraper.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"scraper.js","sourceRoot":"","sources":["../src/scraper.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAuB,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/slack/client.d.ts
CHANGED
|
@@ -1,45 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
token: string;
|
|
3
|
-
cookie: string;
|
|
4
|
-
}
|
|
5
|
-
export interface SlackUser {
|
|
6
|
-
id: string;
|
|
7
|
-
name: string;
|
|
8
|
-
realName: string;
|
|
9
|
-
team: string;
|
|
10
|
-
}
|
|
11
|
-
export interface SlackConversation {
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
isPrivate: boolean;
|
|
15
|
-
isIm: boolean;
|
|
16
|
-
isMpim: boolean;
|
|
17
|
-
user?: string;
|
|
18
|
-
}
|
|
19
|
-
export interface SlackMessage {
|
|
20
|
-
ts: string;
|
|
21
|
-
text: string;
|
|
22
|
-
user?: string;
|
|
23
|
-
type: string;
|
|
24
|
-
}
|
|
25
|
-
export declare class SlackClient {
|
|
26
|
-
private token;
|
|
27
|
-
private cookie;
|
|
28
|
-
private baseUrl;
|
|
29
|
-
constructor(auth: SlackAuthConfig);
|
|
30
|
-
private request;
|
|
31
|
-
testAuth(): Promise<SlackUser>;
|
|
32
|
-
getConversations(): Promise<SlackConversation[]>;
|
|
33
|
-
getUserInfo(userId: string): Promise<{
|
|
34
|
-
id: string;
|
|
35
|
-
name: string;
|
|
36
|
-
realName: string;
|
|
37
|
-
}>;
|
|
38
|
-
getConversationHistory(channelId: string, options?: {
|
|
39
|
-
limit?: number;
|
|
40
|
-
oldest?: string;
|
|
41
|
-
latest?: string;
|
|
42
|
-
}): Promise<SlackMessage[]>;
|
|
43
|
-
generateSourceId(): string;
|
|
44
|
-
}
|
|
1
|
+
export { SlackClient, type SlackAuthConfig, type SlackUser, type SlackConversation, type SlackMessage, } from '@linkpress/core';
|
|
45
2
|
//# sourceMappingURL=client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/slack/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/slack/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,KAAK,eAAe,EACpB,KAAK,SAAS,EACd,KAAK,iBAAiB,EACtB,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC"}
|
package/dist/slack/client.js
CHANGED
|
@@ -1,98 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export class SlackClient {
|
|
3
|
-
token;
|
|
4
|
-
cookie;
|
|
5
|
-
baseUrl = 'https://slack.com/api';
|
|
6
|
-
constructor(auth) {
|
|
7
|
-
this.token = auth.token;
|
|
8
|
-
this.cookie = auth.cookie;
|
|
9
|
-
}
|
|
10
|
-
async request(method, params = {}) {
|
|
11
|
-
const url = new URL(`${this.baseUrl}/${method}`);
|
|
12
|
-
const response = await fetch(url, {
|
|
13
|
-
method: 'POST',
|
|
14
|
-
headers: {
|
|
15
|
-
'Authorization': `Bearer ${this.token}`,
|
|
16
|
-
'Cookie': `d=${this.cookie}`,
|
|
17
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
18
|
-
},
|
|
19
|
-
body: new URLSearchParams(params).toString(),
|
|
20
|
-
});
|
|
21
|
-
const data = await response.json();
|
|
22
|
-
if (!data.ok) {
|
|
23
|
-
throw new Error(`Slack API error: ${data.error || 'Unknown error'}`);
|
|
24
|
-
}
|
|
25
|
-
return data;
|
|
26
|
-
}
|
|
27
|
-
async testAuth() {
|
|
28
|
-
const response = await this.request('auth.test');
|
|
29
|
-
return {
|
|
30
|
-
id: response.user_id,
|
|
31
|
-
name: response.user,
|
|
32
|
-
realName: response.user,
|
|
33
|
-
team: response.team,
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
async getConversations() {
|
|
37
|
-
const conversations = [];
|
|
38
|
-
let cursor;
|
|
39
|
-
do {
|
|
40
|
-
const params = {
|
|
41
|
-
types: 'public_channel,private_channel,mpim,im',
|
|
42
|
-
limit: '200',
|
|
43
|
-
exclude_archived: 'true',
|
|
44
|
-
};
|
|
45
|
-
if (cursor) {
|
|
46
|
-
params.cursor = cursor;
|
|
47
|
-
}
|
|
48
|
-
const response = await this.request('conversations.list', params);
|
|
49
|
-
for (const channel of response.channels) {
|
|
50
|
-
conversations.push({
|
|
51
|
-
id: channel.id,
|
|
52
|
-
name: channel.name || channel.id,
|
|
53
|
-
isPrivate: channel.is_private,
|
|
54
|
-
isIm: channel.is_im,
|
|
55
|
-
isMpim: channel.is_mpim,
|
|
56
|
-
user: channel.user,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
cursor = response.response_metadata?.next_cursor;
|
|
60
|
-
} while (cursor);
|
|
61
|
-
return conversations;
|
|
62
|
-
}
|
|
63
|
-
async getUserInfo(userId) {
|
|
64
|
-
const response = await this.request('users.info', { user: userId });
|
|
65
|
-
return {
|
|
66
|
-
id: response.user.id,
|
|
67
|
-
name: response.user.name,
|
|
68
|
-
realName: response.user.real_name,
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
async getConversationHistory(channelId, options = {}) {
|
|
72
|
-
const messages = [];
|
|
73
|
-
let cursor;
|
|
74
|
-
let fetched = 0;
|
|
75
|
-
const limit = options.limit || 100;
|
|
76
|
-
do {
|
|
77
|
-
const params = {
|
|
78
|
-
channel: channelId,
|
|
79
|
-
limit: Math.min(limit - fetched, 200).toString(),
|
|
80
|
-
};
|
|
81
|
-
if (cursor)
|
|
82
|
-
params.cursor = cursor;
|
|
83
|
-
if (options.oldest)
|
|
84
|
-
params.oldest = options.oldest;
|
|
85
|
-
if (options.latest)
|
|
86
|
-
params.latest = options.latest;
|
|
87
|
-
const response = await this.request('conversations.history', params);
|
|
88
|
-
messages.push(...response.messages);
|
|
89
|
-
fetched += response.messages.length;
|
|
90
|
-
cursor = response.response_metadata?.next_cursor;
|
|
91
|
-
} while (cursor && fetched < limit);
|
|
92
|
-
return messages;
|
|
93
|
-
}
|
|
94
|
-
generateSourceId() {
|
|
95
|
-
return crypto.randomUUID();
|
|
96
|
-
}
|
|
97
|
-
}
|
|
1
|
+
export { SlackClient, } from '@linkpress/core';
|
|
98
2
|
//# sourceMappingURL=client.js.map
|
package/dist/slack/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/slack/client.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/slack/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,GAKZ,MAAM,iBAAiB,CAAC"}
|
package/dist/slack/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/slack/sync.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/slack/sync.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAM1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,gBAAgB,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,EAAE,OAAO,GAAE,WAAgB,GAAG,OAAO,CAAC,UAAU,CAAC,CA0G7H"}
|
package/dist/slack/sync.js
CHANGED
|
@@ -1,85 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
import crypto from 'crypto';
|
|
4
|
-
import { SlackClient } from '
|
|
4
|
+
import { SlackClient, extractLinksFromMessages } from '@linkpress/core';
|
|
5
5
|
import { loadConfig } from '../config.js';
|
|
6
6
|
import { insertArticle, articleExists } from '../db.js';
|
|
7
7
|
import { classifyContent } from '../ai.js';
|
|
8
8
|
import { t } from '../i18n.js';
|
|
9
|
-
const URL_REGEX = /https?:\/\/[^\s<>|]+/g;
|
|
10
|
-
const SLACK_URL_REGEX = /<(https?:\/\/[^|>]+)(?:\|[^>]*)?>/g;
|
|
11
|
-
const IGNORED_DOMAINS = [
|
|
12
|
-
'slack.com',
|
|
13
|
-
'slack-edge.com',
|
|
14
|
-
'slack-imgs.com',
|
|
15
|
-
'giphy.com',
|
|
16
|
-
'tenor.com',
|
|
17
|
-
'emoji.slack-edge.com',
|
|
18
|
-
];
|
|
19
|
-
function extractUrls(text) {
|
|
20
|
-
const urls = new Set();
|
|
21
|
-
const slackMatches = text.matchAll(/<(https?:\/\/[^|>]+)(?:\|[^>]*)?>/g);
|
|
22
|
-
for (const match of slackMatches) {
|
|
23
|
-
urls.add(match[1]);
|
|
24
|
-
}
|
|
25
|
-
const plainMatches = text.matchAll(URL_REGEX);
|
|
26
|
-
for (const match of plainMatches) {
|
|
27
|
-
let url = match[0];
|
|
28
|
-
url = url.replace(/[.,;:!?)]+$/, '');
|
|
29
|
-
urls.add(url);
|
|
30
|
-
}
|
|
31
|
-
return Array.from(urls).filter(url => {
|
|
32
|
-
try {
|
|
33
|
-
const parsed = new URL(url);
|
|
34
|
-
return !IGNORED_DOMAINS.some(domain => parsed.hostname.includes(domain));
|
|
35
|
-
}
|
|
36
|
-
catch {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
function isArticleUrl(url) {
|
|
42
|
-
try {
|
|
43
|
-
const parsed = new URL(url);
|
|
44
|
-
const path = parsed.pathname.toLowerCase();
|
|
45
|
-
const nonArticlePatterns = [
|
|
46
|
-
/\.(png|jpg|jpeg|gif|webp|svg|ico|pdf|zip|tar|gz)$/i,
|
|
47
|
-
/^\/?(favicon|robots\.txt|sitemap)/i,
|
|
48
|
-
];
|
|
49
|
-
for (const pattern of nonArticlePatterns) {
|
|
50
|
-
if (pattern.test(path)) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const articleDomains = [
|
|
55
|
-
'medium.com',
|
|
56
|
-
'dev.to',
|
|
57
|
-
'hashnode.dev',
|
|
58
|
-
'substack.com',
|
|
59
|
-
'github.com',
|
|
60
|
-
'twitter.com',
|
|
61
|
-
'x.com',
|
|
62
|
-
'linkedin.com',
|
|
63
|
-
'youtube.com',
|
|
64
|
-
'youtu.be',
|
|
65
|
-
'notion.so',
|
|
66
|
-
'notion.site',
|
|
67
|
-
'velog.io',
|
|
68
|
-
'tistory.com',
|
|
69
|
-
'brunch.co.kr',
|
|
70
|
-
];
|
|
71
|
-
if (articleDomains.some(d => parsed.hostname.includes(d))) {
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
if (path.length > 10 || path.includes('/blog') || path.includes('/post') || path.includes('/article')) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
return true;
|
|
78
|
-
}
|
|
79
|
-
catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
9
|
export async function syncSlackSources(config, options = {}) {
|
|
84
10
|
const cfg = config || loadConfig();
|
|
85
11
|
const { silent = false } = options;
|
|
@@ -104,21 +30,7 @@ export async function syncSlackSources(config, options = {}) {
|
|
|
104
30
|
const spinner = silent ? null : ora(t('sync.fetching', { channel: channel.name })).start();
|
|
105
31
|
try {
|
|
106
32
|
const messages = await client.getConversationHistory(channel.id, { limit: 200 });
|
|
107
|
-
const
|
|
108
|
-
for (const message of messages) {
|
|
109
|
-
if (message.text) {
|
|
110
|
-
const urls = extractUrls(message.text);
|
|
111
|
-
for (const url of urls.filter(isArticleUrl)) {
|
|
112
|
-
extractedLinks.push({ url, messageText: message.text });
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
const uniqueLinks = extractedLinks.reduce((acc, link) => {
|
|
117
|
-
if (!acc.some(l => l.url === link.url)) {
|
|
118
|
-
acc.push(link);
|
|
119
|
-
}
|
|
120
|
-
return acc;
|
|
121
|
-
}, []);
|
|
33
|
+
const uniqueLinks = extractLinksFromMessages(messages);
|
|
122
34
|
totalUrls += uniqueLinks.length;
|
|
123
35
|
let channelNew = 0;
|
|
124
36
|
let channelSkipped = 0;
|
package/dist/slack/sync.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/slack/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../../src/slack/sync.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,42 +1,10 @@
|
|
|
1
|
-
export
|
|
2
|
-
id: string;
|
|
3
|
-
url: string;
|
|
4
|
-
title: string;
|
|
5
|
-
description?: string;
|
|
6
|
-
content?: string;
|
|
7
|
-
summary?: string;
|
|
8
|
-
tags: string[];
|
|
9
|
-
difficulty?: 'beginner' | 'intermediate' | 'advanced';
|
|
10
|
-
readingTimeMinutes?: number;
|
|
11
|
-
image?: string;
|
|
12
|
-
sourceLabel?: string;
|
|
13
|
-
sourceType: 'slack' | 'manual' | 'import';
|
|
14
|
-
sourceId?: string;
|
|
15
|
-
createdAt: Date;
|
|
16
|
-
processedAt?: Date;
|
|
17
|
-
readAt?: Date;
|
|
18
|
-
}
|
|
19
|
-
export interface SlackSource {
|
|
20
|
-
id: string;
|
|
21
|
-
workspace: string;
|
|
22
|
-
token: string;
|
|
23
|
-
cookie: string;
|
|
24
|
-
channels: SlackChannel[];
|
|
25
|
-
addedAt: Date;
|
|
26
|
-
}
|
|
27
|
-
export interface SlackChannel {
|
|
28
|
-
id: string;
|
|
29
|
-
name: string;
|
|
30
|
-
isPrivate: boolean;
|
|
31
|
-
isSelfDM: boolean;
|
|
32
|
-
}
|
|
33
|
-
export type AIProvider = 'anthropic' | 'openai' | 'gemini';
|
|
1
|
+
export type { Article, AIProvider, SlackSource, SlackChannel, SlackAuthConfig, SlackUser, SlackConversation, SlackMessage, ExtractedLink, ScrapedContent, ArticleSummary, ContentClassification, ModelInfo, AIConfig, } from '@linkpress/core';
|
|
34
2
|
export interface Config {
|
|
35
3
|
sources: {
|
|
36
|
-
slack?: SlackSource[];
|
|
4
|
+
slack?: import('@linkpress/core').SlackSource[];
|
|
37
5
|
};
|
|
38
6
|
ai: {
|
|
39
|
-
provider: AIProvider;
|
|
7
|
+
provider: import('@linkpress/core').AIProvider;
|
|
40
8
|
apiKey?: string;
|
|
41
9
|
model: string;
|
|
42
10
|
language: string;
|
|
@@ -50,7 +18,7 @@ export interface Magazine {
|
|
|
50
18
|
id: string;
|
|
51
19
|
title: string;
|
|
52
20
|
generatedAt: Date;
|
|
53
|
-
articles: Article[];
|
|
21
|
+
articles: import('@linkpress/core').Article[];
|
|
54
22
|
period: {
|
|
55
23
|
from: Date;
|
|
56
24
|
to: Date;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,OAAO,EACP,UAAU,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,EACd,qBAAqB,EACrB,SAAS,EACT,QAAQ,GACT,MAAM,iBAAiB,CAAC;AAEzB,MAAM,WAAW,MAAM;IACrB,OAAO,EAAE;QACP,KAAK,CAAC,EAAE,OAAO,iBAAiB,EAAE,WAAW,EAAE,CAAC;KACjD,CAAC;IACF,EAAE,EAAE;QACF,QAAQ,EAAE,OAAO,iBAAiB,EAAE,UAAU,CAAC;QAC/C,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;IACF,MAAM,EAAE;QACN,SAAS,EAAE,MAAM,CAAC;QAClB,MAAM,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;KACtC,CAAC;CACH;AAED,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,IAAI,CAAC;IAClB,QAAQ,EAAE,OAAO,iBAAiB,EAAE,OAAO,EAAE,CAAC;IAC9C,MAAM,EAAE;QACN,IAAI,EAAE,IAAI,CAAC;QACX,EAAE,EAAE,IAAI,CAAC;KACV,CAAC;CACH;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;CACb"}
|
package/dist/utils.d.ts
CHANGED
package/dist/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/utils.js
CHANGED
|
@@ -1,15 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
if (str.length <= maxLength)
|
|
3
|
-
return str;
|
|
4
|
-
return str.substring(0, maxLength - 3) + '...';
|
|
5
|
-
}
|
|
6
|
-
export function isValidUrl(str) {
|
|
7
|
-
try {
|
|
8
|
-
new URL(str);
|
|
9
|
-
return true;
|
|
10
|
-
}
|
|
11
|
-
catch {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
export { truncate, isValidUrl } from '@linkpress/core';
|
|
15
2
|
//# sourceMappingURL=utils.js.map
|
package/dist/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "linkpress",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Turn your Slack links into a personal tech magazine with AI-powered summaries",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -43,17 +43,13 @@
|
|
|
43
43
|
"url": "https://github.com/mindori/linkpress/issues"
|
|
44
44
|
},
|
|
45
45
|
"dependencies": {
|
|
46
|
-
"@
|
|
47
|
-
"@google/generative-ai": "^0.24.1",
|
|
46
|
+
"@linkpress/core": "^0.1.0",
|
|
48
47
|
"better-sqlite3": "^11.7.0",
|
|
49
48
|
"chalk": "^5.3.0",
|
|
50
|
-
"cheerio": "^1.0.0",
|
|
51
49
|
"commander": "^12.1.0",
|
|
52
50
|
"express": "^4.21.2",
|
|
53
51
|
"inquirer": "^12.3.2",
|
|
54
|
-
"node-fetch": "^3.3.2",
|
|
55
52
|
"open": "^10.1.0",
|
|
56
|
-
"openai": "^6.16.0",
|
|
57
53
|
"ora": "^8.1.1",
|
|
58
54
|
"playwright": "^1.57.0",
|
|
59
55
|
"yaml": "^2.7.0"
|