moshcode 0.48.0 → 0.49.0
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/package.json +1 -1
- package/src/news-sources.mjs +61 -19
- package/src/profullstack-feeds.opml +25 -0
- package/src/settings-sync.mjs +10 -0
package/package.json
CHANGED
package/src/news-sources.mjs
CHANGED
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
// holds feeds with stable well-known URLs and defers everything else to the
|
|
21
21
|
// published lists, where the list is somebody else's to maintain.
|
|
22
22
|
|
|
23
|
+
import fs from "node:fs";
|
|
24
|
+
|
|
23
25
|
/**
|
|
24
26
|
* Bing News query feed — the only search feed left.
|
|
25
27
|
*
|
|
@@ -86,6 +88,53 @@ export function isDeadEndLink(url) {
|
|
|
86
88
|
}
|
|
87
89
|
}
|
|
88
90
|
|
|
91
|
+
/** The vendored copy of what profullstack.com/feeds.opml serves. */
|
|
92
|
+
const PROFULLSTACK_OPML = new URL("./profullstack-feeds.opml", import.meta.url);
|
|
93
|
+
|
|
94
|
+
/** slugify() from news.mjs, kept in step by a test rather than imported. */
|
|
95
|
+
function slug(label) {
|
|
96
|
+
return String(label ?? "")
|
|
97
|
+
.toLowerCase()
|
|
98
|
+
.replace(/['’]/g, "")
|
|
99
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
100
|
+
.replace(/^-+|-+$/g, "")
|
|
101
|
+
.slice(0, 32);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* The profullstack blogs, read from the vendored OPML rather than typed out.
|
|
106
|
+
*
|
|
107
|
+
* Read synchronously and at module load because defaultFeeds() is synchronous —
|
|
108
|
+
* a fresh install must not wait on a network call, or on a promise, to show
|
|
109
|
+
* anything at all. The file ships with the package (`files` includes `src`), so
|
|
110
|
+
* it is there in both the npm and the install.sh channel.
|
|
111
|
+
*
|
|
112
|
+
* Parsed here with a small matcher instead of news.mjs's parseOpml, because
|
|
113
|
+
* news.mjs imports this module and taking the import back the other way makes a
|
|
114
|
+
* cycle. The matcher can afford to be small: this is our own file, flat, and a
|
|
115
|
+
* test asserts the two agree on every feed it contains.
|
|
116
|
+
*
|
|
117
|
+
* A missing or unreadable file degrades to no profullstack defaults rather than
|
|
118
|
+
* throwing, which would take `/news` down entirely over a packaging mistake.
|
|
119
|
+
*/
|
|
120
|
+
function profullstackFeeds() {
|
|
121
|
+
let xml;
|
|
122
|
+
try { xml = fs.readFileSync(PROFULLSTACK_OPML, "utf8"); }
|
|
123
|
+
catch { return []; }
|
|
124
|
+
|
|
125
|
+
const feeds = [];
|
|
126
|
+
const seen = new Set();
|
|
127
|
+
for (const tag of xml.match(/<outline\b[^>]*>/gi) ?? []) {
|
|
128
|
+
const attr = (name) => (new RegExp(`\\b${name}="([^"]*)"`, "i").exec(tag) ?? [])[1] ?? "";
|
|
129
|
+
const url = attr("xmlUrl");
|
|
130
|
+
if (!/^https?:\/\//i.test(url) || seen.has(url)) continue;
|
|
131
|
+
seen.add(url);
|
|
132
|
+
const title = attr("title") || attr("text") || url;
|
|
133
|
+
feeds.push({ name: slug(title), title, url, site: attr("htmlUrl"), category: "profullstack" });
|
|
134
|
+
}
|
|
135
|
+
return feeds;
|
|
136
|
+
}
|
|
137
|
+
|
|
89
138
|
/**
|
|
90
139
|
* The feeds a fresh install reads.
|
|
91
140
|
*
|
|
@@ -120,25 +169,18 @@ export const DEFAULT_FEEDS = [
|
|
|
120
169
|
|
|
121
170
|
{ name: "npr-politics", title: "NPR — Politics", url: "https://feeds.npr.org/1014/rss.xml", site: "https://www.npr.org", category: "politics" },
|
|
122
171
|
|
|
123
|
-
// The profullstack blogs, read out of the box.
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{ name: "logicsrc-blog", title: "LogicSRC Blog", url: "https://logicsrc.com/blog/rss.xml", site: "https://logicsrc.com/blog", category: "profullstack" },
|
|
136
|
-
{ name: "pairux-blog", title: "PairUX Blog", url: "https://pairux.com/blog/rss.xml", site: "https://pairux.com/blog", category: "profullstack" },
|
|
137
|
-
{ name: "qryptchat-blog", title: "QryptChat Blog", url: "https://qrypt.chat/blog/rss.xml", site: "https://qrypt.chat/blog", category: "profullstack" },
|
|
138
|
-
{ name: "saasrow-blog", title: "SaaSRow Blog", url: "https://www.saasrow.com/blog/rss.xml", site: "https://www.saasrow.com/blog", category: "profullstack" },
|
|
139
|
-
{ name: "sh1pt-blog", title: "sh1pt Blog", url: "https://sh1pt.com/blog/rss.xml", site: "https://sh1pt.com/blog", category: "profullstack" },
|
|
140
|
-
{ name: "threatcrush-blog", title: "ThreatCrush Blog", url: "https://threatcrush.com/blog/rss.xml", site: "https://threatcrush.com/blog", category: "profullstack" },
|
|
141
|
-
{ name: "ugig-blog", title: "ugig Blog", url: "https://ugig.net/blog/rss.xml", site: "https://ugig.net/blog", category: "profullstack" },
|
|
172
|
+
// The profullstack blogs, read out of the box. Not typed out here: they are
|
|
173
|
+
// parsed from src/profullstack-feeds.opml, which is a copy of what
|
|
174
|
+
// profullstack.com/feeds.opml actually serves. Two hand-maintained lists of
|
|
175
|
+
// the same fourteen blogs is one more than can be kept in step, and the copy
|
|
176
|
+
// that would go stale is this one — nobody editing the published OPML has a
|
|
177
|
+
// reason to think about moshcode. Refresh it with:
|
|
178
|
+
//
|
|
179
|
+
// curl -sL https://profullstack.com/feeds.opml -o src/profullstack-feeds.opml
|
|
180
|
+
//
|
|
181
|
+
// test/profullstack-feeds.test.mjs checks that against the live file when
|
|
182
|
+
// MOSHCODE_CHECK_FEED_DRIFT=1 is set.
|
|
183
|
+
...profullstackFeeds(),
|
|
142
184
|
];
|
|
143
185
|
|
|
144
186
|
/**
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<opml version="2.0">
|
|
3
|
+
<head>
|
|
4
|
+
<title>Profullstack Blogs</title>
|
|
5
|
+
<ownerName>Profullstack</ownerName>
|
|
6
|
+
<ownerId>https://profullstack.com</ownerId>
|
|
7
|
+
<dateCreated>Thu, 13 Aug 2026 00:00:00 +0000</dateCreated>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<outline text="BitTorrented Blog" title="BitTorrented Blog" type="rss" xmlUrl="https://bittorrented.com/blog/rss.xml" htmlUrl="https://bittorrented.com/blog" />
|
|
11
|
+
<outline text="bl0ggers Blog" title="bl0ggers Blog" type="rss" xmlUrl="https://bl0ggers.com/blog/rss.xml" htmlUrl="https://bl0ggers.com/blog" />
|
|
12
|
+
<outline text="c0mpute Blog" title="c0mpute Blog" type="rss" xmlUrl="https://c0mpute.com/blog/rss.xml" htmlUrl="https://c0mpute.com/blog" />
|
|
13
|
+
<outline text="c0upons Blog" title="c0upons Blog" type="rss" xmlUrl="https://c0upons.com/blog/rss.xml" htmlUrl="https://c0upons.com/blog" />
|
|
14
|
+
<outline text="CoinPay Blog" title="CoinPay Blog" type="rss" xmlUrl="https://coinpayportal.com/blog/rss.xml" htmlUrl="https://coinpayportal.com/blog" />
|
|
15
|
+
<outline text="CrawlProof Blog" title="CrawlProof Blog" type="rss" xmlUrl="https://crawlproof.com/blog/rss.xml" htmlUrl="https://crawlproof.com/blog" />
|
|
16
|
+
<outline text="d0rz Blog" title="d0rz Blog" type="rss" xmlUrl="https://d0rz.com/blog/rss.xml" htmlUrl="https://d0rz.com/blog" />
|
|
17
|
+
<outline text="LogicSRC Blog" title="LogicSRC Blog" type="rss" xmlUrl="https://logicsrc.com/blog/rss.xml" htmlUrl="https://logicsrc.com/blog" />
|
|
18
|
+
<outline text="PairUX Blog" title="PairUX Blog" type="rss" xmlUrl="https://pairux.com/blog/rss.xml" htmlUrl="https://pairux.com/blog" />
|
|
19
|
+
<outline text="QryptChat Blog" title="QryptChat Blog" type="rss" xmlUrl="https://qrypt.chat/blog/rss.xml" htmlUrl="https://qrypt.chat/blog" />
|
|
20
|
+
<outline text="SaaSRow Blog" title="SaaSRow Blog" type="rss" xmlUrl="https://www.saasrow.com/blog/rss.xml" htmlUrl="https://www.saasrow.com/blog" />
|
|
21
|
+
<outline text="sh1pt Blog" title="sh1pt Blog" type="rss" xmlUrl="https://sh1pt.com/blog/rss.xml" htmlUrl="https://sh1pt.com/blog" />
|
|
22
|
+
<outline text="ThreatCrush Blog" title="ThreatCrush Blog" type="rss" xmlUrl="https://threatcrush.com/blog/rss.xml" htmlUrl="https://threatcrush.com/blog" />
|
|
23
|
+
<outline text="ugig Blog" title="ugig Blog" type="rss" xmlUrl="https://ugig.net/blog/rss.xml" htmlUrl="https://ugig.net/blog" />
|
|
24
|
+
</body>
|
|
25
|
+
</opml>
|
package/src/settings-sync.mjs
CHANGED
|
@@ -64,6 +64,16 @@ export const SYNCED_FILES = [
|
|
|
64
64
|
// and read by nothing here — moshcode's interest in it begins and ends with
|
|
65
65
|
// moving it, and a file this does not parse cannot be broken by this.
|
|
66
66
|
{ path: "feeds.opml", json: false, label: "rss feeds" },
|
|
67
|
+
// `/news` and `/rss` keep their subscriptions here — see opmlFile() in
|
|
68
|
+
// news.mjs. A near-identical name sat in this list above it for a while and
|
|
69
|
+
// the two were easy to mistake for each other, so: feeds.opml belongs to
|
|
70
|
+
// tcfeed, news.opml belongs to this. Carrying only the first meant the feeds
|
|
71
|
+
// you actually subscribed to were the one thing `/save` left behind.
|
|
72
|
+
//
|
|
73
|
+
// Deliberately no cap change. A subscription list past MAX_FILE_BYTES is
|
|
74
|
+
// reported as skipped rather than failing the snapshot, which is the right
|
|
75
|
+
// answer for a list that got big by importing somebody else's.
|
|
76
|
+
{ path: "news.opml", json: false, label: "news subscriptions" },
|
|
67
77
|
];
|
|
68
78
|
|
|
69
79
|
/**
|