hn-bits 0.7.0 โ 0.7.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.
- package/dist/notify/desktop.js +4 -3
- package/dist/notify/telegram.js +3 -2
- package/dist/watch.js +60 -41
- package/package.json +1 -1
package/dist/notify/desktop.js
CHANGED
|
@@ -27,14 +27,15 @@ export function findAlerter() {
|
|
|
27
27
|
}
|
|
28
28
|
return null;
|
|
29
29
|
}
|
|
30
|
-
function wrapperArguments({
|
|
30
|
+
function wrapperArguments({ subscriptions, story }, timeoutSeconds, alerterPath) {
|
|
31
|
+
const names = subscriptions.map((s) => s.name).join(', ');
|
|
31
32
|
return [
|
|
32
33
|
'sh',
|
|
33
|
-
`๐ ${
|
|
34
|
+
`๐ ${names}`,
|
|
34
35
|
`${story.score} pts ยท ${story.descendants} comments`,
|
|
35
36
|
story.title,
|
|
36
37
|
String(timeoutSeconds),
|
|
37
|
-
`hn-${
|
|
38
|
+
`hn-${story.id}`,
|
|
38
39
|
story.url ?? hnItemUrl(story.id),
|
|
39
40
|
alerterPath,
|
|
40
41
|
];
|
package/dist/notify/telegram.js
CHANGED
|
@@ -5,8 +5,9 @@ export class NotifyError extends Error {
|
|
|
5
5
|
function escapeHtml(text) {
|
|
6
6
|
return text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
7
7
|
}
|
|
8
|
-
function buildMessage({
|
|
9
|
-
const
|
|
8
|
+
function buildMessage({ subscriptions, story }) {
|
|
9
|
+
const names = subscriptions.map((s) => s.name).join(', ');
|
|
10
|
+
const lines = [`๐ <b>${escapeHtml(names)}</b>`];
|
|
10
11
|
if (story.url) {
|
|
11
12
|
lines.push(`<a href="${story.url}">${escapeHtml(story.title)}</a>`);
|
|
12
13
|
lines.push(`${story.score} points ยท ${story.descendants} comments ยท by ${escapeHtml(story.by)}`);
|
package/dist/watch.js
CHANGED
|
@@ -32,57 +32,77 @@ function buildNotifiers() {
|
|
|
32
32
|
}
|
|
33
33
|
return { notifiers, desktopSkipped };
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
/** Query plus unseen filter for one subscription. null on query failure. */
|
|
36
|
+
async function fetchMatches(sub, now) {
|
|
36
37
|
try {
|
|
37
|
-
|
|
38
|
-
await notifier.send({ subscription: sub, story });
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
log(`${sub.name}: notify failed for ${story.id} - ${err.message}`);
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
async function processSubscription(sub, notifiers, now, dryRun) {
|
|
47
|
-
let stories;
|
|
48
|
-
try {
|
|
49
|
-
stories = await searchRecent(sub.query, {
|
|
38
|
+
const stories = await searchRecent(sub.query, {
|
|
50
39
|
createdAfter: windowStart(sub.lastRunAt, now),
|
|
51
40
|
minPoints: sub.minPoints,
|
|
52
41
|
minComments: sub.minComments,
|
|
53
42
|
});
|
|
43
|
+
return stories.filter((story) => !isSeen(story.id, sub.id));
|
|
54
44
|
}
|
|
55
45
|
catch (err) {
|
|
56
46
|
log(`${sub.name}: query failed - ${err.message}`);
|
|
57
|
-
return
|
|
47
|
+
return null;
|
|
58
48
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
49
|
+
}
|
|
50
|
+
/** Phase A: fetch every subscription, merging stories matched by more than one. */
|
|
51
|
+
async function collectPending(subs, now) {
|
|
52
|
+
const pending = new Map();
|
|
53
|
+
const fetchedSubs = [];
|
|
54
|
+
let failedSubs = 0;
|
|
55
|
+
for (const sub of subs) {
|
|
56
|
+
const matches = await fetchMatches(sub, now);
|
|
57
|
+
if (matches === null) {
|
|
58
|
+
failedSubs++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
fetchedSubs.push(sub);
|
|
62
|
+
for (const story of matches) {
|
|
63
|
+
const entry = pending.get(story.id);
|
|
64
|
+
if (entry)
|
|
65
|
+
entry.subscriptions.push(sub);
|
|
66
|
+
else
|
|
67
|
+
pending.set(story.id, { story, subscriptions: [sub] });
|
|
68
|
+
}
|
|
65
69
|
}
|
|
66
|
-
|
|
67
|
-
|
|
70
|
+
return { pending, fetchedSubs, failedSubs };
|
|
71
|
+
}
|
|
72
|
+
async function notifyStory(subscriptions, story, notifiers) {
|
|
73
|
+
const names = subscriptions.map((s) => s.name).join(', ');
|
|
74
|
+
try {
|
|
75
|
+
for (const notifier of notifiers)
|
|
76
|
+
await notifier.send({ subscriptions, story });
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
catch (err) {
|
|
80
|
+
log(`${names}: notify failed for ${story.id} - ${err.message}`);
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/** Phase B: one notify per story, oldest first. */
|
|
85
|
+
async function dispatchPending(pending, notifiers, now, dryRun) {
|
|
86
|
+
const ordered = [...pending.values()].sort((a, b) => a.story.time - b.story.time);
|
|
87
|
+
let totalNotified = 0;
|
|
68
88
|
let hadFailure = false;
|
|
69
|
-
for (const story of
|
|
89
|
+
for (const { story, subscriptions } of ordered) {
|
|
90
|
+
const names = subscriptions.map((s) => s.name).join(', ');
|
|
70
91
|
if (dryRun) {
|
|
71
|
-
log(`would notify: [${
|
|
92
|
+
log(`would notify: [${names}] ${story.title} (${story.score} pts)`);
|
|
72
93
|
continue;
|
|
73
94
|
}
|
|
74
|
-
const sent = await notifyStory(
|
|
95
|
+
const sent = await notifyStory(subscriptions, story, notifiers);
|
|
75
96
|
if (!sent) {
|
|
76
97
|
hadFailure = true;
|
|
77
98
|
continue;
|
|
78
99
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
notified
|
|
100
|
+
for (const sub of subscriptions)
|
|
101
|
+
markSeen(story.id, sub.id, now);
|
|
102
|
+
log(`${names}: notified ${story.id} "${story.title}" (${story.score} pts)`);
|
|
103
|
+
totalNotified++;
|
|
82
104
|
}
|
|
83
|
-
|
|
84
|
-
touchLastRun(sub.id, now);
|
|
85
|
-
return { notified, hadFailure };
|
|
105
|
+
return { totalNotified, hadFailure };
|
|
86
106
|
}
|
|
87
107
|
/** hn watch --once: one-shot pass over all subscriptions. Returns the process exit code. */
|
|
88
108
|
export async function runWatch(options) {
|
|
@@ -100,14 +120,13 @@ export async function runWatch(options) {
|
|
|
100
120
|
}
|
|
101
121
|
log(`watch: ${subs.length} subscriptions`);
|
|
102
122
|
const now = Math.floor(Date.now() / 1000);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
}
|
|
123
|
+
const { pending, fetchedSubs, failedSubs } = await collectPending(subs, now);
|
|
124
|
+
if (pending.size === 0)
|
|
125
|
+
log('no new matches');
|
|
126
|
+
const { totalNotified, hadFailure } = await dispatchPending(pending, notifiers, now, options.dryRun);
|
|
127
|
+
if (!options.dryRun)
|
|
128
|
+
for (const sub of fetchedSubs)
|
|
129
|
+
touchLastRun(sub.id, now);
|
|
111
130
|
log(`done: ${totalNotified} notified, ${failedSubs} failed`);
|
|
112
|
-
return failedSubs > 0 ? 1 : 0;
|
|
131
|
+
return failedSubs > 0 || hadFailure ? 1 : 0;
|
|
113
132
|
}
|