@xriss/weerss 0.0.2

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/hoard.js ADDED
@@ -0,0 +1,85 @@
1
+
2
+
3
+ let hoard={}
4
+ export default hoard
5
+
6
+
7
+
8
+ import db from "./db.js"
9
+
10
+
11
+
12
+ // make sure we have a cache of the page available, no matter how old
13
+ hoard.first_text=async function(url)
14
+ {
15
+ if( ! await db.get("hoard",url) ) // if not exist
16
+ {
17
+ return await hoard.fetch_text(url) // make exist
18
+ }
19
+ }
20
+
21
+ hoard.fast_text=async function(url)
22
+ {
23
+ let it
24
+ it=await db.get("hoard",url)
25
+ if(it)
26
+ {
27
+ let randage=it.randage||0 // make updates have a random interval
28
+ if( ( Date.now() - (new Date(it.date)).getTime() ) < ( hoard.maxage + randage ) ) // use cache ?
29
+ {
30
+ return it.text // no refresh
31
+ }
32
+ hoard.fetch_text(url,true) // refresh
33
+ return it.text // but return fast
34
+ }
35
+ else
36
+ {
37
+ return await hoard.fetch_text(url)
38
+ }
39
+ }
40
+
41
+ // cache lasts from maxage to double maxage as a random thingy
42
+ hoard.maxage=15*60*1000
43
+
44
+ hoard.maxsize=2*1024
45
+
46
+ hoard.fetch_text=async function(url,refresh)
47
+ {
48
+ // let corsurl=(arss.cors||"")+url
49
+ let corsurl=url
50
+ let oldtext
51
+ let it
52
+ if(!refresh) { it=await db.get("hoard",url) } // try cache
53
+ if(it)
54
+ {
55
+ let randage=it.randage||0 // make updates have a random interval
56
+ if( ( Date.now() - (new Date(it.date)).getTime() ) < ( hoard.maxage + randage ) ) // use cache ?
57
+ {
58
+ return it.text
59
+ }
60
+ oldtext=it.text
61
+ }
62
+ let write=async function()
63
+ {
64
+ it={}
65
+ it.status=0
66
+ it.date=new Date()
67
+ try{
68
+
69
+ let res=await Promise.race([
70
+ fetch(corsurl),
71
+ new Promise((_, reject) => setTimeout(() => reject("timeout"), 2*1000)),
72
+ ])
73
+ it.status=res.status
74
+ it.text=await res.text()
75
+
76
+ if(it.text=="undefined") { it.text="STATUS : "+it.status }
77
+ it.randage=Math.floor(Math.random() * hoard.maxage);
78
+ await db.set("hoard",url,it) // always write if we get here, errors will not write
79
+
80
+ }catch(e){console.error("failed url",url);console.error(e)}
81
+ }
82
+ await write()
83
+ return it.text
84
+ }
85
+
package/items.js ADDED
@@ -0,0 +1,188 @@
1
+
2
+
3
+ let items={}
4
+ export default items
5
+
6
+
7
+ import hoard from "./hoard.js"
8
+ import db from "./db.js"
9
+ import jxml from "./jxml.js"
10
+
11
+ import { configure } from 'safe-stable-stringify'
12
+ const stringify = configure({})
13
+
14
+
15
+
16
+
17
+ items.cached={}
18
+
19
+ items.precache=async function() // faster then individual cache, db is fecking slow
20
+ {
21
+ for(let item of await db.list("items"))
22
+ {
23
+ items.cache(item.uuid,item)
24
+ }
25
+ }
26
+
27
+
28
+ items.cache=async function(uuid,item) // probably fast
29
+ {
30
+ if(item) { items.cached[uuid]=item ; return item }
31
+ if(items.cached[uuid]) { return items.cached[uuid] }
32
+ await items.get(uuid)
33
+ return items.cached[uuid]
34
+ }
35
+ items.get=async function(uuid) // always slow
36
+ {
37
+ let item=await db.get("items",uuid)
38
+ if(item) { items.cache(item.uuid,item) }
39
+ return item
40
+ }
41
+ items.set=async function(item)
42
+ {
43
+ await db.set("items",item.uuid,item)
44
+ if(item) { items.cache(item.uuid,item) }
45
+ }
46
+
47
+
48
+
49
+ items.prepare=function(item,feed)
50
+ {
51
+ let atom=item.atom||{}
52
+ let rss=item.rss||{}
53
+ let any=item.rss||item.atom||{}
54
+
55
+ if(feed) // cache some basic feed values
56
+ {
57
+ item.feed=feed.url
58
+ item.feed_title=feed.title
59
+ item.feed_tags=feed.tags
60
+ }
61
+
62
+ if(!item.date) // do not change date, just set it once
63
+ {
64
+ if(rss["/pubdate"])
65
+ {
66
+ item.date=new Date(rss["/pubdate"])
67
+ }
68
+ else
69
+ if(atom["/published"]) // prefer published
70
+ {
71
+ item.date=new Date(atom["/published"])
72
+ }
73
+ else
74
+ if(atom["/updated"]) // but sometimes we do not have it
75
+ {
76
+ item.date=new Date(atom["/updated"])
77
+ }
78
+ else
79
+ if(atom["/created"]) // wtf?
80
+ {
81
+ item.date=new Date(atom["/created"])
82
+ }
83
+ else
84
+ if(atom["/issued"]) // wtf?
85
+ {
86
+ item.date=new Date(atom["/issued"])
87
+ }
88
+ else
89
+ if(atom["/modified"]) // wtf?
90
+ {
91
+ item.date=new Date(atom["/modified"])
92
+ }
93
+ else
94
+ if(any["/dc:date"]) // why you do dis?
95
+ {
96
+ item.date=new Date(any["/dc:date"])
97
+ }
98
+ else
99
+ if(feed && feed.date) // maybe the feed had a date
100
+ {
101
+ item.date=feed.date
102
+ }
103
+ }
104
+
105
+
106
+ if(rss["/link"])
107
+ {
108
+ item.link=rss["/link"]
109
+ }
110
+ else
111
+ if(rss["/id"]) // sometimes the real link is the RSS id
112
+ {
113
+ item.link=rss["/id"]
114
+ }
115
+ else
116
+ if(atom["/link"])
117
+ {
118
+ item.link=atom["/link"][0]["@href"]
119
+ for(let link of atom["/link"])
120
+ {
121
+ if( link["@rel"]=="alternate" )
122
+ {
123
+ item.link=link["@href"]
124
+ }
125
+ }
126
+ }
127
+ else
128
+ if(atom["/id"]) // sometimes the real link is the RSS id
129
+ {
130
+ item.link=atom["/id"]
131
+ }
132
+
133
+ item.title=item.title||""
134
+ if(rss["/title"])
135
+ {
136
+ item.title=rss["/title"]
137
+ }
138
+ else
139
+ if(atom["/title"])
140
+ {
141
+ item.title=atom["/title"]
142
+ }
143
+
144
+ item.html=item.html||""
145
+ if(rss["/description"])
146
+ {
147
+ item.html=rss["/description"]
148
+ }
149
+ else
150
+ if(atom["/content"])
151
+ {
152
+ item.html=atom["/content"]
153
+ }
154
+
155
+ // prefer link, but might be a feed url + ( id or date )
156
+ item.uuid=item.link || ( item.feed+"#"+( ( rss["/guid"] || atom["/id"] ) || (""+item.date) ) )
157
+
158
+ return item
159
+ }
160
+
161
+ items.add_count=0
162
+ items.add=async function(it)
163
+ {
164
+ let old=await items.cache(it.uuid) // db.get("items",it.uuid)
165
+ let item={}
166
+ if(old)
167
+ {
168
+ for(let n in old ){ item[n]=old[n] }
169
+ }
170
+ else
171
+ {
172
+ items.add_count++
173
+ }
174
+ for(let n in it )
175
+ {
176
+ item[n]=item[n]||it[n]
177
+ }
178
+ if(old) // old date has precedence
179
+ {
180
+ item.date=old.date||item.date
181
+ }
182
+ item.date=item.date||new Date() // make sure we have a date
183
+
184
+ if( stringify(old) != stringify(item) ) // something changed
185
+ {
186
+ await items.set(item)
187
+ }
188
+ }
package/jxml.js ADDED
@@ -0,0 +1,262 @@
1
+
2
+ let jxml={}
3
+ export default jxml
4
+
5
+
6
+ import sax from "sax"
7
+
8
+
9
+ jxml.xmap={}
10
+
11
+ // technically we should keep nesting outlines
12
+ jxml.xmap.opml={
13
+ "/opml/body/outline":true,
14
+ "/opml/body/outline/outline":true,
15
+ "/opml/body/outline/outline/outline":true,
16
+ "/opml/body/outline/outline/outline/outline":true,
17
+ }
18
+
19
+ jxml.xmap.rss={
20
+ "/rss/channel/category":true,
21
+ "/rss/channel/item":true,
22
+ "/rss/channel/item/category":true,
23
+ "/rss/channel/item/enclosure":true,
24
+ "/rss/channel/item/media:content":true,
25
+ "/rss/channel/item/media:group":true,
26
+ "/rss/channel/item/media:group/media:content":true,
27
+ }
28
+
29
+ jxml.xmap.atom={
30
+ "/feed/category":true,
31
+ "/feed/link":true,
32
+ "/feed/entry":true,
33
+ "/feed/entry/category":true,
34
+ "/feed/entry/link":true,
35
+ }
36
+
37
+
38
+
39
+ jxml.sanistr=function(s)
40
+ {
41
+ s=""+s
42
+ const map = {
43
+ '&': '&amp;',
44
+ '<': '&lt;',
45
+ '>': '&gt;',
46
+ '"': '&quot;',
47
+ };
48
+ return s.replace(/[&<>"]/ig, function(match){return map[match]});
49
+ }
50
+
51
+ /*
52
+
53
+ Create a json object from an xml string using the given xmap.
54
+
55
+ The xmap is a dictionary of xpath:true for each element that may occur
56
+ multiple times.
57
+
58
+ Not providing an xmap will make us assume that every element can occur
59
+ multiple times which always work but will create an ugly/inefficient
60
+ json format.
61
+
62
+ */
63
+ jxml.parse_xml=function(data,xmap)
64
+ {
65
+ let json=[];
66
+ let stack=[];
67
+ let top={};stack.push(top);
68
+ let ret=top
69
+ let xpath=[""]
70
+ let opath=[""]
71
+ let cdata=false;
72
+
73
+ let parser = sax.parser(true)
74
+
75
+ let ismulti=function(path)
76
+ {
77
+ if(!xmap) { return true } // if no map then everything is multi
78
+ if("function"===typeof xmap) { return xmap(path) }// only approved paths are multi
79
+ if(xmap[path]) { return true } // only given paths are multi
80
+ return false
81
+ }
82
+
83
+ parser.onopentag=function(node){
84
+ let tag="/"+node.name.toLowerCase()
85
+
86
+ let path=xpath.join("")+tag
87
+ let tagpath=opath.at(-1)+tag
88
+ if( ismulti(path) )
89
+ {
90
+ let parent=top
91
+ top={}
92
+ if(! parent[tagpath] ) { parent[tagpath]=[] }
93
+ parent[tagpath].push(top)
94
+ for(let n in node.attributes) { top["@"+n.toLowerCase()]=node.attributes[n] }
95
+ stack.push(top)
96
+ xpath.push(tag)
97
+ opath.push("")
98
+ }
99
+ else
100
+ {
101
+ for(let n in node.attributes) { top[tagpath+"@"+n.toLowerCase()]=node.attributes[n] }
102
+ stack.push(top)
103
+ xpath.push(tag)
104
+ opath.push(tagpath)
105
+ }
106
+ // console.log("+"+node.name+" "+path)
107
+ }
108
+
109
+ parser.onclosetag=function(name){
110
+ stack.pop()
111
+ top=stack.at(-1)
112
+ xpath.pop()
113
+ opath.pop()
114
+ // console.log("-"+name)
115
+ }
116
+
117
+ parser.ontext=function(text){
118
+ text=text.trim()
119
+ if(text!="") // ignore white space
120
+ {
121
+ let tag=opath.at(-1)
122
+ if( top[tag] )
123
+ {
124
+ top[tag]=top[tag]+" "+text
125
+ }
126
+ else
127
+ {
128
+ top[tag]=text
129
+ }
130
+ }
131
+ }
132
+ parser.oncdata=parser.ontext // oncdata is same function as ontext
133
+
134
+ // maintain cdata text flag
135
+ parser.onopencdata=function(){ cdata=true; }
136
+ parser.onclosecdata=function(){ cdata=false; }
137
+
138
+ //throw any errors
139
+ parser.onerror=function(e){ throw new Error(e) }
140
+
141
+ parser.write(data);
142
+
143
+ if(stack.length!=1)
144
+ {
145
+ throw new Error("premature ending in xml data")
146
+ }
147
+
148
+ return ret
149
+ }
150
+
151
+ // expand a json xml object so each element is forced into an array
152
+ // this guarantees the format is what you would have gotten from a parse without an xmap
153
+ jxml.expand=function(it,paths)
154
+ {
155
+ let ret=jxml.expand_one(it)
156
+ jxml.recurse(ret,jxml.expand,paths)
157
+ return ret
158
+ }
159
+ // perform top level expansion only, still need to recurse
160
+ jxml.expand_one=function(it)
161
+ {
162
+ let ret={}
163
+ for(let path in it ) // make arrays
164
+ {
165
+ if( (path=="") || (path[0]=="@") ) // simple
166
+ {
167
+ ret[path]=it[path] // this should never be an array
168
+ }
169
+ else // find split and put in an array
170
+ {
171
+ let idx=path.indexOf("/",1) // skip first char which can be a / or @
172
+ let idx2=path.indexOf("@",1) // skip first char which can be a / or @
173
+ if( (idx2>0) && ( (idx2<idx) || (idx<0) ) ) { idx=idx2 } // second / or first @
174
+ if( (idx<0) && Array.isArray(it[path]) ) // full path, no / or @ to split and already an array
175
+ {
176
+ ret[path]=[ ...it[path] ] // copy array
177
+ }
178
+ else
179
+ {
180
+ if( idx<0 ) // a text node
181
+ {
182
+ idx=path.length
183
+ }
184
+ let base=path.substring(0,idx)
185
+ if( !Array.isArray(ret[base]) ) { ret[base]=[{}] } // make array with one object
186
+ ret[base][0][ path.substring(idx) ]=it[path] // copy into first object ( we will dupe the array on next pass )
187
+ }
188
+ }
189
+ }
190
+ return ret
191
+ }
192
+
193
+ // call recursively into arrays with an array of path strings
194
+ jxml.recurse=function(it,func,paths)
195
+ {
196
+ paths=paths||[]
197
+ for(let path in it ) // do arrays
198
+ {
199
+ if(Array.isArray(it[path]))
200
+ {
201
+ paths.push(path)
202
+ for(let i=0;i<it[path].length;i++)
203
+ {
204
+ it[path][i]=func(it[path][i],paths)
205
+ }
206
+ paths.pop()
207
+ }
208
+ }
209
+ return it
210
+ }
211
+
212
+ jxml.build_xml=function(data)
213
+ {
214
+ let ss=['<?xml version="1.0" encoding="UTF-8" ?>\n']
215
+
216
+ data=jxml.expand(data) // fully expand so every sub tag is an array
217
+
218
+ let parse
219
+ parse=function(it,paths)
220
+ {
221
+ let top=paths[paths.length-1] // this will start with a /
222
+ let indent=(" ").repeat(Math.max(0,paths.length-1))
223
+ let children=false
224
+ if(top) // catch the starting empty paths
225
+ {
226
+ ss.push(indent+"<"+top.substring(1))
227
+ for(let path in it )
228
+ {
229
+ if(path[0]=="@") // add atributes
230
+ {
231
+ ss.push(" "+path.substring(1)+"=\""+jxml.sanistr(it[path])+"\"")
232
+ }
233
+ if((path[0]=="/")||(path=="")) // children
234
+ {
235
+ children=true
236
+ }
237
+ }
238
+ if(children)
239
+ {
240
+ ss.push(" >\n")
241
+ if(it[""]){ss.push(indent+" "+jxml.sanistr(it[""])+"\n")} // push text
242
+ }
243
+ else
244
+ {
245
+ ss.push(" />\n")
246
+ }
247
+ }
248
+ jxml.recurse(it,parse,paths) // add sub tags
249
+ if(top)
250
+ {
251
+ if(children)
252
+ {
253
+ ss.push(indent+"<"+top+">\n")
254
+ }
255
+ }
256
+ }
257
+ parse(data,[])
258
+
259
+
260
+ let s=ss.join("")
261
+ return s
262
+ }
package/npm-publish ADDED
@@ -0,0 +1,11 @@
1
+
2
+ cd `dirname $0`
3
+
4
+ git add .
5
+ git commit -m.
6
+ npm version patch
7
+ git add .
8
+ git commit -m.
9
+ git push
10
+ npm publish
11
+
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@xriss/weerss",
3
+ "version": "0.0.2",
4
+ "source": "weerss.js",
5
+ "type": "module",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/xriss/weerss.git"
9
+ },
10
+ "bin": {
11
+ "weerss": "./cmd.import.js"
12
+ },
13
+ "scripts": {
14
+ "watch": " webpack watch --config webpack.config.cjs --mode development ",
15
+ "build": " webpack build --config webpack.config.cjs --mode production "
16
+ },
17
+ "dependencies": {
18
+ "@xriss/djon": "^0.0.14",
19
+ "express": "^4.19.2",
20
+ "minimist": "^1.2.8",
21
+ "move-file": "^3.1.0",
22
+ "parse-torrent": "^11.0.17",
23
+ "path-browserify": "^1.0.1",
24
+ "promised-sqlite3": "^2.1.0",
25
+ "safe-stable-stringify": "^2.5.0",
26
+ "sanitize-filename": "^1.6.3",
27
+ "sax": "^1.4.1",
28
+ "sqlite3": "^5.1.7",
29
+ "stream-browserify": "^3.0.0",
30
+ "webpack": "^5.94.0"
31
+ },
32
+ "devDependencies": {
33
+ "webpack-cli": "^5.1.4"
34
+ }
35
+ }
@@ -0,0 +1,35 @@
1
+
2
+ // default to just .txt
3
+ let extensions = /\.(txt)$/i;
4
+
5
+ // copy raw-loader pattern so we are in sync
6
+ import wp from "./webpack.config.cjs"
7
+ for( let it of wp && wp.module && wp.module.rules || [] )
8
+ {
9
+ if(it.use=="raw-loader")
10
+ {
11
+ extensions=it.test
12
+ break
13
+ }
14
+ }
15
+
16
+ // hook into imports
17
+ export async function load(url, context, nextLoad)
18
+ {
19
+ if (extensions.test(url))
20
+ {
21
+ let { source: source } = await nextLoad(url, { ...context , format:"module" } );
22
+ let data=JSON.stringify(source.toString())
23
+ source=`
24
+ const text=${data};
25
+ export default text;
26
+ `
27
+ return {
28
+ format: "module",
29
+ shortCircuit: true,
30
+ source: source
31
+ };
32
+ }
33
+ return nextLoad(url);
34
+ }
35
+
package/register.js ADDED
@@ -0,0 +1,3 @@
1
+ // just register hooks
2
+ import module from "module"
3
+ await module.register("./register-hooks.js",import.meta.url)
package/serv.js ADDED
@@ -0,0 +1,21 @@
1
+
2
+ const serv={}
3
+ export default serv
4
+
5
+ import express from 'express'
6
+
7
+ serv.start=function()
8
+ {
9
+
10
+ const app = express()
11
+ const port = 3000
12
+
13
+ app.get('/', (req, res) => {
14
+ res.send('Hello World!')
15
+ })
16
+
17
+ app.listen(port, () => {
18
+ console.log(`Example app listening on port ${port}`)
19
+ })
20
+
21
+ }