stella-coder 5.2.0 → 5.2.1

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.
@@ -0,0 +1,229 @@
1
+ import http from "http"
2
+ import https from "https"
3
+ import { JSDOM } from "jsdom"
4
+
5
+ function fetchUrl(url, options = {}) {
6
+ return new Promise((resolve, reject) => {
7
+ const mod = url.startsWith("https") ? https : http
8
+ const headers = {
9
+ "User-Agent": "StellaBot/5.2 (compatible; like Googlebot)",
10
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
11
+ "Accept-Language": "en-US,en;q=0.5",
12
+ ...options.headers,
13
+ }
14
+
15
+ const req = mod.get(url, { headers, timeout: 15000 }, (res) => {
16
+ // Follow redirects
17
+ if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
18
+ const redirectUrl = new URL(res.headers.location, url).href
19
+ return fetchUrl(redirectUrl, options).then(resolve).catch(reject)
20
+ }
21
+
22
+ let data = ""
23
+ res.on("data", (chunk) => data += chunk)
24
+ res.on("end", () => resolve({
25
+ status: res.statusCode,
26
+ headers: res.headers,
27
+ html: data,
28
+ url,
29
+ }))
30
+ })
31
+ req.on("error", reject)
32
+ req.on("timeout", () => { req.destroy(); reject(new Error("Timeout")) })
33
+ })
34
+ }
35
+
36
+ export class WebParser {
37
+ constructor() {
38
+ this.cache = new Map()
39
+ }
40
+
41
+ async fetchPage(url) {
42
+ if (this.cache.has(url)) {
43
+ return this.cache.get(url)
44
+ }
45
+
46
+ const result = await fetchUrl(url)
47
+ const dom = new JSDOM(result.html)
48
+ const doc = dom.window.document
49
+
50
+ const parsed = {
51
+ url,
52
+ status: result.status,
53
+ title: doc.querySelector("title")?.textContent?.trim() || "",
54
+ meta: {
55
+ description: doc.querySelector('meta[name="description"]')?.content || "",
56
+ keywords: doc.querySelector('meta[name="keywords"]')?.content || "",
57
+ ogTitle: doc.querySelector('meta[property="og:title"]')?.content || "",
58
+ ogDescription: doc.querySelector('meta[property="og:description"]')?.content || "",
59
+ ogImage: doc.querySelector('meta[property="og:image"]')?.content || "",
60
+ canonical: doc.querySelector('link[rel="canonical"]')?.href || "",
61
+ },
62
+ headings: Array.from(doc.querySelectorAll("h1,h2,h3,h4")).map(h => ({
63
+ level: parseInt(h.tagName[1]),
64
+ text: h.textContent.trim(),
65
+ })),
66
+ links: Array.from(doc.querySelectorAll("a[href]")).map(a => ({
67
+ text: a.textContent.trim().slice(0, 100),
68
+ href: a.href,
69
+ })).filter(l => l.text).slice(0, 100),
70
+ images: Array.from(doc.querySelectorAll("img[src]")).map(img => ({
71
+ src: img.src,
72
+ alt: img.alt || "",
73
+ })).slice(0, 50),
74
+ text: doc.body?.innerText?.trim()?.slice(0, 50000) || "",
75
+ html: result.html,
76
+ linksCount: doc.querySelectorAll("a[href]").length,
77
+ imagesCount: doc.querySelectorAll("img[src]").length,
78
+ formsCount: doc.querySelectorAll("form").length,
79
+ scriptsCount: doc.querySelectorAll("script").length,
80
+ }
81
+
82
+ this.cache.set(url, parsed)
83
+ return parsed
84
+ }
85
+
86
+ async extractText(url) {
87
+ const page = await this.fetchPage(url)
88
+ return { success: true, title: page.title, text: page.text }
89
+ }
90
+
91
+ async extractLinks(url) {
92
+ const page = await this.fetchPage(url)
93
+ return { success: true, links: page.links }
94
+ }
95
+
96
+ async extractImages(url) {
97
+ const page = await this.fetchPage(url)
98
+ return { success: true, images: page.images }
99
+ }
100
+
101
+ async extractForms(url) {
102
+ const page = await this.fetchPage(url)
103
+ const dom = new JSDOM(page.html)
104
+ const doc = dom.window.document
105
+
106
+ const forms = Array.from(doc.querySelectorAll("form")).map(f => ({
107
+ action: f.action,
108
+ method: f.method?.toUpperCase() || "GET",
109
+ fields: Array.from(f.querySelectorAll("input,textarea,select,button")).map(el => ({
110
+ tag: el.tagName.toLowerCase(),
111
+ type: el.type || "",
112
+ name: el.name || "",
113
+ placeholder: el.placeholder || "",
114
+ value: el.value || "",
115
+ required: el.required,
116
+ options: el.tagName === "SELECT"
117
+ ? Array.from(el.querySelectorAll("option")).map(o => ({ value: o.value, text: o.textContent.trim() }))
118
+ : undefined,
119
+ })),
120
+ }))
121
+
122
+ return { success: true, forms }
123
+ }
124
+
125
+ async search(query, engine = "duckduckgo") {
126
+ if (engine === "duckduckgo") {
127
+ const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}`
128
+ const page = await this.fetchPage(url)
129
+ const dom = new JSDOM(page.html)
130
+ const doc = dom.window.document
131
+
132
+ const results = Array.from(doc.querySelectorAll(".result")).map(r => ({
133
+ title: r.querySelector(".result__title")?.textContent?.trim() || "",
134
+ url: r.querySelector(".result__url")?.textContent?.trim() || "",
135
+ snippet: r.querySelector(".result__snippet")?.textContent?.trim() || "",
136
+ })).slice(0, 10)
137
+
138
+ return { success: true, results }
139
+ }
140
+
141
+ if (engine === "google") {
142
+ const url = `https://www.google.com/search?q=${encodeURIComponent(query)}`
143
+ const page = await this.fetchPage(url)
144
+ const dom = new JSDOM(page.html)
145
+ const doc = dom.window.document
146
+
147
+ const results = Array.from(doc.querySelectorAll("div.g,div[data-sokoban-container]")).map(r => ({
148
+ title: r.querySelector("h3")?.textContent?.trim() || "",
149
+ url: r.querySelector("a")?.href || "",
150
+ snippet: r.querySelector(".VwiC3b,.st")?.textContent?.trim() || "",
151
+ })).filter(r => r.title).slice(0, 10)
152
+
153
+ return { success: true, results }
154
+ }
155
+
156
+ return { success: false, error: `Unknown engine: ${engine}` }
157
+ }
158
+
159
+ async getSEO(url) {
160
+ const page = await this.fetchPage(url)
161
+ const dom = new JSDOM(page.html)
162
+ const doc = dom.window.document
163
+
164
+ const seo = {
165
+ title: page.title,
166
+ titleLength: page.title.length,
167
+ metaDescription: page.meta.description,
168
+ metaDescLength: page.meta.description.length,
169
+ h1Count: doc.querySelectorAll("h1").length,
170
+ hasCanonical: !!page.meta.canonical,
171
+ hasOgTitle: !!page.meta.ogTitle,
172
+ hasOgDescription: !!page.meta.ogDescription,
173
+ hasOgImage: !!page.meta.ogImage,
174
+ imageAlts: Array.from(doc.querySelectorAll("img")).filter(img => !img.alt).length === 0,
175
+ linksInternal: page.links.filter(l => new URL(l.href, url).hostname === new URL(url).hostname).length,
176
+ linksExternal: page.links.filter(l => new URL(l.href, url).hostname !== new URL(url).hostname).length,
177
+ score: 0,
178
+ issues: [],
179
+ }
180
+
181
+ // Score
182
+ if (seo.titleLength >= 30 && seo.titleLength <= 60) seo.score += 20
183
+ else seo.issues.push(`Title length ${seo.titleLength} (optimal: 30-60)`)
184
+
185
+ if (seo.metaDescLength >= 120 && seo.metaDescLength <= 160) seo.score += 20
186
+ else seo.issues.push(`Meta description length ${seo.metaDescLength} (optimal: 120-160)`)
187
+
188
+ if (seo.h1Count === 1) seo.score += 15
189
+ else seo.issues.push(`H1 count: ${seo.h1Count} (should be 1)`)
190
+
191
+ if (seo.hasCanonical) seo.score += 10
192
+ else seo.issues.push("Missing canonical tag")
193
+
194
+ if (seo.hasOgTitle) seo.score += 10
195
+ else seo.issues.push("Missing og:title")
196
+
197
+ if (seo.hasOgDescription) seo.score += 10
198
+ else seo.issues.push("Missing og:description")
199
+
200
+ if (seo.hasOgImage) seo.score += 5
201
+ else seo.issues.push("Missing og:image")
202
+
203
+ if (seo.imageAlts) seo.score += 10
204
+ else seo.issues.push("Images without alt text")
205
+
206
+ return { success: true, seo }
207
+ }
208
+
209
+ async batchFetch(urls) {
210
+ const results = await Promise.allSettled(
211
+ urls.map(url => this.fetchPage(url).then(p => ({
212
+ url,
213
+ title: p.title,
214
+ status: p.status,
215
+ textLength: p.text.length,
216
+ })))
217
+ )
218
+
219
+ return {
220
+ success: true,
221
+ results: results.map((r, i) => ({
222
+ url: urls[i],
223
+ ...(r.status === "fulfilled" ? r.value : { error: r.reason?.message }),
224
+ })),
225
+ }
226
+ }
227
+ }
228
+
229
+ export default WebParser
package/capture.ps1 DELETED
@@ -1,14 +0,0 @@
1
- Add-Type -AssemblyName System.Windows.Forms
2
- Add-Type -AssemblyName System.Drawing
3
- try {
4
- $bounds = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds
5
- $bitmap = New-Object System.Drawing.Bitmap($bounds.Width, $bounds.Height)
6
- $graphics = [System.Drawing.Graphics]::FromImage($bitmap)
7
- $graphics.CopyFromScreen($bounds.Location, [System.Drawing.Point]::Empty, $bounds.Size)
8
- $bitmap.Save("C:\Users\user\Downloads\stella-coder-3-9\test_screen.png")
9
- $graphics.Dispose()
10
- $bitmap.Dispose()
11
- Write-Output "OK"
12
- } catch {
13
- Write-Output "FAIL:$($_.Exception.Message)"
14
- }
package/test_screen.png DELETED
Binary file