spectrawl 0.3.19 → 0.3.22
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 +9 -6
- package/package.json +1 -1
- package/src/server.js +59 -0
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
The unified web layer for AI agents. Search, browse, authenticate, and act on platforms — one package, self-hosted.
|
|
4
4
|
|
|
5
|
-
**5,000 free searches/month** via Gemini Grounded Search. Full page scraping, stealth browsing,
|
|
5
|
+
**5,000 free searches/month** via Gemini Grounded Search. Full page scraping, stealth browsing, 19 platform adapters.
|
|
6
6
|
|
|
7
7
|
## What It Does
|
|
8
8
|
|
|
@@ -55,7 +55,7 @@ Different tools for different needs.
|
|
|
55
55
|
| Returns | Snippets + AI answer | Full page content + snippets |
|
|
56
56
|
| Self-hosted | No | Yes |
|
|
57
57
|
| Stealth browsing | No | Yes (Camoufox + Playwright) |
|
|
58
|
-
| Platform posting | No |
|
|
58
|
+
| Platform posting | No | 19 adapters |
|
|
59
59
|
| Auth management | No | Cookie store + auto-refresh |
|
|
60
60
|
| Cached repeats | No | <1ms |
|
|
61
61
|
|
|
@@ -124,9 +124,9 @@ const accounts = await web.auth.getStatus()
|
|
|
124
124
|
|
|
125
125
|
Cookie refresh cron fires `cookie_expiring` and `cookie_expired` events before accounts go stale.
|
|
126
126
|
|
|
127
|
-
## Act —
|
|
127
|
+
## Act — 19 Platform Adapters
|
|
128
128
|
|
|
129
|
-
Post to
|
|
129
|
+
Post to 19 platforms with one API:
|
|
130
130
|
|
|
131
131
|
```js
|
|
132
132
|
await web.act('github', 'create-issue', { repo: 'user/repo', title: 'Bug report', body: '...' })
|
|
@@ -135,7 +135,7 @@ await web.act('devto', 'post', { title: '...', body: '...', tags: ['ai'] })
|
|
|
135
135
|
await web.act('huggingface', 'create-repo', { name: 'my-model', type: 'model' })
|
|
136
136
|
```
|
|
137
137
|
|
|
138
|
-
**Live tested:** GitHub ✅, Reddit ✅, Dev.to ✅, HuggingFace ✅, X (reads) ✅
|
|
138
|
+
**Live tested:** GitHub ✅, Reddit ✅, Dev.to ✅, HuggingFace ✅, X (reads) ✅, Hashnode ✅, Discord ✅, Product Hunt ✅
|
|
139
139
|
|
|
140
140
|
| Platform | Auth Method | Actions |
|
|
141
141
|
|----------|-------------|---------|
|
|
@@ -154,7 +154,10 @@ await web.act('huggingface', 'create-repo', { name: 'my-model', type: 'model' })
|
|
|
154
154
|
| Quora | Browser automation | answer |
|
|
155
155
|
| HuggingFace | Hub API | repo, model card, upload |
|
|
156
156
|
| BetaList | REST API | submit |
|
|
157
|
-
|
|
|
157
|
+
| AlternativeTo | Cookie session | submit, claim |
|
|
158
|
+
| DevHunt | Supabase auth | submit, upvote |
|
|
159
|
+
| SaaSHub | Generic adapter | submit |
|
|
160
|
+
| **Generic Directory** | Configurable | submit |
|
|
158
161
|
|
|
159
162
|
Built-in rate limiting, content dedup (MD5, 24h window), and dead letter queue for retries.
|
|
160
163
|
|
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -61,6 +61,65 @@ const server = http.createServer(async (req, res) => {
|
|
|
61
61
|
return json(res, result)
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
+
// Threads OAuth callback
|
|
65
|
+
if (req.method === 'GET' && path === '/auth/callback/threads') {
|
|
66
|
+
const code = url.searchParams.get('code')
|
|
67
|
+
const errParam = url.searchParams.get('error')
|
|
68
|
+
if (errParam) {
|
|
69
|
+
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
70
|
+
return res.end(`<h2>❌ Auth error: ${errParam}</h2>`)
|
|
71
|
+
}
|
|
72
|
+
if (!code) {
|
|
73
|
+
res.writeHead(400, { 'Content-Type': 'text/html' })
|
|
74
|
+
return res.end('<h2>❌ No code received</h2>')
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
// Exchange code for token
|
|
78
|
+
const fetch = require('node:https')
|
|
79
|
+
const params = new URLSearchParams({
|
|
80
|
+
client_id: '1574846783732558',
|
|
81
|
+
client_secret: 'f8589ca3523b0ea5bab3fac2c2ae4c15',
|
|
82
|
+
code,
|
|
83
|
+
grant_type: 'authorization_code',
|
|
84
|
+
redirect_uri: 'https://gateway.xanos.org/auth/callback/threads'
|
|
85
|
+
})
|
|
86
|
+
const tokenRes = await new Promise((resolve, reject) => {
|
|
87
|
+
const postData = params.toString()
|
|
88
|
+
const options = {
|
|
89
|
+
hostname: 'graph.threads.net',
|
|
90
|
+
path: '/oauth/access_token',
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
94
|
+
'Content-Length': Buffer.byteLength(postData)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const req2 = fetch.request(options, (r) => {
|
|
98
|
+
let data = ''
|
|
99
|
+
r.on('data', chunk => data += chunk)
|
|
100
|
+
r.on('end', () => resolve(JSON.parse(data)))
|
|
101
|
+
})
|
|
102
|
+
req2.on('error', reject)
|
|
103
|
+
req2.write(postData)
|
|
104
|
+
req2.end()
|
|
105
|
+
})
|
|
106
|
+
// Save to credentials
|
|
107
|
+
const fs = require('fs')
|
|
108
|
+
const credsPath = '/root/.openclaw/workspace-dijiclaw/.openclaw/credentials/threads-api.json'
|
|
109
|
+
const creds = JSON.parse(fs.readFileSync(credsPath, 'utf8'))
|
|
110
|
+
creds.user_token = tokenRes.access_token
|
|
111
|
+
creds.user_id = tokenRes.user_id
|
|
112
|
+
creds.token_type = tokenRes.token_type
|
|
113
|
+
creds.note = 'User token saved via OAuth callback'
|
|
114
|
+
fs.writeFileSync(credsPath, JSON.stringify(creds, null, 2))
|
|
115
|
+
res.writeHead(200, { 'Content-Type': 'text/html' })
|
|
116
|
+
return res.end('<h2>✅ Threads connected! You can close this tab.</h2>')
|
|
117
|
+
} catch (e) {
|
|
118
|
+
res.writeHead(500, { 'Content-Type': 'text/html' })
|
|
119
|
+
return res.end(`<h2>❌ Token exchange failed: ${e.message}</h2>`)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
64
123
|
return error(res, 404, 'Not found')
|
|
65
124
|
} catch (err) {
|
|
66
125
|
console.error('Server error:', err)
|