33import threading
44import time
55import os
6+ import queue
67from flask import Flask , render_template , request , jsonify
78from scanner import scan_network
9+ from pynostr .key import PrivateKey
10+ from pynostr .relay_manager import RelayManager
11+ from pynostr .encrypted_dm import EncryptedDirectMessage
812
913app = Flask (__name__ )
1014
1115DATA_FILE = "miners.json"
1216SETTINGS_FILE = "settings.json"
1317UPDATE_EVENT = threading .Event ()
18+ NOSTR_QUEUE = queue .Queue ()
1419
1520def load_db ():
1621 if os .path .exists (DATA_FILE ):
@@ -38,7 +43,10 @@ def load_settings():
3843 "ntfy_timeout" : 30 ,
3944 "notify_offline" : True ,
4045 "notify_blocks" : True ,
41- "notify_tuning" : True
46+ "notify_tuning" : True ,
47+ "nostr_privkey" : "" ,
48+ "nostr_recipient_pubkey" : "" ,
49+ "nostr_relays" : ["wss://nostr.mom/" , "wss://nostrelites.org/" , "wss://relay.damus.io/" , "wss://wot.nostr.net/" ]
4250 }
4351 if os .path .exists (SETTINGS_FILE ):
4452 try :
@@ -75,6 +83,33 @@ def save_settings(settings):
7583OFFLINE_TRACKER = {}
7684LAST_BLOCK_COUNT = 0
7785
86+ def nostr_queue_worker ():
87+ while True :
88+ try :
89+ item = NOSTR_QUEUE .get ()
90+ if item is None : break
91+ message , priv , pub , relays = item
92+ try :
93+ pk = PrivateKey .from_hex (priv )
94+ dm = EncryptedDirectMessage ()
95+ dm .encrypt (pk .hex (), recipient_pubkey = pub , cleartext_content = message )
96+ event = dm .to_event ()
97+ event .pubkey = pk .public_key .hex ()
98+ event .add_tag ("p" , pub )
99+ event .sign (pk .hex ())
100+ rm = RelayManager (timeout = 6 )
101+ for r in relays : rm .add_relay (r .strip ())
102+ time .sleep (1.5 )
103+ rm .publish_event (event )
104+ rm .run_sync ()
105+ time .sleep (2 )
106+ rm .close_connections ()
107+ except Exception as e : print (f"Nostr Error: { e } " )
108+ NOSTR_QUEUE .task_done ()
109+ except : time .sleep (1 )
110+
111+ threading .Thread (target = nostr_queue_worker , daemon = True ).start ()
112+
78113def send_ntfy (message , title = "OpenAxe Alert" , tags = "cpu" ):
79114 topic = DB ['settings' ].get ('ntfy_topic' , '' ).strip ()
80115 server = DB ['settings' ].get ('ntfy_server' , 'https://ntfy.sh' ).strip ().rstrip ('/' )
@@ -88,6 +123,12 @@ def send_ntfy(message, title="OpenAxe Alert", tags="cpu"):
88123 except :
89124 pass
90125
126+ def send_nostr (message ):
127+ p = DB ['settings' ].get ('nostr_privkey' , '' ).strip ()
128+ r_pub = DB ['settings' ].get ('nostr_recipient_pubkey' , '' ).strip ()
129+ relays = DB ['settings' ].get ('nostr_relays' , [])
130+ if p and r_pub : NOSTR_QUEUE .put ((message , p , r_pub , relays ))
131+
91132def fetch_market_data ():
92133 global MARKET_DATA
93134 while True :
@@ -179,11 +220,15 @@ def gentle_miner_poller():
179220 if threshold <= downtime < threshold + 25 :
180221 if DB ['settings' ].get ('notify_offline' ):
181222 name = m .get ('custom_name' ) or m .get ('name' ) or ip
182- send_ntfy (f"Miner { name } offline for { int (downtime )} s" , "MINER DOWN" , "warning,skull" )
223+ msg = f"Miner { name } offline for { int (downtime )} s"
224+ send_ntfy (msg , "MINER DOWN" , "warning,skull" )
225+ send_nostr (msg )
183226 OFFLINE_TRACKER [ip ] = now + 86400
184227 time .sleep (0.5 )
185228 if DB ['settings' ].get ('notify_blocks' ) and LAST_BLOCK_COUNT > 0 and current_total_blocks > LAST_BLOCK_COUNT :
186- send_ntfy (f"Fleet found a new block! Total: { current_total_blocks } " , "BLOCK FOUND!" , "moneybag,tada" )
229+ msg = f"Fleet found a new block! Total: { current_total_blocks } "
230+ send_ntfy (msg , "BLOCK FOUND!" , "moneybag,tada" )
231+ send_nostr (msg )
187232 LAST_BLOCK_COUNT = current_total_blocks
188233 time .sleep (20 )
189234
@@ -287,7 +332,9 @@ def overclock():
287332 payload = {"frequency" : int (d ['freq' ]), "coreVoltage" : int (d ['volt' ])}
288333 requests .patch (f"http://{ d ['ip' ]} /api/system" , json = payload , timeout = 5 )
289334 if DB ['settings' ].get ('notify_tuning' ):
290- send_ntfy (f"Tuning applied to { d ['ip' ]} : { d ['freq' ]} MHz / { d ['volt' ]} mV" , "TUNING SUCCESS" , "zap" )
335+ msg = f"Tuning applied to { d ['ip' ]} : { d ['freq' ]} MHz / { d ['volt' ]} mV"
336+ send_ntfy (msg , "TUNING SUCCESS" , "zap" )
337+ send_nostr (msg )
291338 return jsonify ({"status" : "ok" })
292339 except :
293340 try :
@@ -324,5 +371,13 @@ def test_ntfy():
324371 except :
325372 return jsonify ({"status" : "error" }), 500
326373
374+ @app .route ('/api/test_nostr' , methods = ['POST' ])
375+ def test_nostr ():
376+ try :
377+ send_nostr ("Testing Nostr notification system. If you see this, it works!" )
378+ return jsonify ({"status" : "ok" })
379+ except Exception as e :
380+ return jsonify ({"status" : "error" , "msg" : str (e )}), 500
381+
327382if __name__ == '__main__' :
328383 app .run (host = '0.0.0.0' , port = 5055 , debug = True )
0 commit comments