@allior/wmake-streamelements-events 2.0.1 → 2.0.3

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.
Files changed (45) hide show
  1. package/package.json +2 -4
  2. package/src/react/hooks/index.ts +0 -3
  3. package/src/react/hooks/use-event-listener.ts +0 -20
  4. package/src/react/hooks/use-on-event-received.ts +0 -108
  5. package/src/react/hooks/use-on-widget-load.ts +0 -15
  6. package/src/react/index.ts +0 -3
  7. package/src/react/types/index.ts +0 -1
  8. package/src/react/types/window-events.ts +0 -6
  9. package/src/root/aggregate.ts +0 -257
  10. package/src/root/classifier.ts +0 -271
  11. package/src/root/commands.ts +0 -39
  12. package/src/root/data/field-value.ts +0 -2
  13. package/src/root/data/index.ts +0 -2
  14. package/src/root/data/widget-load.ts +0 -5
  15. package/src/root/guards/index.ts +0 -1
  16. package/src/root/guards/on-event-received.ts +0 -58
  17. package/src/root/index.ts +0 -11
  18. package/src/root/keys.ts +0 -14
  19. package/src/root/message/index.ts +0 -1
  20. package/src/root/message/twitch/index.ts +0 -2
  21. package/src/root/message/twitch/message.ts +0 -48
  22. package/src/root/message/twitch/user-message-data.ts +0 -112
  23. package/src/root/queue/alert-queue.ts +0 -31
  24. package/src/root/queue/index.ts +0 -2
  25. package/src/root/queue/next-alert-callback.ts +0 -7
  26. package/src/root/sources/alerts.ts +0 -163
  27. package/src/root/sources/index.ts +0 -5
  28. package/src/root/sources/messages.ts +0 -1611
  29. package/src/root/sources/on-widget-load-detail.ts +0 -968
  30. package/src/root/types/index.ts +0 -2
  31. package/src/root/types/on-event-received/base.ts +0 -94
  32. package/src/root/types/on-event-received/cheer.ts +0 -10
  33. package/src/root/types/on-event-received/donation.ts +0 -29
  34. package/src/root/types/on-event-received/follower.ts +0 -9
  35. package/src/root/types/on-event-received/index.ts +0 -58
  36. package/src/root/types/on-event-received/message.ts +0 -13
  37. package/src/root/types/on-event-received/moderation.ts +0 -7
  38. package/src/root/types/on-event-received/other.ts +0 -14
  39. package/src/root/types/on-event-received/raid.ts +0 -10
  40. package/src/root/types/on-event-received/subscriber.ts +0 -77
  41. package/src/root/types/on-widget-load/base.ts +0 -35
  42. package/src/root/types/on-widget-load/index.ts +0 -3
  43. package/src/root/types/on-widget-load/recents.ts +0 -33
  44. package/src/root/types/on-widget-load/session.ts +0 -102
  45. package/src/root/window-event-map.ts +0 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allior/wmake-streamelements-events",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "Twitch/StreamElements subscription event normalizer for widgets (alerts).",
5
5
  "type": "module",
6
6
  "types": "./dist/root/index.d.ts",
@@ -13,12 +13,10 @@
13
13
  "./react": {
14
14
  "types": "./dist/react/index.d.ts",
15
15
  "import": "./dist/react/index.js"
16
- },
17
- "./src/*": "./src/*"
16
+ }
18
17
  },
19
18
  "files": [
20
19
  "dist",
21
- "src",
22
20
  "README.md"
23
21
  ],
