mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
626a5dfe16 | ||
|
|
e63f4816c4 | ||
|
|
13852b194b | ||
|
|
a68c20098b | ||
|
|
432b5a767e | ||
|
|
952659198c | ||
|
|
4e518758e5 | ||
|
|
e1b3dd311f | ||
|
|
bb0f923155 | ||
|
|
ab86f02bd7 | ||
|
|
43067cfb07 | ||
|
|
3300694059 | ||
|
|
f59b8715dd | ||
|
|
60abadd1fc | ||
|
|
4297c91c5e | ||
|
|
c8eddc3787 | ||
|
|
d01d81a6d7 | ||
|
|
40b31fd8af | ||
|
|
7b995b35cd | ||
|
|
00885d57c9 | ||
|
|
d03d7dbc47 | ||
|
|
7fd4074bd3 | ||
|
|
8367bca4d5 | ||
|
|
5059990adb | ||
|
|
9dd9d39df4 | ||
|
|
87f89fea6d |
@@ -314,7 +314,6 @@ sudo apt-get install fonts-noto-color-emoji
|
||||
| `videopoker` | Plays basic 5-card hold Video Poker | ✅ |
|
||||
| `mastermind` | Plays the classic code-breaking game | ✅ |
|
||||
| `golfsim` | Plays a 9-hole Golf Simulator | ✅ |
|
||||
| `uno` | Plays Uno card game against the bot or with others on the mesh near you! | ✅ |
|
||||
|
||||
# Recognition
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ blackjack = True
|
||||
videopoker = True
|
||||
mastermind = True
|
||||
golfsim = True
|
||||
uno = True
|
||||
|
||||
[sentry]
|
||||
# detect anyone close to the bot
|
||||
|
||||
@@ -25,7 +25,7 @@ def get_name_from_number(nodeID, length='short', interface=1):
|
||||
# # Function to handle, or the project in test
|
||||
|
||||
|
||||
def example_handler(nodeID, message):
|
||||
def example_handler(message, nodeID, deviceID):
|
||||
readableTime = time.ctime(time.time())
|
||||
msg = "Hello World! "
|
||||
msg += f" You are Node ID: {nodeID} "
|
||||
|
||||
41
mesh_bot.py
41
mesh_bot.py
@@ -10,7 +10,7 @@ from modules.log import *
|
||||
from modules.system import *
|
||||
|
||||
# list of commands to remove from the default list for DM only
|
||||
restrictedCommands = ["blackjack", "videopoker", "dopewars", "lemonstand", "golfsim", "mastermind", "uno"]
|
||||
restrictedCommands = ["blackjack", "videopoker", "dopewars", "lemonstand", "golfsim", "mastermind"]
|
||||
restrictedResponse = "🤖only available in a Direct Message📵" # "" for none
|
||||
|
||||
# Global Variables
|
||||
@@ -28,9 +28,11 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"ack": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM),
|
||||
"ask:": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel),
|
||||
"askai": lambda: handle_llm(message_from_id, channel_number, deviceID, message, publicChannel),
|
||||
"bbslink": lambda: bbs_sync_posts(message, message_from_id, deviceID),
|
||||
"bbsdelete": lambda: handle_bbsdelete(message, message_from_id),
|
||||
"bbshelp": bbs_help,
|
||||
"bbsinfo": lambda: get_bbs_stats(),
|
||||
"bbslink": lambda: bbs_sync_posts(message, message_from_id, deviceID),
|
||||
"bbslist": bbs_list_messages,
|
||||
"bbspost": lambda: handle_bbspost(message, message_from_id, deviceID),
|
||||
"bbsread": lambda: handle_bbsread(message),
|
||||
@@ -54,7 +56,6 @@ def auto_response(message, snr, rssi, hop, pkiStatus, message_from_id, channel_n
|
||||
"motd": lambda: handle_motd(message, message_from_id, isDM),
|
||||
"ping": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM),
|
||||
"pinging": lambda: handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM),
|
||||
"playuno": lambda: handleUno(message, message_from_id, deviceID),
|
||||
"pong": lambda: "🏓PING!!🛜",
|
||||
"rlist": lambda: handle_repeaterQuery(message_from_id, deviceID, channel_number),
|
||||
"sitrep": lambda: handle_lheard(message, message_from_id, deviceID, isDM),
|
||||
@@ -160,6 +161,8 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM):
|
||||
msg = "🛑 auto-ping"
|
||||
try:
|
||||
pingCount = int(message.split(" ")[1])
|
||||
if pingCount == 123 or pingCount == 1234:
|
||||
pingCount = 1
|
||||
if pingCount > 51:
|
||||
pingCount = 50
|
||||
except:
|
||||
@@ -213,8 +216,9 @@ def handle_wxalert(message_from_id, deviceID, message):
|
||||
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]), str(location[1]))
|
||||
else:
|
||||
weatherAlert = getWeatherAlerts(str(location[0]), str(location[1]))
|
||||
|
||||
weatherAlert = weatherAlert[0]
|
||||
|
||||
if NO_ALERTS not in weatherAlert:
|
||||
weatherAlert = weatherAlert[0]
|
||||
return weatherAlert
|
||||
|
||||
def handle_wiki(message, isDM):
|
||||
@@ -545,29 +549,6 @@ def handleGolf(message, nodeID, deviceID):
|
||||
time.sleep(responseDelay + 1)
|
||||
return msg
|
||||
|
||||
def handleUno(message, nodeID, deviceID):
|
||||
global unoTracker
|
||||
msg = ''
|
||||
|
||||
# get player's last command from tracker if not new player
|
||||
last_cmd = ""
|
||||
for i in range(len(unoTracker)):
|
||||
if unoTracker[i]['nodeID'] == nodeID:
|
||||
last_cmd = unoTracker[i]['cmd']
|
||||
|
||||
logger.debug(f"System: {nodeID} PlayingGame uno last_cmd: {last_cmd}")
|
||||
|
||||
if last_cmd == "" and nodeID != 0:
|
||||
# create new player
|
||||
logger.debug("System: Uno: New Player: " + str(nodeID) + " " + get_name_from_number(nodeID))
|
||||
unoTracker.append({'nodeID': nodeID, 'last_played': time.time(), 'cmd': '', 'playerName': get_name_from_number(nodeID)})
|
||||
msg = "Welcome to 🃏 Uno!, waiting for others to join, (S)tart when ready"
|
||||
|
||||
msg += playUno(nodeID, message=message)
|
||||
# wait a second to keep from message collision
|
||||
time.sleep(responseDelay + 1)
|
||||
return msg
|
||||
|
||||
def handle_wxc(message_from_id, deviceID, cmd):
|
||||
location = get_node_location(message_from_id, deviceID)
|
||||
if use_meteo_wxApi and not "wxc" in cmd and not use_metric:
|
||||
@@ -798,7 +779,6 @@ def checkPlayingGame(message_from_id, message_string, rxNode, channel_number):
|
||||
(jackTracker, "BlackJack", handleBlackJack),
|
||||
(mindTracker, "MasterMind", handleMmind),
|
||||
(golfTracker, "GolfSim", handleGolf),
|
||||
(unoTracker, "Uno", handleUno)
|
||||
]
|
||||
|
||||
for tracker, game_name, handle_game_func in trackers:
|
||||
@@ -919,7 +899,7 @@ def onReceive(packet, interface):
|
||||
# message is DM to us
|
||||
isDM = True
|
||||
# check if the message contains a trap word, DMs are always responded to
|
||||
if messageTrap(message_string):
|
||||
if (messageTrap(message_string) and not llm_enabled) or messageTrap(message_string.split()[0]):
|
||||
# log the message to the message log
|
||||
logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\
|
||||
"From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
@@ -1081,6 +1061,9 @@ async def start_rx():
|
||||
|
||||
# Send the MOTD every day at 13:00 using send_message function to channel 2 on device 1
|
||||
#schedule.every().day.at("13:00").do(lambda: send_message(MOTD, 2, 0, 1))
|
||||
|
||||
# Send bbslink looking for peers every other day at 10:00 using send_message function to channel 0 on device 1
|
||||
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", 0, 0, 1))
|
||||
|
||||
#
|
||||
logger.debug("System: Starting the broadcast scheduler")
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import pickle # pip install pickle
|
||||
from modules.log import *
|
||||
|
||||
trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp", "bbsinfo")
|
||||
trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp", "bbsinfo", "bbslink", "bbsack")
|
||||
|
||||
# global message list, later we will use a pickle on disk
|
||||
bbs_messages = []
|
||||
@@ -77,6 +77,12 @@ def bbs_post_message(subject, message, fromNode):
|
||||
if str(fromNode) in bbs_ban_list:
|
||||
logger.warning(f"System: Naughty node {fromNode}, tried to post a message: {subject}, {message} and was dropped.")
|
||||
return "Message posted. ID is: " + str(messageID)
|
||||
|
||||
# validate not a duplicate message
|
||||
for msg in bbs_messages:
|
||||
if msg[1].strip().lower() == subject.strip().lower() and msg[2].strip().lower() == message.strip().lower():
|
||||
messageID = msg[0]
|
||||
return "Message posted. ID is: " + str(messageID)
|
||||
|
||||
# append the message to the list
|
||||
bbs_messages.append([messageID, subject, message, fromNode])
|
||||
@@ -156,6 +162,29 @@ def bbs_delete_dm(toNode, message):
|
||||
return "System: cleared mail for" + str(toNode)
|
||||
return "System: No DM found for node " + str(toNode)
|
||||
|
||||
def bbs_sync_posts(input, peerNode, RxNode):
|
||||
messageID = 0
|
||||
# respond when another bot asks for the bbs posts to sync
|
||||
if "bbslink" in input.lower():
|
||||
if "$" in input and "#" in input:
|
||||
#store the message
|
||||
subject = input.split("$")[1].split("#")[0]
|
||||
body = input.split("#")[1]
|
||||
bbs_post_message(subject, body, peerNode)
|
||||
messageID = input.split(" ")[1]
|
||||
return f"bbsack {messageID}"
|
||||
elif "bbsack" in input.lower():
|
||||
# increment the messageID
|
||||
ack = int(input.split(" ")[1])
|
||||
messageID = int(ack) + 1
|
||||
|
||||
# send message
|
||||
if messageID < len(bbs_messages):
|
||||
return f"bbslink {messageID} ${bbs_messages[messageID][1]} #{bbs_messages[messageID][2]}"
|
||||
else:
|
||||
logger.debug("System: bbslink sync complete with peer " + str(peerNode))
|
||||
|
||||
|
||||
#initialize the bbsdb's
|
||||
load_bbsdb()
|
||||
load_bbsdm()
|
||||
|
||||
@@ -159,7 +159,7 @@ def get_found_items(nodeID):
|
||||
if dwInventoryDb[i].get('userID') == nodeID:
|
||||
dwInventoryDb[i]['inventory'] = inventory
|
||||
dwInventoryDb[i]['amount'] = amount
|
||||
msg = "💊You found " + str(qty) + " of " + my_drugs[found]
|
||||
msg = "💊You found " + str(qty) + " of " + str(my_drugs[found])
|
||||
else:
|
||||
# rolls to see how much cash the user finds
|
||||
cash_found = random.randint(1, 977)
|
||||
@@ -232,8 +232,9 @@ def buy_func(nodeID, price_list, choice=0, value='0'):
|
||||
else:
|
||||
if drug_choice in range(1, len(my_drugs) + 1):
|
||||
drug_choice = drug_choice - 1
|
||||
cost = price_list[drug_choice]
|
||||
msg = my_drugs[drug_choice].name + ": you have🎒 " + str(amount[drug_choice]) + " "
|
||||
msg += " The going price is: $" + "{:,}".format(price_list[drug_choice]) + " "
|
||||
msg += " The going price is: $" + "{:,}".format(cost) + " "
|
||||
|
||||
buy_amount = value
|
||||
if buy_amount == 'm':
|
||||
@@ -315,15 +316,17 @@ def sell_func(nodeID, price_list, choice=0, value='0'):
|
||||
else:
|
||||
if drug_choice in range(1, len(my_drugs) + 1) and amount[drug_choice - 1] > 0:
|
||||
drug_choice = drug_choice - 1
|
||||
cost = price_list[drug_choice]
|
||||
msg = my_drugs[drug_choice].name + ": you have " + str(amount[drug_choice]) +\
|
||||
" The going price is: $" + str(price_list[drug_choice])
|
||||
" The going price is: $" + str("{:,}".format(cost))
|
||||
# check if the user has enough of the drug to sell
|
||||
if sell_amount <= amount[drug_choice]:
|
||||
amount[drug_choice] -= sell_amount
|
||||
cash += sell_amount * price_list[drug_choice]
|
||||
inventory -= sell_amount
|
||||
msg += " You sold " + str(sell_amount) + " " + my_drugs[drug_choice].name + ' for $' +\
|
||||
str(sell_amount * price_list[drug_choice]) + '. Total cash: $' + "{:,}".format(cash)
|
||||
profit = sell_amount * price_list[drug_choice]
|
||||
msg += " You sold " + str(sell_amount) + " " + my_drugs[drug_choice].name +\
|
||||
' for $' + "{:,}".format(profit) + '. Total cash: $' + "{:,}".format(cash)
|
||||
else:
|
||||
msg = "You don't have that much"
|
||||
return msg
|
||||
@@ -392,7 +395,7 @@ def endGameDw(nodeID):
|
||||
dwHighScore = ({'userID': nodeID, 'cash': round(cash, 2)})
|
||||
with open('data/dopewar_hs.pkl', 'wb') as file:
|
||||
pickle.dump(dwHighScore, file)
|
||||
msg = "You finished with $" + str(cash) + " and beat the high score!🎉💰"
|
||||
msg = "You finished with $" + "{:,}".format(cash) + " and beat the high score!🎉💰"
|
||||
return msg
|
||||
if cash > starting_cash:
|
||||
msg = 'You made money! 💵 Up ' + str((cash/starting_cash).__round__()) + 'x! Well done.'
|
||||
@@ -601,9 +604,9 @@ def playDopeWars(nodeID, cmd):
|
||||
sell = sell_func(nodeID, price_list, i, 'm')
|
||||
# ignore starts with "You don't have any"
|
||||
if not sell.startswith("You don't have any"):
|
||||
msg += sell
|
||||
if i != len(my_drugs):
|
||||
msg += '\n'
|
||||
msg += sell + '\n'
|
||||
# trim the last newline
|
||||
msg = msg[:-1]
|
||||
return msg
|
||||
elif 'f' in menu_choice:
|
||||
# set last command to location
|
||||
@@ -614,7 +617,7 @@ def playDopeWars(nodeID, cmd):
|
||||
|
||||
elif 'p' in menu_choice:
|
||||
# render_game_screen
|
||||
msg = render_game_screen(nodeID, game_day, total_days, loc_choice, -1, price_list, 0)
|
||||
msg = render_game_screen(nodeID, game_day, total_days, loc_choice, -1, price_list, 0, 'nothing')
|
||||
return msg
|
||||
elif 'e' in menu_choice:
|
||||
msg = endGameDw(nodeID)
|
||||
|
||||
@@ -78,14 +78,14 @@ def tableOfContents():
|
||||
'whale': '🐋', 'dolphin': '🐬', 'fish': '🐟', 'blowfish': '🐡', 'shark': '🦈', 'octopus': '🐙', 'shell': '🐚', 'crab': '🦀', 'lobster': '🦞', 'shrimp': '🦐', 'squid': '🦑', 'snail': '🐌', 'butterfly': '🦋',
|
||||
'bee': '🐝', 'beetle': '🐞', 'ant': '🐜', 'cricket': '🦗', 'spider': '🕷️', 'scorpion': '🦂', 'mosquito': '🦟', 'microbe': '🦠', 'locomotive': '🚂', 'arm': '💪', 'leg': '🦵', 'sponge': '🧽',
|
||||
'toothbrush': '🪥', 'broom': '🧹', 'basket': '🧺', 'roll of paper': '🧻', 'bucket': '🪣', 'soap': '🧼', 'toilet paper': '🧻', 'shower': '🚿', 'bathtub': '🛁', 'razor': '🪒', 'lotion': '🧴',
|
||||
'letter': '✉️', 'envelope': '✉️', 'mail': '📬', 'post': '📮', 'golf': '⛳️', 'golfing': '⛳️', 'office': '🏢', 'work': '💼', 'meeting': '📅', 'presentation': '📊', 'report': '📄', 'document': '📄',
|
||||
'letter': '✉️', 'envelope': '✉️', 'mail': '📬', 'post': '📮', 'golf': '⛳️', 'golfing': '⛳️', 'office': '🏢', 'meeting': '📅', 'presentation': '📊', 'report': '📄', 'document': '📄',
|
||||
'file': '📁', 'folder': '📂', 'sports': '🏅', 'athlete': '🏃', 'competition': '🏆', 'race': '🏁', 'tournament': '🏆', 'champion': '🏆', 'medal': '🏅', 'victory': '🏆', 'win': '🏆', 'lose': '😞',
|
||||
'draw': '🤝', 'team': '👥', 'player': '👤', 'coach': '👨🏫', 'referee': '🧑⚖️', 'stadium': '🏟️', 'arena': '🏟️', 'field': '🏟️', 'court': '🏟️', 'track': '🏟️', 'gym': '🏋️', 'fitness': '🏋️', 'exercise': '🏋️',
|
||||
'workout': '🏋️', 'training': '🏋️', 'practice': '🏋️', 'game': '🎮', 'match': '🎮', 'score': '🏅', 'goal': '🥅', 'point': '🏅', 'basket': '🏀', 'home run': '⚾️', 'strike': '🎳', 'spare': '🎳', 'frame': '🎳',
|
||||
'inning': '⚾️', 'quarter': '🏈', 'half': '🏈', 'overtime': '🏈', 'penalty': '⚽️', 'foul': '⚽️', 'timeout': '⏱️', 'substitute': '🔄', 'bench': '🪑', 'sideline': '🏟️', 'dugout': '⚾️', 'locker room': '🚪', 'shower': '🚿',
|
||||
'uniform': '👕', 'jersey': '👕', 'cleats': '👟', 'helmet': '⛑️', 'pads': '🛡️', 'gloves': '🧤', 'bat': '⚾️', 'ball': '⚽️', 'puck': '🏒', 'stick': '🏒', 'net': '🥅', 'hoop': '🏀', 'goalpost': '🥅', 'whistle': '🔔',
|
||||
'scoreboard': '📊', 'fans': '👥', 'crowd': '👥', 'cheer': '📣', 'boo': '😠', 'applause': '👏', 'celebration': '🎉', 'parade': '🎉', 'trophy': '🏆', 'medal': '🏅', 'ribbon': '🎀', 'cup': '🏆', 'championship': '🏆',
|
||||
'league': '🏆', 'season': '🏆', 'playoffs': '🏆', 'finals': '🏆', 'champion': '🏆', 'runner-up': '🥈', 'third place': '🥉'
|
||||
'league': '🏆', 'season': '🏆', 'playoffs': '🏆', 'finals': '🏆', 'champion': '🏆', 'runner-up': '🥈', 'third place': '🥉', 'snowman': '☃️', 'snowmen': '⛄️'
|
||||
}
|
||||
|
||||
return wordToEmojiMap
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
# https://github.com/melvin-02/UNO-game
|
||||
# Adapted for Meshtastic mesh-bot by K7MHI Kelly Keeton 2024
|
||||
|
||||
import random
|
||||
import time
|
||||
from modules.log import *
|
||||
|
||||
color = ('RED', 'GREEN', 'BLUE', 'YELLOW')
|
||||
rank = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'Skip', 'Reverse', 'Draw2', 'Draw4', 'Wild')
|
||||
ctype = {'0': 'number', '1': 'number', '2': 'number', '3': 'number', '4': 'number', '5': 'number', '6': 'number',
|
||||
'7': 'number', '8': 'number', '9': 'number', 'Skip': 'action', 'Reverse': 'action', 'Draw2': 'action',
|
||||
'Draw4': 'action_nocolor', 'Wild': 'action_nocolor'}
|
||||
|
||||
# Player List
|
||||
unoLobby = []
|
||||
unoTracker = [{'nodeID': 0, 'last_played': time.time(), 'cmd': '', 'playerName': ''}]
|
||||
unoGameTable = {'turn': -1, 'direction': 1, 'deck': None, 'hands': None, 'top_card': None}
|
||||
|
||||
class Card:
|
||||
def __init__(self, color, rank):
|
||||
self.rank = rank
|
||||
if ctype[rank] == 'number':
|
||||
self.color = color
|
||||
self.cardtype = 'number'
|
||||
elif ctype[rank] == 'action':
|
||||
self.color = color
|
||||
self.cardtype = 'action'
|
||||
else:
|
||||
self.color = None
|
||||
self.cardtype = 'action_nocolor'
|
||||
|
||||
def __str__(self):
|
||||
if self.color is None:
|
||||
return self.rank
|
||||
else:
|
||||
return self.color + " " + self.rank
|
||||
|
||||
class Deck:
|
||||
def __init__(self):
|
||||
self.deck = []
|
||||
self.discard_pile = []
|
||||
for clr in color:
|
||||
for ran in rank:
|
||||
if ctype[ran] != 'action_nocolor':
|
||||
self.deck.append(Card(clr, ran))
|
||||
self.deck.append(Card(clr, ran))
|
||||
else:
|
||||
self.deck.append(Card(clr, ran))
|
||||
|
||||
def __str__(self):
|
||||
deck_comp = ''
|
||||
for card in self.deck:
|
||||
deck_comp += '\n' + card.__str__()
|
||||
return 'The deck has ' + deck_comp
|
||||
|
||||
def shuffle(self):
|
||||
random.shuffle(self.deck)
|
||||
|
||||
def deal(self):
|
||||
if not self.deck:
|
||||
self.reshuffle_discard_pile()
|
||||
return self.deck.pop()
|
||||
|
||||
def reshuffle_discard_pile(self):
|
||||
if len(self.discard_pile) > 1:
|
||||
top_card = self.discard_pile.pop()
|
||||
self.deck = self.discard_pile[:]
|
||||
self.discard_pile = [top_card]
|
||||
self.shuffle()
|
||||
else:
|
||||
raise IndexError("No cards left to reshuffle")
|
||||
|
||||
class Hand:
|
||||
def __init__(self):
|
||||
self.cards = []
|
||||
self.cardsstr = []
|
||||
self.number_cards = 0
|
||||
self.action_cards = 0
|
||||
|
||||
def add_card(self, card):
|
||||
self.cards.append(card)
|
||||
self.cardsstr.append(str(card))
|
||||
if card.cardtype == 'number':
|
||||
self.number_cards += 1
|
||||
else:
|
||||
self.action_cards += 1
|
||||
self.sort_cards()
|
||||
|
||||
def remove_card(self, place):
|
||||
self.cardsstr.pop(place - 1)
|
||||
return self.cards.pop(place - 1)
|
||||
|
||||
def cards_in_hand(self):
|
||||
msg = ''
|
||||
for i in range(len(self.cardsstr)):
|
||||
msg += f' {i + 1}.{self.cardsstr[i]}'
|
||||
return msg
|
||||
|
||||
def single_card(self, place):
|
||||
return self.cards[place - 1]
|
||||
|
||||
def no_of_cards(self):
|
||||
return len(self.cards)
|
||||
|
||||
def sort_cards(self):
|
||||
self.cards.sort(key=lambda card: (
|
||||
card.color if card.color is not None else '',
|
||||
int(card.rank) if card.cardtype == 'number' and card.rank is not None else 0))
|
||||
self.cardsstr = [str(card) for card in self.cards]
|
||||
|
||||
def choose_first():
|
||||
global unoLobby
|
||||
if unoLobby != []:
|
||||
random_player = random.choice(unoLobby)
|
||||
return random_player
|
||||
else:
|
||||
return None
|
||||
|
||||
def single_card_check(top_card, card):
|
||||
if card.color == top_card.color or top_card.rank == card.rank or card.cardtype == 'action_nocolor':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def full_hand_check(hand, top_card):
|
||||
for c in hand.cards:
|
||||
if c.color == top_card.color or c.rank == top_card.rank or c.cardtype == 'action_nocolor':
|
||||
#return hand.remove_card(hand.cardsstr.index(str(c)) + 1)
|
||||
return hand.remove_card(hand.cards.index(c) + 1)
|
||||
else:
|
||||
return 'no card'
|
||||
|
||||
def win_check(hand):
|
||||
if len(hand.cards) == 0:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def last_card_check(hand):
|
||||
for c in hand.cards:
|
||||
if c.cardtype != 'number':
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def getNextPlayer(playerIndex, direction=1, skip=False):
|
||||
current_index = unoLobby.index(playerIndex)
|
||||
next_index = (current_index + direction) % len(unoLobby)
|
||||
if skip:
|
||||
next_index = (next_index + direction) % len(unoLobby)
|
||||
return unoLobby[next_index]
|
||||
|
||||
def getNextPlayerID(playerIndex, direction=1, skip=False):
|
||||
current_index = unoLobby.index(playerIndex)
|
||||
next_index = (current_index + direction) % len(unoLobby)
|
||||
if skip:
|
||||
next_index = (next_index + direction) % len(unoLobby)
|
||||
return unoTracker[next_index]['nodeID']
|
||||
|
||||
def unoPlayerDetail(nodeID):
|
||||
for i in range(len(unoTracker)):
|
||||
if unoTracker[i] == nodeID:
|
||||
return f'{unoTracker[i]}'
|
||||
|
||||
def getUnoPname(nodeID):
|
||||
global unoTracker
|
||||
for i in range(len(unoTracker)):
|
||||
if unoTracker[i]['nodeID'] == nodeID:
|
||||
return unoTracker[i]['playerName']
|
||||
|
||||
def setLastCmd(nodeID, cmd):
|
||||
global unoTracker
|
||||
for i in range(len(unoTracker)):
|
||||
if unoTracker[i]['nodeID'] == nodeID:
|
||||
unoTracker[i]['cmd'] = cmd
|
||||
|
||||
def getLastCmd(nodeID):
|
||||
global unoTracker
|
||||
for i in range(len(unoTracker)):
|
||||
if unoTracker[i]['nodeID'] == nodeID:
|
||||
return unoTracker[i]['cmd']
|
||||
|
||||
def getUnoIDs():
|
||||
global unoTracker, unoLobby
|
||||
userIDlist = []
|
||||
for i in range(len(unoLobby)):
|
||||
for j in range(len(unoTracker)):
|
||||
if unoTracker[j]['playerName'] == unoLobby[i]:
|
||||
unoTracker[j]['last_played'] = time.time()
|
||||
userIDlist.append(unoTracker[j]['nodeID'])
|
||||
return (userIDlist)
|
||||
|
||||
def playUno(nodeID, message):
|
||||
global unoTracker, unoGameTable, unoLobby
|
||||
playing = False
|
||||
nextPlayerNodeID = 0
|
||||
msg = 'Not implemented yet'
|
||||
|
||||
|
||||
return msg
|
||||
@@ -165,7 +165,7 @@ class PlayerVP:
|
||||
except Exception as e:
|
||||
pass
|
||||
|
||||
return "Re-Draw/Deal ex:1,3,4 to hold cards 1,3 and 4, or (N)o to keep current (H)and"
|
||||
return "ex:1,3,4 deals them new, and keeps 2,5 or (N)o to keep current (H)and"
|
||||
|
||||
# Method for scoring hand, calculating winnings, and outputting message
|
||||
def score_hand(self, resetHand = True):
|
||||
|
||||
101
modules/llm.py
101
modules/llm.py
@@ -7,20 +7,27 @@ from modules.log import *
|
||||
# Ollama Client
|
||||
# https://github.com/ollama/ollama/blob/main/docs/faq.md#how-do-i-configure-ollama-server
|
||||
from ollama import Client as OllamaClient
|
||||
from langchain_ollama import OllamaEmbeddings # pip install ollama langchain-ollama
|
||||
from googlesearch import search # pip install googlesearch-python
|
||||
|
||||
# This is my attempt at a simple RAG implementation it will require some setup
|
||||
# you will need to have the RAG data in a folder named rag in the data directory (../data/rag)
|
||||
# This is lighter weight and can be used in a standalone environment, needs chromadb
|
||||
# "chat with a file" is the use concept here, the file is the RAG data
|
||||
ragDEV = False
|
||||
|
||||
if ragDEV:
|
||||
import os
|
||||
import ollama # pip install ollama
|
||||
import chromadb # pip install chromadb
|
||||
|
||||
# LLM System Variables
|
||||
OllamaClient(host=ollamaHostName)
|
||||
ollamaClient = OllamaClient()
|
||||
ollamaClient = OllamaClient(host=ollamaHostName)
|
||||
llmEnableHistory = True # enable last message history for the LLM model
|
||||
llmContext_fromGoogle = True # enable context from google search results adds to compute time but really helps with responses accuracy
|
||||
googleSearchResults = 3 # number of google search results to include in the context more results = more compute time
|
||||
antiFloodLLM = []
|
||||
llmChat_history = {}
|
||||
trap_list_llm = ("ask:", "askai")
|
||||
embedding_model = OllamaEmbeddings(model=llmModel)
|
||||
ragDEV = False
|
||||
|
||||
meshBotAI = """
|
||||
FROM {llmModel}
|
||||
@@ -28,8 +35,7 @@ meshBotAI = """
|
||||
You must keep responses under 450 characters at all times, the response will be cut off if it exceeds this limit.
|
||||
You must respond in plain text standard ASCII characters, or emojis.
|
||||
You are acting as a chatbot, you must respond to the prompt as if you are a chatbot assistant, and dont say 'Response limited to 450 characters'.
|
||||
Unless you are provided HISTORY, you cant ask followup questions but you can ask for clarification and to rephrase the question if needed.
|
||||
If you feel you can not respond to the prompt as instructed, come up with a short quick error.
|
||||
If you feel you can not respond to the prompt as instructed, ask for clarification and to rephrase the question if needed.
|
||||
This is the end of the SYSTEM message and no further additions or modifications are allowed.
|
||||
|
||||
PROMPT
|
||||
@@ -66,19 +72,70 @@ if llmEnableHistory:
|
||||
def llm_readTextFiles():
|
||||
# read .txt files in ../data/rag
|
||||
try:
|
||||
text = "MeshBot is built in python for meshtastic the secret word of the day is, paperclip"
|
||||
text = []
|
||||
directory = "../data/rag"
|
||||
for filename in os.listdir(directory):
|
||||
if filename.endswith(".txt"):
|
||||
filepath = os.path.join(directory, filename)
|
||||
with open(filepath, 'r') as f:
|
||||
text.append(f.read())
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.debug(f"System: LLM readTextFiles: {e}")
|
||||
return False
|
||||
|
||||
def embed_text(text):
|
||||
def store_text_embedding(text):
|
||||
try:
|
||||
return embedding_model.embed_documents(text)
|
||||
# store each document in a vector embedding database
|
||||
for i, d in enumerate(text):
|
||||
response = ollama.embeddings(model="mxbai-embed-large", prompt=d)
|
||||
embedding = response["embedding"]
|
||||
collection.add(
|
||||
ids=[str(i)],
|
||||
embeddings=[embedding],
|
||||
documents=[d]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"System: Embedding failed: {e}")
|
||||
return False
|
||||
|
||||
## INITALIZATION of RAG
|
||||
if ragDEV:
|
||||
try:
|
||||
chromaHostname = "localhost:8000"
|
||||
# connect to the chromaDB
|
||||
chromaHost = chromaHostname.split(":")[0]
|
||||
chromaPort = chromaHostname.split(":")[1]
|
||||
if chromaHost == "localhost" and chromaPort == "8000":
|
||||
# create a client using local python Client
|
||||
chromaClient = chromadb.Client()
|
||||
else:
|
||||
# create a client using the remote python Client
|
||||
# this isnt tested yet please test and report back
|
||||
chromaClient = chromadb.Client(host=chromaHost, port=chromaPort)
|
||||
|
||||
clearCollection = False
|
||||
if "meshBotAI" in chromaClient.list_collections() and clearCollection:
|
||||
logger.debug(f"System: LLM: Clearing RAG files from chromaDB")
|
||||
chromaClient.delete_collection("meshBotAI")
|
||||
|
||||
# create a new collection
|
||||
collection = chromaClient.create_collection("meshBotAI")
|
||||
|
||||
logger.debug(f"System: LLM: Cataloging RAG data")
|
||||
store_text_embedding(llm_readTextFiles())
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"System: LLM: RAG Initalization failed: {e}")
|
||||
|
||||
def query_collection(prompt):
|
||||
# generate an embedding for the prompt and retrieve the most relevant doc
|
||||
response = ollama.embeddings(prompt=prompt, model="mxbai-embed-large")
|
||||
results = collection.query(query_embeddings=[response["embedding"]], n_results=1)
|
||||
data = results['documents'][0][0]
|
||||
return data
|
||||
|
||||
def llm_query(input, nodeID=0, location_name=None):
|
||||
global antiFloodLLM, llmChat_history
|
||||
googleResults = []
|
||||
@@ -125,24 +182,26 @@ def llm_query(input, nodeID=0, location_name=None):
|
||||
location_name += f" at the current time of {datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z')}"
|
||||
|
||||
try:
|
||||
# Build the query from the template
|
||||
modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history)
|
||||
|
||||
# RAG context inclusion testing
|
||||
ragData = llm_readTextFiles()
|
||||
|
||||
if ragData and ragDEV:
|
||||
ragContext = embed_text(ragData)
|
||||
ragContext = False
|
||||
if ragDEV:
|
||||
ragContext = query_collection(input)
|
||||
|
||||
if ragContext:
|
||||
ragContextGooogle = ragContext + '\n'.join(googleResults)
|
||||
# Build the query from the template
|
||||
modelPrompt = meshBotAI.format(input=input, context=ragContext, location_name=location_name, llmModel=llmModel, history=history)
|
||||
# Query the model with RAG context
|
||||
if ragContext:
|
||||
result = ollamaClient.generate(model=llmModel, prompt=modelPrompt, context=ragContext)
|
||||
result = ollamaClient.generate(model=llmModel, prompt=modelPrompt)
|
||||
else:
|
||||
# Build the query from the template
|
||||
modelPrompt = meshBotAI.format(input=input, context='\n'.join(googleResults), location_name=location_name, llmModel=llmModel, history=history)
|
||||
# Query the model without RAG context
|
||||
result = ollamaClient.generate(model=llmModel, prompt=modelPrompt)
|
||||
|
||||
# Condense the result to just needed
|
||||
result = result.get("response")
|
||||
if isinstance(result, dict):
|
||||
result = result.get("response")
|
||||
|
||||
#logger.debug(f"System: LLM Response: " + result.strip().replace('\n', ' '))
|
||||
except Exception as e:
|
||||
@@ -169,4 +228,4 @@ def llm_query(input, nodeID=0, location_name=None):
|
||||
# logger.debug(f"System: Ollama process with CPU, query time will be slower")
|
||||
# except Exception as e:
|
||||
# logger.debug(f"System: Ollama process not found, {e}")
|
||||
# return False
|
||||
# return False
|
||||
|
||||
@@ -180,40 +180,44 @@ def get_tide(lat=0, lon=0):
|
||||
logger.error("Location:Error fetching tide station table from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
station_url = "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=" + station_id
|
||||
if zuluTime:
|
||||
station_url += "&clock=24hour"
|
||||
station_url = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?date=today&time_zone=lst_ldt&datum=MLLW&product=predictions&interval=hilo&format=json&station=" + station_id
|
||||
|
||||
if use_metric:
|
||||
station_url += "&units=metric"
|
||||
else:
|
||||
station_url += "&units=english"
|
||||
|
||||
try:
|
||||
station_data = requests.get(station_url, timeout=urlTimeoutSeconds)
|
||||
if not station_data.ok:
|
||||
logger.error("Location:Error fetching station data from NOAA")
|
||||
tide_data = requests.get(station_url, timeout=urlTimeoutSeconds)
|
||||
if tide_data.ok:
|
||||
tide_json = tide_data.json()
|
||||
else:
|
||||
logger.error("Location:Error fetching tide data from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException):
|
||||
logger.error("Location:Error fetching station data from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
# extract table class="table table-condensed"
|
||||
soup = bs.BeautifulSoup(station_data.text, 'html.parser')
|
||||
table = soup.find('table', class_='table table-condensed')
|
||||
|
||||
# extract rows
|
||||
rows = table.find_all('tr')
|
||||
# extract data from rows
|
||||
tide_data = []
|
||||
for row in rows:
|
||||
row_text = ""
|
||||
cols = row.find_all('td')
|
||||
for col in cols:
|
||||
row_text += col.text + " "
|
||||
tide_data.append(row_text)
|
||||
# format tide data into a string
|
||||
tide_string = ""
|
||||
for data in tide_data:
|
||||
tide_string += data + "\n"
|
||||
# trim off last newline
|
||||
tide_string = tide_string[:-1]
|
||||
return tide_string
|
||||
except (requests.exceptions.RequestException, json.JSONDecodeError):
|
||||
logger.error("Location:Error fetching tide data from NOAA")
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
tide_data = tide_json['predictions']
|
||||
|
||||
# format tide data into a table string for mesh
|
||||
# get the date out of the first t value
|
||||
tide_date = tide_data[0]['t'].split(" ")[0]
|
||||
tide_table = "Tide Data for " + tide_date + "\n"
|
||||
for tide in tide_data:
|
||||
tide_time = tide['t'].split(" ")[1]
|
||||
if not zuluTime:
|
||||
# convert to 12 hour clock
|
||||
if int(tide_time.split(":")[0]) > 12:
|
||||
tide_time = str(int(tide_time.split(":")[0]) - 12) + ":" + tide_time.split(":")[1] + " PM"
|
||||
else:
|
||||
tide_time = tide_time + " AM"
|
||||
|
||||
tide_table += tide['type'] + " " + tide_time + ", " + tide['v'] + "\n"
|
||||
# remove last newline
|
||||
tide_table = tide_table[:-1]
|
||||
return tide_table
|
||||
|
||||
def get_weather(lat=0, lon=0, unit=0):
|
||||
# get weather report from NOAA for forecast detailed
|
||||
|
||||
@@ -160,7 +160,6 @@ try:
|
||||
videoPoker_enabled = config['games'].getboolean('videoPoker', True)
|
||||
mastermind_enabled = config['games'].getboolean('mastermind', True)
|
||||
golfSim_enabled = config['games'].getboolean('golfSim', True)
|
||||
uno_enabled = config['games'].getboolean('uno', True)
|
||||
|
||||
# messaging settings
|
||||
responseDelay = config['messagingSettings'].getfloat('responseDelay', 0.7) # default 0.7
|
||||
|
||||
@@ -129,11 +129,6 @@ if golfSim_enabled:
|
||||
from modules.games.golfsim import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("golfsim",)
|
||||
games_enabled = True
|
||||
|
||||
if uno_enabled:
|
||||
from modules.games.uno import * # from the spudgunman/meshing-around repo
|
||||
trap_list = trap_list + ("playuno",)
|
||||
games_enabled = True
|
||||
|
||||
# Games Configuration
|
||||
if games_enabled is True:
|
||||
@@ -155,8 +150,6 @@ if games_enabled is True:
|
||||
gamesCmdList += "masterMind, "
|
||||
if golfSim_enabled:
|
||||
gamesCmdList += "golfSim, "
|
||||
if uno_enabled:
|
||||
gamesCmdList += "playuno, "
|
||||
gamesCmdList = gamesCmdList[:-2] # remove the last comma
|
||||
else:
|
||||
gamesCmdList = ""
|
||||
|
||||
Reference in New Issue
Block a user