Subscribe notifications

This commit is contained in:
koalasat
2025-07-22 19:06:03 +02:00
parent d9385ae0ea
commit ba6353a57e
4 changed files with 59 additions and 13 deletions

View File

@ -53,10 +53,10 @@ class Nostr:
Tag.parse( Tag.parse(
[ [
"order_id", "order_id",
f"{config("COORDINATOR_ALIAS", cast=str)}#{order.id}", f"{config("COORDINATOR_ALIAS", cast=str).lower()}#{order.id}",
] ]
), ),
Tag.parse(["status", Order.Status(order.status).label]), Tag.parse(["status", order.status]),
] ]
await client.send_private_msg(PublicKey.parse(robot.nostr_pubkey), text, tags) await client.send_private_msg(PublicKey.parse(robot.nostr_pubkey), text, tags)

View File

@ -37,7 +37,7 @@ class Notifications:
self.save_message(order, robot, title, description) self.save_message(order, robot, title, description)
if robot.nostr_pubkey: if robot.nostr_pubkey:
nostr_send_notification_event.delay( nostr_send_notification_event.delay(
robot_id=robot.id, order_id=order.id, text=description robot_id=robot.id, order_id=order.id, text=title
) )
if robot.telegram_enabled: if robot.telegram_enabled:
self.send_telegram_message(robot.telegram_chat_id, title, description) self.send_telegram_message(robot.telegram_chat_id, title, description)

View File

