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 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, 24 platform adapters.
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 | 24 adapters |
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 — 24 Platform Adapters
127
+ ## Act — 19 Platform Adapters
128
128
 
129
- Post to 24+ platforms with one API:
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
- | **14 Directories** | Generic adapter | submit |
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spectrawl",
3
- "version": "0.3.19",
3
+ "version": "0.3.22",
4
4
  "description": "The unified web layer for AI agents. Search (8 engines), stealth browse, auth, act on 24 platforms. Self-hosted.",
5
5
  "main": "src/index.js",
6
6
  "types": "index.d.ts",
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)