├── pyproject.toml ├── LICENSE ├── Bot ├── spam_filter.py ├── main.py ├── responses.py └── commands.py └── README.md /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "python-template" 3 | version = "0.1.0" 4 | description = "" 5 | authors = ["Your Name "] 6 | 7 | [tool.poetry.dependencies] 8 | python = ">=3.10.0,<3.11" 9 | numpy = "^1.22.2" 10 | replit = "^3.2.4" 11 | Flask = "^2.2.0" 12 | urllib3 = "^1.26.12" 13 | schedule = "^1.1.0" 14 | nostr = "^0.0.2" 15 | 16 | [tool.poetry.dev-dependencies] 17 | debugpy = "^1.6.2" 18 | replit-python-lsp-server = {extras = ["yapf", "rope", "pyflakes"], version = "^1.5.9"} 19 | 20 | [build-system] 21 | requires = ["poetry-core>=1.0.0"] 22 | build-backend = "poetry.core.masonry.api" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 xeift.eth 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Bot/spam_filter.py: -------------------------------------------------------------------------------- 1 | '''the main purpose of this function is to filter spam post''' 2 | def is_spam_post(post_content): 3 | spam_keyword = [ 4 | 'Make sure you sub to my channel bras', 5 | 'MemeBook', 6 | 'web3social', 7 | 'web3社交+投研', 8 | '你所不了解的币圈', 9 | '123导航', 10 | 'airdrops', 11 | '黄站', 12 | 'Damus最大', 13 | '波場用戶回饋', 14 | 'damus中文最大电', 15 | 'Sign up to get 6TRX', 16 | '成人短视频', 17 | 'Thanks me later', 18 | '澳门金沙', 19 | '早期好项目', 20 | '全国同城约', 21 | '全球华人社区群', 22 | 'Meet girls nak', 23 | '广告机器人卡', 24 | '全国同城', 25 | '引流', 26 | '支付宝', 27 | '红包', 28 | '推广', 29 | '黑丝', 30 | '友好交流,长期看好', 31 | '互帮互助', 32 | '灰产', 33 | '互联网打工人', 34 | '湾区中国留学生', 35 | 'Click the link below to recei', 36 | '硅谷攻城狮', 37 | '管理 3D 设置', 38 | '加群不迷路', 39 | '群推工具', 40 | 'Resource search robot' 41 | ] 42 | for w in spam_keyword: 43 | if w in post_content: 44 | return True 45 | return False -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nostr-Bot 2 | Simple Nostr bot that auto reply to certain keyword and execute commands. 3 | 4 | ## Introduction 5 | This is a Nostr bot that auto reply to certain keyword and execute commands. 6 | 7 | There are 2 commands in this bot currently, 8 | 9 | `//help` Display help message. 10 | 11 | `//gas` Query Ethereum gas. 12 | 13 | ## Requirements 14 | 15 | [nostr 0.0.2](https://pypi.org/project/nostr/) 16 | 17 | ## Quickstart 18 | 19 | try it on [Replit](https://replit.com/@xeiftc/nostr-bot-demo-public)😎 20 | 21 | ## FAQ 22 | 23 | ### How to add a relay? 24 | 25 | > Add custom relay [here](https://github.com/Xeift/Nostr-Bot/blob/main/Bot/main.py#L32). 26 | 27 | ### Where to add new commands? 28 | 29 | > Add your commands and code in [commands.py](https://github.com/Xeift/Nostr-Bot/blob/main/Bot/commands.py). 30 | 31 | ### Where to add new responses? 32 | 33 | > Add your responses and code in [responses.py](https://github.com/Xeift/Nostr-Bot/blob/main/Bot/commands.py). 34 | 35 | ## Note 36 | 37 | Feel free to create an issue if you have any problem. 38 | 39 | This is a demo bot and may not update frequently. 40 | 41 | ## License 42 | 43 | MIT 44 | -------------------------------------------------------------------------------- /Bot/main.py: -------------------------------------------------------------------------------- 1 | ''' 2 | this script will reply every new post which contain specific keyword or execute command 3 | ''' 4 | import ssl 5 | import json 6 | import time 7 | 8 | from nostr.filter import Filter, Filters 9 | from nostr.event import EventKind 10 | from nostr.relay_manager import RelayManager 11 | from nostr.message_type import ClientMessageType 12 | 13 | from spam_filter import is_spam_post 14 | from commands import execute_cmds 15 | from responses import execute_resp 16 | 17 | filters = Filters([ # enter filter condition 18 | Filter( 19 | since=int(time.time()), 20 | kinds=[EventKind.TEXT_NOTE] 21 | ) 22 | ]) 23 | subscription_id = '' # any string 24 | request = [ 25 | ClientMessageType.REQUEST, 26 | subscription_id 27 | ] 28 | request.extend(filters.to_json_array()) 29 | 30 | relay_manager = RelayManager() 31 | # relay_manager.add_relay('wss://relay.snort.social') # add relay 32 | relay_manager.add_relay('wss://nostr-pub.wellorder.net') # add relay 33 | relay_manager.add_subscription(subscription_id, filters) 34 | relay_manager.open_connections({'cert_reqs': ssl.CERT_NONE}) # NOTE: This disables ssl certificate verification 35 | time.sleep(1.25) # allow the connections to open 36 | 37 | message = json.dumps(request) 38 | relay_manager.publish_message(message) 39 | time.sleep(1) # allow the messages to send 40 | print('bot online') 41 | 42 | while 1: 43 | while relay_manager.message_pool.has_events(): 44 | event_msg = relay_manager.message_pool.get_event() 45 | 46 | if is_spam_post(event_msg.event.content): 47 | print('spam post blocked') 48 | continue 49 | 50 | execute_cmds(event_msg, relay_manager) 51 | execute_resp(event_msg, relay_manager) 52 | 53 | print("\n--------------------") 54 | # print(event_msg.event.public_key) 55 | # print(event_msg.event.id) 56 | # print(f'{time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(event_msg.event.created_at + 8 * 60 * 60))} {event_msg.event.content[:20]}') 57 | print(event_msg.event.content[0:30]) 58 | print("--------------------\n") 59 | 60 | relay_manager.close_connections() 61 | print('finish') -------------------------------------------------------------------------------- /Bot/responses.py: -------------------------------------------------------------------------------- 1 | '''bot will respond to post which contain keyword in NORMAL_RESPONSE_DATA''' 2 | import os 3 | import time 4 | 5 | from nostr.event import Event 6 | from nostr.key import PrivateKey 7 | 8 | NOSTR_PRIVATE_KEY = PrivateKey.from_nsec(os.environ['NOSTR_PRIVATE_KEY']) 9 | NOSTR_PUBLICE_KEY = NOSTR_PRIVATE_KEY.public_key.hex() 10 | AUTHOR_INFO = '\nbot by xeift.eth\nhttps://github.com/Xeift/Nostr-Bot' 11 | NORMAL_RESPONSE_DATA = { 12 | "GM": "Good Morning!\nhttps://www.vobss.com/wp-content/uploads/2021/12/good-morning-anime-meme-vobss-9.jpg", 13 | "Hello": "Hello!\nhttps://media.tenor.com/3g3D1mECft0AAAAC/anime-hi.gif", 14 | "GN": "Good Night! Have a good dream\nhttps://pbs.twimg.com/media/EPMSrKIWsAE3PdD.jpg" 15 | } 16 | 17 | '''----------------------------------------FUNCTIONS----------------------------------------''' 18 | def execute_resp(event_msg, relay_manager): 19 | if event_msg.event.public_key == NOSTR_PUBLICE_KEY or event_msg.event.public_key == 'ee8bf9594fbf8a3ed277b9fb60da8d963f7fd2f889315163761c90e91ca71277': return False # do not reply to messages sent by ourself 20 | for keyword in NORMAL_RESPONSE_DATA: 21 | if keyword in event_msg.event.content: normal_response(event_msg, relay_manager, NORMAL_RESPONSE_DATA[keyword]) 22 | 23 | 24 | def sign_and_publish_event(event, relay_manager): 25 | NOSTR_PRIVATE_KEY.sign_event(event) 26 | relay_manager.publish_event(event) 27 | time.sleep(1)# allow the messages to send 28 | '''----------------------------------------FUNCTIONS----------------------------------------''' 29 | 30 | 31 | 32 | '''----------------------------------------RESPONSES----------------------------------------''' 33 | def normal_response(event_msg, relay_manager, resp): 34 | event = Event( 35 | public_key = NOSTR_PUBLICE_KEY, 36 | content = f'#[1]\n{resp}{AUTHOR_INFO}', # message you want to send in the post 37 | tags = [ 38 | ['e', event_msg.event.id, ''], # the post id you want to reply. 'e' stands for "event", read nip-01 for futher information. 39 | ['p', event_msg.event.public_key, ''] # the user you want to mention. 'p' stands for "pubkey", read nip-08 for futher information. 40 | ] 41 | ) 42 | sign_and_publish_event(event, relay_manager) 43 | '''----------------------------------------RESPONSES----------------------------------------''' 44 | -------------------------------------------------------------------------------- /Bot/commands.py: -------------------------------------------------------------------------------- 1 | ''' 2 | bot will execute the command if post exactly match it 3 | `//help` will return a simple help message, 4 | `//gas` will return ethereum gas. 5 | ''' 6 | import os 7 | import time 8 | import requests 9 | 10 | from nostr.event import Event 11 | from nostr.key import PrivateKey 12 | 13 | NOSTR_PRIVATE_KEY = PrivateKey.from_nsec(os.environ['NOSTR_PRIVATE_KEY']) 14 | NOSTR_PUBLICE_KEY = NOSTR_PRIVATE_KEY.public_key.hex() 15 | ETHERSCAN_API_KEY = os.environ['ETHERSCAN_API_KEY'] 16 | AUTHOR_INFO = '\nbot by xeift.eth\nhttps://github.com/Xeift/Nostr-Bot' 17 | 18 | '''----------------------------------------FUNCTIONS----------------------------------------''' 19 | def execute_cmds(event_msg, relay_manager): 20 | if event_msg.event.public_key == NOSTR_PUBLICE_KEY: return False # do not reply to messages sent by ourself 21 | if event_msg.event.content == '//help': help(event_msg, relay_manager) 22 | elif event_msg.event.content == '//gas': gas(event_msg, relay_manager) 23 | 24 | 25 | def sign_and_publish_event(event, relay_manager): 26 | NOSTR_PRIVATE_KEY.sign_event(event) 27 | relay_manager.publish_event(event) 28 | time.sleep(1)# allow the messages to send 29 | '''----------------------------------------FUNCTIONS----------------------------------------''' 30 | 31 | 32 | 33 | '''----------------------------------------COMMANDS----------------------------------------''' 34 | def help(event_msg, relay_manager): 35 | event = Event( 36 | public_key = NOSTR_PUBLICE_KEY, 37 | content = f'#[1]\nhelp command!{AUTHOR_INFO}', # message you want to send in the post 38 | tags = [ 39 | ['e', event_msg.event.id, ''], # the post id you want to reply. 'e' stands for "event", read nip-01 for futher information. 40 | ['p', event_msg.event.public_key, ''] # the user you want to mention. 'p' stands for "pubkey", read nip-08 for futher information. 41 | ] 42 | ) 43 | sign_and_publish_event(event, relay_manager) 44 | 45 | 46 | def gas(event_msg, relay_manager): 47 | url = f'https://api.etherscan.io/api?module=gastracker&action=gasoracle&apikey={ETHERSCAN_API_KEY}' 48 | r = requests.get(url).json()['result'] 49 | event = Event( 50 | public_key = NOSTR_PUBLICE_KEY, 51 | content = f"#[1]\nEthereum gas:\n🐢{r['SafeGasPrice']} 🚗{r['ProposeGasPrice']} 🚀{r['FastGasPrice']}{AUTHOR_INFO}", # message you want to send in the post 52 | tags = [ 53 | ['e', event_msg.event.id, ''], # the post id you want to reply. 'e' stands for "event", read nip-01 for futher information. 54 | ['p', event_msg.event.public_key, ''] # the user you want to mention. 'p' stands for "pubkey", read nip-08 for futher information. 55 | ] 56 | ) 57 | sign_and_publish_event(event, relay_manager) 58 | '''----------------------------------------COMMANDS----------------------------------------''' --------------------------------------------------------------------------------