@ -3,6 +3,8 @@ import { Box, Drawer, List, ListItem, Typography, useTheme } from '@mui/material
import { AppContext, type UseAppStoreType } from '../../../contexts/AppContext'; import { AppContext, type UseAppStoreType } from '../../../contexts/AppContext';
import { RoboSatsTextIcon } from '../../../components/Icons'; import { RoboSatsTextIcon } from '../../../components/Icons';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { UseFederationStoreType, FederationContext } from '../../../contexts/FederationContext';
import { GarageContext, UseGarageStoreType } from '../../../contexts/GarageContext';
interface NotificationsDrawerProps { interface NotificationsDrawerProps {
show: boolean; show: boolean;
@ -12,11 +14,25 @@ interface NotificationsDrawerProps {
const NotificationsDrawer = ({ show, setShow }: NotificationsDrawerProps): React.JSX.Element => { const NotificationsDrawer = ({ show, setShow }: NotificationsDrawerProps): React.JSX.Element => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const theme = useTheme();
const { page } = useContext<UseAppStoreType>(AppContext); const { page, settings } = useContext<UseAppStoreType>(AppContext);
const { federation } = useContext<UseFederationStoreType>(FederationContext);
const { garage } = useContext<UseGarageStoreType>(GarageContext);
useEffect(() => { useEffect(() => {
setShow(false); setShow(false);
}, [page]); }, [page]);
useEffect(() => {
if (settings.connection === 'nostr' && !federation.loading) loadNotifciationsNostr();
}, [settings.connection, federation.loading]);
const loadNotifciationsNostr = (): void => {
federation.roboPool.subscribeNotifications(garage, {
onevent: (_event) => {},
oneose: () => {},
});
};
return ( return (
<Drawer anchor='right' open={show} onClose={() => setShow(false)}> <Drawer anchor='right' open={show} onClose={() => setShow(false)}>
<Box sx={{ width: 270, height: '100%' }} role='presentation'> <Box sx={{ width: 270, height: '100%' }} role='presentation'>

View File

@ -1,5 +1,5 @@
import { type Event } from 'nostr-tools'; import { nip17, type Event } from 'nostr-tools';
import { type Coordinator, type Settings } from '../../models'; import { Garage, type Coordinator, type Settings } from '../../models';
import defaultFederation from '../../../static/federation.json'; import defaultFederation from '../../../static/federation.json';
import { websocketClient, type WebsocketConnection, WebsocketState } from '../Websocket'; import { websocketClient, type WebsocketConnection, WebsocketState } from '../Websocket';
import thirdParties from '../../../static/thirdparties.json'; import thirdParties from '../../../static/thirdparties.json';
@ -100,14 +100,17 @@ class RoboPool {
} }
const authors = scope.map((f) => f.nostrHexPubkey).filter((item) => item !== undefined); const authors = scope.map((f) => f.nostrHexPubkey).filter((item) => item !== undefined);
const subscribeBookPending = 'subscribeBookPending';
const subscribeBookSuccess = 'subscribeBookPending';
const requestPending = [ const requestPending = [
'REQ', 'REQ',
'subscribeBookPending', subscribeBookPending,
{ authors, kinds: [38383], '#s': ['pending'] }, { authors, kinds: [38383], '#s': ['pending'] },
]; ];
const requestSuccess = [ const requestSuccess = [
'REQ', 'REQ',
'subscribeBookSuccess', subscribeBookSuccess,
{ {
authors, authors,
kinds: [38383], kinds: [38383],
@ -118,6 +121,9 @@ class RoboPool {
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => { this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
const jsonMessage = JSON.parse(messageEvent.data); const jsonMessage = JSON.parse(messageEvent.data);
if (![subscribeBookPending, subscribeBookSuccess].includes(jsonMessage[1])) return;
if (jsonMessage[0] === 'EVENT') { if (jsonMessage[0] === 'EVENT') {
const event: Event = jsonMessage[2]; const event: Event = jsonMessage[2];
const network = event.tags.find((e) => e[0] === 'network'); const network = event.tags.find((e) => e[0] === 'network');
@ -135,14 +141,18 @@ class RoboPool {
.map((f) => f.nostrHexPubkey) .map((f) => f.nostrHexPubkey)
.filter((item) => item !== undefined); .filter((item) => item !== undefined);
const subscribeRatings = `subscribeRatings${id ?? ''}`;
const requestRatings = [ const requestRatings = [
'REQ', 'REQ',
`subscribeRatings${id ?? ''}`, subscribeRatings,
{ kinds: [31986], '#p': pubkeys ?? defaultPubkeys, since: 1746316800 }, { kinds: [31986], '#p': pubkeys ?? defaultPubkeys, since: 1746316800 },
]; ];
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => { this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
const jsonMessage = JSON.parse(messageEvent.data); const jsonMessage = JSON.parse(messageEvent.data);
if (subscribeRatings !== jsonMessage[1]) return;
if (jsonMessage[0] === 'EVENT') { if (jsonMessage[0] === 'EVENT') {
events.onevent(jsonMessage[2]); events.onevent(jsonMessage[2]);
} else if (jsonMessage[0] === 'EOSE') { } else if (jsonMessage[0] === 'EOSE') {
@ -152,18 +162,38 @@ class RoboPool {
this.sendMessage(JSON.stringify(requestRatings)); this.sendMessage(JSON.stringify(requestRatings));
}; };
subscribeChat = (hexPubKeys: string[], since: number, events: RoboPoolEvents): void => { subscribeNotifications = (garage: Garage, events: RoboPoolEvents): void => {
const requestRatings = ['REQ', 'subscribeChat', { kinds: [1059], '#p': hexPubKeys, since }]; const hexPubKeys = Object.values(garage.slots).map((s) => s.nostrPubKey);
if (hexPubKeys.length === 0) return;
const subscribeChat = 'subscribeChat';
const requestNotifications = ['REQ', subscribeChat, { kinds: [1059], '#p': hexPubKeys }];
this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => { this.messageHandlers.push((_url: string, messageEvent: MessageEvent) => {
const jsonMessage = JSON.parse(messageEvent.data); const jsonMessage = JSON.parse(messageEvent.data);
if (subscribeChat !== jsonMessage[1]) return;
if (jsonMessage[0] === 'EVENT') { if (jsonMessage[0] === 'EVENT') {
events.onevent(jsonMessage[2]); const wrappedEvent: Event = jsonMessage[2];
console.log('wrappedEvent', wrappedEvent);
const hexPubKey = wrappedEvent.tags.find((t) => t[0] == 'p')?.[1];
const slot = Object.values(garage.slots).find((s) => s.nostrPubKey == hexPubKey);
if (slot?.nostrSecKey) {
const unwrappedEvent = nip17.unwrapEvent(wrappedEvent, slot.nostrSecKey);
events.onevent(unwrappedEvent as Event);
}
} else if (jsonMessage[0] === 'EOSE') { } else if (jsonMessage[0] === 'EOSE') {
events.oneose(); events.oneose();
} }
}); });
this.sendMessage(JSON.stringify(requestRatings));
this.sendMessage(JSON.stringify(requestNotifications));
}; };
sendEvent = (event: Event): void => { sendEvent = (event: Event): void => {