24
22
  "scripts": {
@@ -1,3 +0,0 @@
1
- export * from "./use-event-listener.js";
2
- export * from "./use-on-event-received.js";
3
- export * from "./use-on-widget-load.js";
@@ -1,20 +0,0 @@
1
- import { type DependencyList, useEffect, useRef } from "react";
2
- import type {
3
- WindowEventKey,
4
- WindowEventListener,
5
- } from "../types/window-events.js";
6
-
7
- export function useEventListener<K extends WindowEventKey>(
8
- eventName: K,
9
- handler: WindowEventListener<K>,
10
- deps: DependencyList = [],
11
- ): void {
12
- const callbackRef = useRef<WindowEventListener<K>>(handler);
13
- callbackRef.current = handler;
14
-
15
- useEffect(() => {
16
- const fn = (ev: WindowEventMap[K]) => callbackRef.current.call(window, ev);
17
- window.addEventListener(eventName, fn);
18
- return () => window.removeEventListener(eventName, fn);
19
- }, [eventName, ...deps]);
20
- }
@@ -1,108 +0,0 @@
1
- import type { DependencyList } from "react";
2
- import {
3
- isCheerDetail,
4
- isCommunityGiftPurchaseDetail,
5
- isDeleteMessageDetail,
6
- isDeleteMessagesDetail,
7
- isDonationDetail,
8
- isFollowerDetail,
9
- isRaidDetail,
10
- isSubscriberDetail,
11
- isSubscriberLatestDetail,
12
- isTwitchMessageEvent,
13
- isWidgetButtonDetail,
14
- } from "@/root/guards";
15
- import type {
16
- CheerDetail,
17
- CommunityGiftPurchaseDetail,
18
- DeleteMessageDetail,
19
- DeleteMessagesDetail,
20
- DonationDetail,
21
- FollowerDetail,
22
- MinimalEvent,
23
- RaidDetail,
24
- SubscriberDetail,
25
- SubscriberLatestDetail,
26
- TwitchMessageDetail,
27
- WidgetButtonDetail,
28
- } from "@/root/types/on-event-received";
29
- import type { WindowEventListener } from "../types/window-events";
30
- import { useEventListener } from "./use-event-listener";
31
-
32
- function useOnEvent<T extends MinimalEvent>(
33
- guard: (d: unknown) => d is T,
34
- handler: (ev: CustomEvent<T>) => void,
35
- deps: DependencyList = [],
36
- ) {
37
- useEventListener(
38
- "onEventReceived",
39
- (ev) => {
40
- if (guard(ev.detail)) handler(ev as CustomEvent<T>);
41
- },
42
- deps,
43
- );
44
- }
45
-
46
- export function useOnEventReceived(
47
- handler: WindowEventListener<"onEventReceived">,
48
- deps: DependencyList = [],
49
- ): void {
50
- useEventListener("onEventReceived", handler, deps);
51
- }
52
-
53
- export const useOnTwitchMessageReceived = (
54
- handler: (ev: CustomEvent<TwitchMessageDetail>) => void,
55
- deps?: DependencyList,
56
- ) => useOnEvent(isTwitchMessageEvent, handler, deps);
57
-
58
- export const useOnMessageReceived = useOnTwitchMessageReceived;
59
-
60
- export const useOnWidgetButtonReceived = (
61
- handler: (ev: CustomEvent<WidgetButtonDetail>) => void,
62
- deps?: DependencyList,
63
- ) => useOnEvent(isWidgetButtonDetail, handler, deps);
64
-
65
- export const useOnFollowerReceived = (
66
- handler: (ev: CustomEvent<FollowerDetail>) => void,
67
- deps?: DependencyList,
68
- ) => useOnEvent(isFollowerDetail, handler, deps);
69
-
70
- export const useOnCommunityGiftPurchaseReceived = (
71
- handler: (ev: CustomEvent<CommunityGiftPurchaseDetail>) => void,
72
- deps?: DependencyList,
73
- ) => useOnEvent(isCommunityGiftPurchaseDetail, handler, deps);
74
-
75
- export const useOnSubscriberLatestReceived = (
76
- handler: (ev: CustomEvent<SubscriberLatestDetail>) => void,
77
- deps?: DependencyList,
78
- ) => useOnEvent(isSubscriberLatestDetail, handler, deps);
79
-
80
- export const useOnSubscriberReceived = (
81
- handler: (ev: CustomEvent<SubscriberDetail>) => void,
82
- deps?: DependencyList,
83
- ) => useOnEvent(isSubscriberDetail, handler, deps);
84
-
85
- export const useOnCheerReceived = (
86
- handler: (ev: CustomEvent<CheerDetail>) => void,
87
- deps?: DependencyList,
88
- ) => useOnEvent(isCheerDetail, handler, deps);
89
-
90
- export const useOnRaidReceived = (
91
- handler: (ev: CustomEvent<RaidDetail>) => void,
92
- deps?: DependencyList,
93
- ) => useOnEvent(isRaidDetail, handler, deps);
94
-
95
- export const useOnDonationReceived = (
96
- handler: (ev: CustomEvent<DonationDetail>) => void,
97
- deps?: DependencyList,
98
- ) => useOnEvent(isDonationDetail, handler, deps);
99
-
100
- export const useOnDeleteMessage = (
101
- handler: (ev: CustomEvent<DeleteMessageDetail>) => void,
102
- deps?: DependencyList,
103
- ) => useOnEvent(isDeleteMessageDetail, handler, deps);
104
-
105
- export const useOnDeleteMessages = (
106
- handler: (ev: CustomEvent<DeleteMessagesDetail>) => void,
107
- deps?: DependencyList,
108
- ) => useOnEvent(isDeleteMessagesDetail, handler, deps);
@@ -1,15 +0,0 @@
1
- import type { DependencyList } from "react";
2
- import type { WindowEventListener } from "@/react/types/window-events.js";
3
- import type { OnWidgetLoadEventDetails } from "@/root/types/on-widget-load";
4
- import { useEventListener } from "./use-event-listener.js";
5
-
6
- export function useOnWidgetLoad<FieldData = unknown>(
7
- handler: (ev: CustomEvent<OnWidgetLoadEventDetails<FieldData>>) => void,
8
- deps: DependencyList = [],
9
- ): void {
10
- useEventListener(
11
- "onWidgetLoad",
12
- handler as WindowEventListener<"onWidgetLoad">,
13
- deps,
14
- );
15
- }
@@ -1,3 +0,0 @@
1
- export * from "../root";
2
- export * from "./hooks";
3
- export * from "./types";
@@ -1 +0,0 @@
1
- export * from "./window-events";
@@ -1,6 +0,0 @@
1
- export type WindowEventKey = keyof WindowEventMap;
2
-
3
- export type WindowEventListener<K extends WindowEventKey> = (
4
- this: Window,
5
- ev: WindowEventMap[K],
6
- ) => unknown;
@@ -1,257 +0,0 @@
1
- /**
2
- * Event normalizer — aggregate mode: buffer events by activityGroup,
3
- * merge into one normalized event per action (timeout 2.5s).
4
- */
5
-
6
- import { classifyEvent, getCacheKey } from "./classifier.js";
7
- import type {
8
- CacheSubsByType,
9
- ClassifiedCommunityGiftPurchase,
10
- ClassifiedEvent,
11
- ClassifiedRecipient,
12
- IncomingDetail,
13
- NormalizerOptions,
14
- } from "./types";
15
-
16
- const AGGREGATE_TIMEOUT_MS = 2500;
17
-
18
- interface BufferEntry {
19
- type: string;
20
- subscribers: Array<{
21
- recipient?: string;
22
- username?: string;
23
- name?: string;
24
- sender?: string;
25
- }>;
26
- tier: number;
27
- tierText: string;
28
- totalAmount: number;
29
- purchase?: ClassifiedCommunityGiftPurchase;
30
- sender?: string;
31
- }
32
-
33
- const bufferByGroup: Record<string, BufferEntry> = {};
34
- const bufferTimers: Record<string, ReturnType<typeof setTimeout>> = {};
35
-
36
- function getCacheForType(
37
- cacheSubs: CacheSubsByType | undefined,
38
- type: string,
39
- ): {
40
- _has: (k: string) => boolean;
41
- _set: (k: string, v: boolean) => void;
42
- } | null {
43
- if (!cacheSubs) return null;
44
- if (
45
- typeof cacheSubs._has === "function" &&
46
- typeof cacheSubs._set === "function"
47
- ) {
48
- return cacheSubs as unknown as {
49
- _has: (k: string) => boolean;
50
- _set: (k: string, v: boolean) => void;
51
- };
52
- }
53
- const c = cacheSubs[type];
54
- return c && typeof (c as { _has?: unknown })._has === "function"
55
- ? (c as {
56
- _has: (k: string) => boolean;
57
- _set: (k: string, v: boolean) => void;
58
- })
59
- : null;
60
- }
61
-
62
- function emitAggregated(
63
- groupKey: string,
64
- merged: ClassifiedCommunityGiftPurchase,
65
- cacheSubs: CacheSubsByType | undefined,
66
- onNormalized: (event: ClassifiedEvent | null) => void,
67
- ): void {
68
- const cache = getCacheForType(cacheSubs, merged.type as string);
69
- if (cache?._has(groupKey)) return;
70
- if (cache) cache._set(groupKey, true);
71
- onNormalized(merged);
72
- }
73
-
74
- function flushBuffer(
75
- groupKey: string,
76
- cacheSubs: CacheSubsByType | undefined,
77
- onNormalized: (event: ClassifiedEvent | null) => void,
78
- broadcaster: string,
79
- ): void {
80
- const buf = bufferByGroup[groupKey];
81
- if (!buf) return;
82
- delete bufferByGroup[groupKey];
83
- if (bufferTimers[groupKey]) {
84
- clearTimeout(bufferTimers[groupKey]);
85
- delete bufferTimers[groupKey];
86
- }
87
- const purchase = buf.purchase;
88
- const subscribers = buf.subscribers ?? [];
89
- const type = buf.type;
90
- const tier = buf.tier;
91
- const tierText = buf.tierText;
92
- const totalAmount = buf.totalAmount ?? subscribers.length;
93
- const recipients = subscribers
94
- .map((s) => s.recipient ?? s.username ?? s.name)
95
- .filter(Boolean) as string[];
96
- const firstSub = subscribers[0];
97
- const senderFromSub = firstSub?.sender;
98
- const sender = purchase?.sender ?? buf.sender ?? senderFromSub;
99
- if (type === "community-gift" && broadcaster && sender === broadcaster) {
100
- return;
101
- }
102
- if (type === "community-gift-anonymous") {
103
- emitAggregated(
104
- groupKey,
105
- {
106
- type: "community-gift-anonymous",
107
- tier,
108
- tierText,
109
- totalAmount,
110
- recipients,
111
- anonymousDisplayName: purchase?.anonymousDisplayName,
112
- },
113
- cacheSubs,
114
- onNormalized,
115
- );
116
- } else {
117
- emitAggregated(
118
- groupKey,
119
- {
120
- type: "community-gift",
121
- sender,
122
- tier,
123
- tierText,
124
- totalAmount,
125
- recipients,
126
- },
127
- cacheSubs,
128
- onNormalized,
129
- );
130
- }
131
- }
132
-
133
- /**
134
- * Normalizes an incoming subscription event (StreamElements/Twitch).
135
- * Buffers community-gift recipients by activityGroup, then emits one normalized event per group.
136
- * For self-sub, solo-sub, sub-renewal emits immediately (with optional cache dedup).
137
- */
138
- export function normalizeIncomingEvent(
139
- detail: IncomingDetail,
140
- options: NormalizerOptions | undefined,
141
- onNormalized: (event: ClassifiedEvent | null) => void,
142
- ): void {
143
- const cacheSubs = options?.cacheSubs;
144
- const broadcaster = options?.broadcasterLogin ?? "";
145
-
146
- const classified = classifyEvent(detail);
147
- if (!classified) {
148
- onNormalized(null);
149
- return;
150
- }
151
-
152
- const cache = getCacheForType(cacheSubs, classified.type);
153
- const cacheKey = getCacheKey(detail);
154
- const event = detail.event ?? {};
155
- const data = (event.data ?? event) as Record<string, unknown>;
156
-
157
- if ((classified as ClassifiedRecipient)._role === "recipient") {
158
- const recipient = classified as ClassifiedRecipient;
159
- const ag = recipient.activityGroup ?? cacheKey;
160
- if (!bufferByGroup[ag]) {
161
- bufferByGroup[ag] = {
162
- type: recipient.type,
163
- subscribers: [],
164
- tier: recipient.tier,
165
- tierText: recipient.tierText,
166
- totalAmount: 0,
167
- };
168
- }
169
- if (bufferTimers[ag]) clearTimeout(bufferTimers[ag]);
170
- bufferTimers[ag] = setTimeout(() => {
171
- flushBuffer(ag, cacheSubs, onNormalized, broadcaster);
172
- }, AGGREGATE_TIMEOUT_MS);
173
- bufferByGroup[ag].subscribers.push({
174
- recipient: recipient.recipient,
175
- username: data.username as string,
176
- name: data.displayName as string,
177
- sender: data.sender as string,
178
- });
179
- if (bufferByGroup[ag].totalAmount === 0) bufferByGroup[ag].totalAmount = 1;
180
- return;
181
- }
182
-
183
- if (
184
- classified.type === "community-gift-anonymous" ||
185
- classified.type === "community-gift"
186
- ) {
187
- const isPurchase =
188
- detail.listener === "event" && event.type === "communityGiftPurchase";
189
- if (isPurchase) {
190
- const ag = (event.activityGroup ??
191
- data?.activityGroup ??
192
- cacheKey) as string;
193
- const cg = classified as ClassifiedCommunityGiftPurchase;
194
- if (!bufferByGroup[ag]) {
195
- bufferByGroup[ag] = {
196
- type: cg.type,
197
- purchase: cg,
198
- subscribers: [],
199
- tier: cg.tier,
200
- tierText: cg.tierText,
201
- totalAmount: Number(cg.totalAmount) || 0,
202
- sender: cg.sender,
203
- };
204
- bufferTimers[ag] = setTimeout(() => {
205
- flushBuffer(ag, cacheSubs, onNormalized, broadcaster);
206
- }, AGGREGATE_TIMEOUT_MS);
207
- } else {
208
- bufferByGroup[ag].purchase = cg;
209
- bufferByGroup[ag].tier = cg.tier;
210
- bufferByGroup[ag].tierText = cg.tierText;
211
- bufferByGroup[ag].totalAmount =
212
- (cg.totalAmount as number) ?? bufferByGroup[ag].totalAmount;
213
- bufferByGroup[ag].sender = cg.sender as string;
214
- if (bufferTimers[ag]) clearTimeout(bufferTimers[ag]);
215
- bufferTimers[ag] = setTimeout(() => {
216
- flushBuffer(ag, cacheSubs, onNormalized, broadcaster);
217
- }, AGGREGATE_TIMEOUT_MS);
218
- }
219
- return;
220
- }
221
- if (detail.listener === "subscriber-latest") {
222
- const c = classified as ClassifiedCommunityGiftPurchase;
223
- if (
224
- classified.type === "community-gift" &&
225
- broadcaster &&
226
- c.sender === broadcaster
227
- ) {
228
- onNormalized(null);
229
- return;
230
- }
231
- return;
232
- }
233
- return;
234
- }
235
-
236
- if (
237
- classified.type === "self-sub" ||
238
- classified.type === "solo-sub-to-someone" ||
239
- classified.type === "sub-renewal"
240
- ) {
241
- if (cache?._has(cacheKey)) {
242
- onNormalized(null);
243
- return;
244
- }
245
- if (cache) cache._set(cacheKey, true);
246
- const c = classified as unknown as Record<string, unknown>;
247
- if (
248
- classified.type === "solo-sub-to-someone" &&
249
- broadcaster &&
250
- c.sender === broadcaster
251
- ) {
252
- onNormalized(null);
253
- return;
254
- }
255
- onNormalized(classified);
256
- }
257
- }