@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/cmd.import.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('@xriss/weerss/cmd.js').catch((err) => { console.error(err); process.exit(1) })
package/cmd.js ADDED
@@ -0,0 +1,106 @@
1
+
2
+ const cmd={}
3
+ export default cmd
4
+
5
+
6
+ import minimist from "minimist"
7
+
8
+ import path from "path"
9
+ import url from "url"
10
+
11
+ import serv from "./serv.js"
12
+ import db from "./db.js"
13
+
14
+ import hoard from "./hoard.js"
15
+ import feeds from "./feeds.js"
16
+ import items from "./items.js"
17
+
18
+ import weerss from "./weerss.js"
19
+
20
+ const args = minimist(process.argv.slice(2),{boolean:true})
21
+
22
+ args.config=args.config || process.env.WEERSS_CONFIG
23
+
24
+ await weerss.load_config(args)
25
+
26
+ let arg1=args._[0] || "help"
27
+ let arg2=args._[1]
28
+
29
+ if( arg1=="help" )
30
+ {
31
+ console.log(`
32
+
33
+ options:
34
+
35
+ Load config file, you should always load your custom config for all
36
+ of the following commands...
37
+
38
+ --config=weerss.config
39
+
40
+ environment variables can also set options with the prefix WEERSS_
41
+ and the option name in all uppercase. eg:
42
+
43
+ export WEERSS_CONFIG=/home/dave/weerss.config
44
+
45
+ commands:
46
+
47
+ tvrss config
48
+ Print current config.
49
+
50
+ tvrss config FILENAME
51
+ Save current (default?) config to FILENAME.
52
+
53
+ tvrss fetch
54
+ Fetch all feeds and scan files with tvmaze.
55
+
56
+ tvrss list
57
+ List torrents in db.
58
+
59
+ tvrss rss
60
+ Save torrents as rss.
61
+
62
+ tvrss move
63
+ Move downloaded chaotic files to an organised location.
64
+
65
+ tvrss dirs
66
+ Create a script to cleanup TV dirs using tvmaze.
67
+
68
+ tvrss help
69
+ Print this help message.
70
+
71
+ `)
72
+ }
73
+ else
74
+ if( arg1=="config" )
75
+ {
76
+ await weerss.save_config(args)
77
+ }
78
+ else
79
+ if( arg1=="fetch" )
80
+ {
81
+ await weerss.fetch(args)
82
+ }
83
+ else
84
+ if( arg1=="list" )
85
+ {
86
+ await weerss.list(args)
87
+ }
88
+ else
89
+ if( arg1=="rss" )
90
+ {
91
+ await weerss.save_rss(args)
92
+ }
93
+ else
94
+ if( arg1=="move" )
95
+ {
96
+ await weerss.move(args)
97
+ }
98
+ else
99
+ if( arg1=="dirs" )
100
+ {
101
+ await weerss.dirs(args)
102
+ }
103
+ else
104
+ {
105
+ console.log(` Unknown weerss command "${arg1}" `)
106
+ }
package/db.js ADDED
@@ -0,0 +1,245 @@
1
+
2
+ let db={}
3
+ export default db
4
+
5
+
6
+ import { AsyncDatabase } from "promised-sqlite3"
7
+
8
+ import { configure } from 'safe-stable-stringify'
9
+ const stringify = configure({})
10
+
11
+ import weerss from "./weerss.js"
12
+
13
+
14
+ db.test=async function()
15
+ {
16
+ await db.setup()
17
+ await db.set(null,"hello","world")
18
+ await db.set(null,"hell","{world:poop}")
19
+
20
+ console.log( await db.list() )
21
+
22
+ await db.close()
23
+ }
24
+
25
+
26
+
27
+ db.name="weerss"
28
+
29
+ db.setup=async function()
30
+ {
31
+
32
+ db.handle = await AsyncDatabase.open( weerss.get_config_path("sqlite_filename") );
33
+
34
+ // db.handle.inner.on("trace", (sql) => console.log("[TRACE]", sql));
35
+
36
+ await db.handle.run("PRAGMA synchronous = 0 ;");
37
+ await db.handle.run("PRAGMA encoding = \"UTF-8\" ;");
38
+ await db.handle.run("PRAGMA journal_mode=WAL;");
39
+ await db.handle.run("PRAGMA mmap_size=268435456;");
40
+ await db.handle.run("PRAGMA temp_store=2;");
41
+
42
+
43
+ await db.handle.run(`
44
+
45
+ CREATE TABLE IF NOT EXISTS keyval
46
+ (
47
+ key STRING PRIMARY KEY ,
48
+ value JSONB NOT NULL
49
+ );
50
+
51
+ `);
52
+
53
+ await db.handle.run(`
54
+
55
+ CREATE TABLE IF NOT EXISTS hoard
56
+ (
57
+ key STRING PRIMARY KEY ,
58
+ value JSONB NOT NULL
59
+ );
60
+
61
+ `);
62
+
63
+ await db.handle.run(`
64
+
65
+ CREATE TABLE IF NOT EXISTS feeds
66
+ (
67
+ key STRING PRIMARY KEY ,
68
+ value JSONB NOT NULL
69
+ );
70
+
71
+ `);
72
+
73
+ await db.handle.run(`
74
+
75
+ CREATE TABLE IF NOT EXISTS items
76
+ (
77
+ key STRING PRIMARY KEY ,
78
+ value JSONB NOT NULL
79
+ );
80
+
81
+ `);
82
+
83
+ await db.handle.run(`
84
+
85
+ CREATE TABLE IF NOT EXISTS torrents
86
+ (
87
+ key STRING PRIMARY KEY ,
88
+ value JSONB NOT NULL
89
+ );
90
+
91
+ `);
92
+
93
+ await db.handle.run(`
94
+
95
+ CREATE TABLE IF NOT EXISTS shows
96
+ (
97
+ key STRING PRIMARY KEY ,
98
+ value JSONB NOT NULL
99
+ );
100
+
101
+ `);
102
+
103
+ }
104
+
105
+ db.close=async function()
106
+ {
107
+ await db.handle.close()
108
+ db.handle=null
109
+ }
110
+
111
+ db.get=async function(table,key)
112
+ {
113
+ table=table||"keyval"
114
+ let row=await db.handle.get(`
115
+
116
+ SELECT * FROM ${table}
117
+ WHERE key=$key;
118
+
119
+ `,{$key:key})
120
+ return row && row.value && JSON.parse(row.value)
121
+ }
122
+
123
+ db.set=async function(table,key,value)
124
+ {
125
+ table=table||"keyval"
126
+ await db.handle.run(`
127
+
128
+ INSERT INTO ${table} (key,value)
129
+ VALUES ($key,$value)
130
+ ON CONFLICT (key)
131
+ DO
132
+ UPDATE SET value = $value
133
+ WHERE key = $key;
134
+
135
+ `,{$key:key,$value:stringify(value)})
136
+ }
137
+
138
+
139
+ db.clear=async function(table)
140
+ {
141
+ table=table||"keyval"
142
+ await db.handle.run(`
143
+
144
+ DELETE FROM ${table};
145
+
146
+ `,{})
147
+ }
148
+
149
+ db.delete=async function(table,key)
150
+ {
151
+ table=table||"keyval"
152
+ await db.handle.run(`
153
+
154
+ DELETE FROM ${table} WHERE key=$key;
155
+
156
+ `,{$key:key})
157
+ }
158
+
159
+ db.list=async function(table,filter)
160
+ {
161
+ table=table||"keyval"
162
+ filter=filter || {}
163
+ let rs=[]
164
+
165
+ let filters=[]
166
+ for(let fb in filter)
167
+ {
168
+ let fv=filter[fb]
169
+ if(typeof fv=="Number")
170
+ {
171
+ fv=String(fv)
172
+ }
173
+ else
174
+ {
175
+ fv='"'+String(fv)+'"'
176
+ }
177
+
178
+ if(fb.endsWith("_is_null"))
179
+ {
180
+ let f=fb.substring(0,fb.length-8)
181
+ filters.push(" json_extract (value, '$."+f+"') IS NULL ")
182
+ }
183
+ else
184
+ if(fb.endsWith("_is_not_null"))
185
+ {
186
+ let f=fb.substring(0,fb.length-12)
187
+ filters.push(" json_extract (value, '$."+f+"') IS NOT NULL ")
188
+ }
189
+ else
190
+ if(fb.endsWith("_lt"))
191
+ {
192
+ let f=fb.substring(0,fb.length-3)
193
+ filters.push(" json_extract (value, '$."+f+"') < "+fv)
194
+ }
195
+ else
196
+ if(fb.endsWith("_lteq"))
197
+ {
198
+ let f=fb.substring(0,fb.length-5)
199
+ filters.push(" json_extract (value, '$."+f+"') <= "+fv)
200
+ }
201
+ else
202
+ if(fb.endsWith("_gt"))
203
+ {
204
+ let f=fb.substring(0,fb.length-3)
205
+ filters.push(" json_extract (value, '$."+f+"') > "+fv)
206
+ }
207
+ else
208
+ if(fb.endsWith("_gteq"))
209
+ {
210
+ let f=fb.substring(0,fb.length-5)
211
+ filters.push(" json_extract (value, '$."+f+"') >= "+fv)
212
+ }
213
+ else
214
+ if(fb.endsWith("_not"))
215
+ {
216
+ let f=fb.substring(0,fb.length-4)
217
+ filters.push(" json_extract (value, '$."+f+"') != "+fv)
218
+ }
219
+ else
220
+ {
221
+ filters.push(" json_extract (value, '$."+fb+"') = "+fv)
222
+ }
223
+ }
224
+ if( filters.length==0 )
225
+ {
226
+ filters=""
227
+ }
228
+ else
229
+ {
230
+ filters="WHERE "+filters.join(" AND\n")
231
+ }
232
+ // console.log(filters)
233
+
234
+ await db.handle.each(`
235
+
236
+ SELECT * FROM ${table}
237
+ ${filters}
238
+
239
+ `,{},function(row)
240
+ {
241
+ rs.push(JSON.parse(row.value))
242
+ })
243
+
244
+ return rs // filtered only, sorted by date
245
+ }
package/feeds.js ADDED
@@ -0,0 +1,321 @@
1
+
2
+ let feeds={}
3
+ export default feeds
4
+
5
+
6
+ import items from "./items.js"
7
+ import db from "./db.js"
8
+ import jxml from "./jxml.js"
9
+ import hoard from "./hoard.js"
10
+
11
+
12
+ import { configure } from 'safe-stable-stringify'
13
+ const stringify = configure({})
14
+
15
+
16
+ feeds.cached={}
17
+
18
+ feeds.precache=async function() // faster then individual cache, db is fecking slow
19
+ {
20
+ for(let feed of await db.list("feeds"))
21
+ {
22
+ feeds.cache(feed.url,feed)
23
+ }
24
+ }
25
+
26
+ feeds.cache=async function(url,feed) // probably fast
27
+ {
28
+ if(feed) { feeds.cached[url]=feed ; return feed }
29
+ if(feeds.cached[url]) { return feeds.cached[url] }
30
+ await feeds.get(url)
31
+ return feeds.cached[url]
32
+ }
33
+ feeds.get=async function(url) // always slow
34
+ {
35
+ let feed=await db.get("feeds",url)
36
+ if(feed) { feeds.cache(feed.url,feed) }
37
+ return feed
38
+ }
39
+ feeds.set=async function(feed)
40
+ {
41
+ await db.set("feeds",feed.url,feed)
42
+ if(feed) { feeds.cache(feed.url,feed) }
43
+ }
44
+
45
+ feeds.prepare=function(feed)
46
+ {
47
+ let atom=feed.atom||{}
48
+ let rss=feed.rss||{}
49
+
50
+ if(rss["/rss/channel/title"])
51
+ {
52
+ feed.title=rss["/rss/channel/title"]
53
+ }
54
+ else
55
+ if(atom["/feed/title"])
56
+ {
57
+ feed.title=atom["/feed/title"]
58
+ }
59
+
60
+ if(rss["/rss/channel/pubdate"])
61
+ {
62
+ feed.date=new Date(rss["/rss/channel/pubdate"])
63
+ }
64
+ else
65
+ if(atom["/feed/updated"])
66
+ {
67
+ feed.date=new Date(atom["/feed/updated"])
68
+ }
69
+
70
+ return feed
71
+ }
72
+
73
+ feeds.add=async function(it)
74
+ {
75
+ let old=await feeds.cache(it.url) // await db.get("feeds",it.url)
76
+
77
+ let feed={} // merge old and new here
78
+ if(old)
79
+ {
80
+ for(let n in old ){ feed[n]=old[n] }
81
+ }
82
+ for(let n in it ){ feed[n]=it[n] }
83
+ // maintain defaults
84
+ feed.date=feed.date || new Date()
85
+ feed.fails=feed.fails || 0
86
+
87
+ if( stringify(old) != stringify(feed) ) // something changed
88
+ {
89
+ await feeds.set(feed)
90
+ }
91
+ }
92
+
93
+
94
+ feeds.add_opml=async function(data,url)
95
+ {
96
+ if(url){data=await hoard.fetch_text(url,true)}
97
+ if(!data){return}
98
+ let jsn=jxml.parse_xml(data,jxml.xmap.opml)
99
+ //console.log(jsn)
100
+
101
+ let check;check=async function(it)
102
+ {
103
+ if( it["@xmlurl"] )
104
+ {
105
+ let feed={}
106
+ feed.url=it["@xmlurl"]
107
+ await feeds.add(feed)
108
+ }
109
+ if(it["/outline"]) // check sub outlines
110
+ {
111
+ for(let it2 of it["/outline"] )
112
+ {
113
+ await check(it2)
114
+ }
115
+ }
116
+ }
117
+
118
+ if( jsn["/opml/body/outline"] )
119
+ {
120
+ for(let it of jsn["/opml/body/outline"] )
121
+ {
122
+ await check(it)
123
+ }
124
+ }
125
+ else
126
+ if( jsn["/rss/channel/item/title"] ) // looks like a rss
127
+ {
128
+ if(url) // so subscribe to this url
129
+ {
130
+ let feed={}
131
+ feed.url=url
132
+ await feeds.add(feed)
133
+ }
134
+ }
135
+ else
136
+ if( jsn["/feed/entry/title"] ) // looks like attom
137
+ {
138
+ if(url) // so subscribe to this url
139
+ {
140
+ let feed={}
141
+ feed.url=url
142
+ await feeds.add(feed)
143
+ }
144
+ }
145
+ }
146
+
147
+ feeds.build_opml=async function()
148
+ {
149
+ let j={
150
+ "/opml/head/title": "RERSS filter",
151
+ "/opml@version": "1.0",
152
+ "/opml/body/outline": [],
153
+ }
154
+
155
+ let out=j["/opml/body/outline"]
156
+
157
+ let feeds=await db.list("feeds")
158
+ let now=(new Date()).getTime()
159
+
160
+ for(let feed of feeds)
161
+ {
162
+ if(!feed.off) // disabled feeds are not exported
163
+ {
164
+ let it={}
165
+ it["@type"]="rss"
166
+ it["@xmlUrl"]=feed.url
167
+ it["@text"]=feed.title
168
+ it["@title"]=feed.title
169
+ out.push(it)
170
+ }
171
+ }
172
+ let x=jxml.build_xml(j)
173
+ return x
174
+ }
175
+
176
+ feeds.load_opml=async function()
177
+ {
178
+ /*
179
+ let data=await gist.read("weerss.opml")
180
+ await feeds.add_opml(data)
181
+ */
182
+ }
183
+
184
+ feeds.save_opml=async function()
185
+ {
186
+ /*
187
+ let data=await feeds.build_opml()
188
+ await gist.write("weerss.opml",data)
189
+ */
190
+ }
191
+
192
+ feeds.list_length=0
193
+ feeds.list_length_count=0
194
+ feeds.fetch_all=async function()
195
+ {
196
+ console.log("starting feeds fetch_all")
197
+
198
+ let rets=[]
199
+ let list=await db.list("feeds")
200
+
201
+ feeds.list_length=list.length
202
+ feeds.list_length_count=0
203
+
204
+ // try and get popular feeds first
205
+ list.sort(function(a,b){
206
+ return ( b.items_date || 0 ) - ( a.items_date || 0 )
207
+ })
208
+
209
+ let topitem=(await db.list("items",{},"date","prev",1))[0] ; topitem=topitem && topitem.uuid
210
+
211
+ for(let feed of list )
212
+ {
213
+ await feeds.cache(feed.url,feed)
214
+ await feeds.fetch(feed)
215
+ }
216
+
217
+ if(items.add_count>0)
218
+ {
219
+ console.log("+"+items.add_count)
220
+ }
221
+ else
222
+ {
223
+ console.log("")
224
+ }
225
+
226
+ }
227
+
228
+ feeds.fetch=async function(feed)
229
+ {
230
+ console.log(feeds.list_length_count +"/"+ feeds.list_length + " +"+items.add_count)
231
+ if(!feed.off) // check if the feed is turned off
232
+ {
233
+ try{
234
+
235
+ console.log("fetching feed",feed.url)
236
+ let before_count=items.add_count
237
+
238
+
239
+ let txt=await hoard.fetch_text(feed.url)
240
+
241
+ let e1,e2
242
+ try{ feed.rss=jxml.parse_xml(txt,jxml.xmap.rss) }
243
+ catch(e){e1=e}
244
+
245
+ try{ feed.atom=jxml.parse_xml(txt,jxml.xmap.atom) }
246
+ catch(e){e2=e}
247
+
248
+ if( e1 && e2 ) // something really went wrong
249
+ {
250
+ console.log(e1)
251
+ console.log(e2)
252
+ }
253
+
254
+ feeds.prepare(feed)
255
+
256
+ // feed.items_date date is date of most recent item we have seen in the feed
257
+ let check_date=function(it)
258
+ {
259
+ if(it.date)
260
+ {
261
+ if(feed.items_date)
262
+ {
263
+ if( feed.items_date < it.date )
264
+ {
265
+ feed.items_date=it.date
266
+ }
267
+ }
268
+ else
269
+ {
270
+ feed.items_date=it.date
271
+ }
272
+ }
273
+ }
274
+
275
+ if(feed.rss && feed.rss["/rss/channel/item"])
276
+ {
277
+ feed.items_count=0
278
+ for(let rss_item of feed.rss["/rss/channel/item"] )
279
+ {
280
+ let item={rss:rss_item}
281
+ feed.items_count++
282
+ items.prepare(item,feed)
283
+ check_date(item)
284
+ await items.add(item)
285
+ // await hoard.first_text(item.link)
286
+ }
287
+ feed.fails=0
288
+ }
289
+ else
290
+ if(feed.atom && feed.atom["/feed/entry"])
291
+ {
292
+ feed.items_count=0
293
+ for(let atom_entry of feed.atom["/feed/entry"] )
294
+ {
295
+ let item={atom:atom_entry}
296
+ feed.items_count++
297
+ items.prepare(item,feed)
298
+ check_date(item)
299
+ await items.add(item)
300
+ // await hoard.first_text(item.link)
301
+ }
302
+ feed.fails=0
303
+ }
304
+ else
305
+ {
306
+ feed.fails=(feed.fails||0)+1
307
+ }
308
+
309
+ await db.set("feeds",feed.url,feed) // save updated feed info
310
+
311
+ let count=items.add_count-before_count
312
+ if(count>0)
313
+ {
314
+ console.log("added "+count+" new items")
315
+ }
316
+ }catch(e){console.error(e)}
317
+ }
318
+
319
+ feeds.list_length_count++
320
+ }
321
+