You've already forked STARK
mirror of
https://github.com/MarkParker5/STARK.git
synced 2025-07-12 22:50:22 +02:00
Integrate googles and wiki
s search systems, fix bugs
This commit is contained in:
@ -3,6 +3,7 @@
|
|||||||
# command - object (class instance)
|
# command - object (class instance)
|
||||||
# Command.list - list of all commands
|
# Command.list - list of all commands
|
||||||
# Command.find() - recognize command from a string, return command object
|
# Command.find() - recognize command from a string, return command object
|
||||||
|
# must return dict like {'cmd': cmd, 'params': params}
|
||||||
# this - object (class instance) pointer
|
# this - object (class instance) pointer
|
||||||
# abstract this.start() - required method for all commands
|
# abstract this.start() - required method for all commands
|
||||||
# abstract this.confirm() - Return True/False (User responce)
|
# abstract this.confirm() - Return True/False (User responce)
|
||||||
@ -33,11 +34,13 @@ class RThread(Thread):
|
|||||||
return self._return
|
return self._return
|
||||||
|
|
||||||
class Command(ABC):
|
class Command(ABC):
|
||||||
_list = [] # list of all commands
|
_list = [] # list of all commands
|
||||||
|
_specials = []
|
||||||
_patterns = {
|
_patterns = {
|
||||||
'word': '[A-Za-zА-ЯЁа-яё0-9]+',
|
'word': '([A-Za-zА-ЯЁа-яё0-9])+',
|
||||||
|
'quest' : '(кто|что|как|какой|какая|какое|где|зачем|почему|сколько|чей|куда|когда)',
|
||||||
}
|
}
|
||||||
_regex = {
|
_regex = {
|
||||||
# stars *
|
# stars *
|
||||||
'([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b\1.*\2\\b', # 'te*xt'
|
'([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b\1.*\2\\b', # 'te*xt'
|
||||||
'\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b.*\1', # '*text'
|
'\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b.*\1', # '*text'
|
||||||
@ -45,16 +48,17 @@ class Command(ABC):
|
|||||||
'(^|\s)\*($|\s)': r'.*', # '*' ' * '
|
'(^|\s)\*($|\s)': r'.*', # '*' ' * '
|
||||||
# one of the list (a|b|c)
|
# one of the list (a|b|c)
|
||||||
'\(((?:.*\|)*.*)\)': r'(?:\1)',
|
'\(((?:.*\|)*.*)\)': r'(?:\1)',
|
||||||
# 0 or 1 the of list [a|b|c]
|
# 0 or 1 the of list [abc]
|
||||||
'\[((?:.*\|?)*?.*?)\]': r'(?:\1)??',
|
'\[((?:.*\|?)*?.*?)\]': r'(?:\1)??',
|
||||||
# one or more of the list, without order {a|b|c}
|
# one or more of the list, without order {a|b|c}
|
||||||
'\{((?:.*\|?)*?.*?)\}': r'(?:\1)+?',
|
'\{((?:.*\|?)*?.*?)\}': r'(?:\1)+?',
|
||||||
}
|
}
|
||||||
def __init__(this, name, keywords, patterns): # initialisation of new command
|
def __init__(this, name, keywords = {}, patterns = [], special = False): # initialisation of new command
|
||||||
this._name = name
|
this._name = name
|
||||||
this._keywords = keywords
|
this._keywords = keywords
|
||||||
this._patterns = patterns
|
this._patterns = patterns
|
||||||
Command.append(this)
|
if special: Command.addSpecial(this)
|
||||||
|
else: Command.append(this)
|
||||||
|
|
||||||
def __str__(this):
|
def __str__(this):
|
||||||
str = f'{this.__class__.__name__}.{this.getName()}:\n'
|
str = f'{this.__class__.__name__}.{this.getName()}:\n'
|
||||||
@ -104,7 +108,7 @@ class Command(ABC):
|
|||||||
|
|
||||||
# abstract
|
# abstract
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def start(this, string): # main method
|
def start(this, params): # main method
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@ -116,33 +120,66 @@ class Command(ABC):
|
|||||||
def getList():
|
def getList():
|
||||||
return Command._list
|
return Command._list
|
||||||
|
|
||||||
|
def getRegexDict():
|
||||||
|
return Command._regex
|
||||||
|
|
||||||
|
def getPatternsDict():
|
||||||
|
return Command._patterns
|
||||||
|
|
||||||
|
def getSpecialsList():
|
||||||
|
return Command._specials
|
||||||
|
|
||||||
|
def getSpecial(string):
|
||||||
|
return Command._specials.get(string)
|
||||||
|
|
||||||
|
def addSpecial(obj):
|
||||||
|
Command._specials.append(obj)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def append(obj):
|
def append(obj):
|
||||||
Command._list.append(obj)
|
Command._list.append(obj)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def setSearch(obj):
|
||||||
|
Command._search = obj
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def ratio(string, word):
|
def ratio(string, word):
|
||||||
return ( fuzz.WRatio(string, word) + fuzz.ratio(string, word) ) / 2
|
return ( fuzz.WRatio(string, word) + fuzz.ratio(string, word) ) / 2
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def compilePattern(pattern):
|
||||||
|
# transform patterns to regexp
|
||||||
|
for ptrn, regex in Command.getRegexDict().items():
|
||||||
|
pattern = re.sub(re.compile(ptrn), regex, pattern)
|
||||||
|
# find links like $Pattern
|
||||||
|
link = re.search(re.compile('\$[A-Za-zА-ЯЁа-яё0-9]+'), pattern)
|
||||||
|
if link: pattern = re.sub('\\'+link[0], f'(?P<{link[0][1:]}>{Command.compilePattern( Command.getPatternsDict()[ link[0][1:] ] )})', pattern)
|
||||||
|
# return compiled regexp
|
||||||
|
return pattern
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def find(string):
|
def find(string):
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
chances = {}
|
chances = {}
|
||||||
list = Command.getList()
|
list = Command.getList()
|
||||||
|
# calculate chances of every command
|
||||||
for i, obj in enumerate( list ):
|
for i, obj in enumerate( list ):
|
||||||
chances[i] = 0
|
chances[i] = 0
|
||||||
k = 1 / ( sum( [int(w)*len(kw) for w, kw in obj.getKeywords().items()] ) or 1 )
|
k = 1 / ( sum( [int(w)*len(kw) for w, kw in obj.getKeywords().items()] ) or 1 )
|
||||||
for weight, kws in obj.getKeywords().items():
|
for weight, kws in obj.getKeywords().items():
|
||||||
for kw in kws:
|
for kw in kws:
|
||||||
chances[i] += Command.ratio(string, kw) * weight * k
|
chances[i] += Command.ratio(string, kw) * weight * k
|
||||||
|
# find command with the biggest chance
|
||||||
if( sum( chances.values() ) ):
|
if( sum( chances.values() ) ):
|
||||||
top = max( chances.values() ) / sum( chances.values() ) * 100
|
top = max( chances.values() ) / sum( chances.values() ) * 100
|
||||||
else:
|
else: # if all chances is 0
|
||||||
return {
|
return {
|
||||||
'cmd': list[0],
|
'cmd': list[0],
|
||||||
'params': None,
|
'params': {},
|
||||||
}
|
}
|
||||||
#if( max( chances.values() ) < 800 or top < 80): return list[0]
|
#if( max( chances.values() ) < 800 or top < 50): return list[0]
|
||||||
|
# find top command obj
|
||||||
for i, chance in chances.items():
|
for i, chance in chances.items():
|
||||||
if chance == max( chances.values() ):
|
if chance == max( chances.values() ):
|
||||||
return {
|
return {
|
||||||
@ -154,28 +191,34 @@ class Command(ABC):
|
|||||||
def reg_find(string):
|
def reg_find(string):
|
||||||
string = string.lower()
|
string = string.lower()
|
||||||
list = Command.getList()
|
list = Command.getList()
|
||||||
|
if not string: return{
|
||||||
|
'cmd': list[0], #dialog mode
|
||||||
|
'params': {},
|
||||||
|
}
|
||||||
|
# find command obj by pattern
|
||||||
for obj in list:
|
for obj in list:
|
||||||
for pattern in obj.getPatterns():
|
for pattern in obj.getPatterns():
|
||||||
# replace patterns
|
if match := re.search(re.compile(Command.compilePattern(pattern)), string):
|
||||||
for ptrn, regex in Command._regex.items():
|
return {
|
||||||
pattern = re.sub(re.compile(ptrn), regex, pattern)
|
'cmd': obj,
|
||||||
# links $Pattern
|
'params': match.groupdict(),
|
||||||
link = re.search(re.compile('\$[A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+'), pattern)
|
}
|
||||||
if link: pattern = re.sub('\\'+link[0], f'(?P<{link[0][1:]}>{Command._patterns[link[0][1:]]})', pattern)
|
# if command is search query
|
||||||
# find
|
for obj in Command.getSpecialsList():
|
||||||
match = re.search(re.compile(pattern), string)
|
for pattern in obj.getPatterns():
|
||||||
if(match): return {
|
if match := re.search(re.compile(Command.compilePattern(pattern)), string): return {
|
||||||
'cmd': obj,
|
'cmd': obj,
|
||||||
'params': match.groupdict(),
|
'params': {**match.groupdict(), 'string': string},
|
||||||
}
|
}
|
||||||
|
# return dialog bot if command not found
|
||||||
return {
|
return {
|
||||||
'cmd': list[0],
|
'cmd': list[0], #dialog mode
|
||||||
'params': None,
|
'params': {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def background(answer = '', voice = ''):
|
def background(answer = '', voice = ''):
|
||||||
def decorator(cmd):
|
def decorator(cmd): #wrapper of wrapper (decorator of decorator)
|
||||||
def wrapper(text):
|
def wrapper(text):
|
||||||
finish_event = Event()
|
finish_event = Event()
|
||||||
thread = RThread(target=cmd, args=(text, finish_event))
|
thread = RThread(target=cmd, args=(text, finish_event))
|
1
Command/__init__.py
Normal file
1
Command/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
from .Command import *
|
77
Search.py
Normal file
77
Search.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import requests
|
||||||
|
import json
|
||||||
|
from bs4 import BeautifulSoup as BS
|
||||||
|
import wikipedia as wiki
|
||||||
|
from Command import Command
|
||||||
|
import re
|
||||||
|
|
||||||
|
class Search(Command):
|
||||||
|
def confirm(this, string): return True
|
||||||
|
|
||||||
|
def googleDictionary(this, word):
|
||||||
|
responce = requests.get(f'https://api.dictionaryapi.dev/api/v2/entries/ru/{word}')
|
||||||
|
if responce.status_code == 200:
|
||||||
|
responce = json.loads(responce.content)
|
||||||
|
text = ''
|
||||||
|
r = responce[0]
|
||||||
|
definition = r['meanings'][0]['definitions'][0]
|
||||||
|
short = r['word'].lower().capitalize() + ' (' + ( r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный' ) + ') — ' + definition['definition'].lower().capitalize() + ( '. Синонимы: ' + ', '.join(definition['synonyms']) if definition['synonyms'] else '')
|
||||||
|
short = short.replace(word[0].lower()+'.', word.lower())
|
||||||
|
short = short.replace(word[0].upper()+'.', word.capitalize())
|
||||||
|
short = short.replace('-н.', '-нибудь')
|
||||||
|
short = short.replace('потр.', 'потребляется')
|
||||||
|
short = short.replace('знач.', 'значении')
|
||||||
|
|
||||||
|
for r in responce:
|
||||||
|
text += '\n' + r['word'].lower().capitalize() + ' (' + (r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный') + ')\n'
|
||||||
|
for definition in r['meanings'][0]['definitions']:
|
||||||
|
text += '\t— ' + definition['definition'].lower().capitalize()
|
||||||
|
if example := definition.get('example'):
|
||||||
|
text += '\n\tНапример: ' + example
|
||||||
|
if synonyms := definition['synonyms']:
|
||||||
|
text += '\n\tСинонимы: ' + ', '.join(synonyms) + '.'
|
||||||
|
if antonyms := definition['antonyms']:
|
||||||
|
text += '\n\tАнтонимы: ' + ', '.join(antonyms) + '.'
|
||||||
|
text += '\n\n'
|
||||||
|
|
||||||
|
return {
|
||||||
|
'text': text,
|
||||||
|
'short': short,
|
||||||
|
}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def wikipedia(this, word):
|
||||||
|
wiki.set_lang("ru")
|
||||||
|
article = wiki.summary(word, sentences=5)
|
||||||
|
try: return article[:article.find('\n\n')][:600]
|
||||||
|
except: return ''
|
||||||
|
|
||||||
|
def googleSearch(this, word):
|
||||||
|
responce = requests.get(f'https://www.google.ru/search?&q={word}&lr=lang_ru&lang=ru')
|
||||||
|
page = BS(responce.content, 'html.parser')
|
||||||
|
info = page.select('div.BNeawe>div>div.BNeawe')
|
||||||
|
return info[0].get_text() if info else ''
|
||||||
|
|
||||||
|
def start(this, params):
|
||||||
|
query = params['string']
|
||||||
|
if 'вики' in query:
|
||||||
|
query = query.replace('википедия', '').replace('вики', '').strip()
|
||||||
|
try: search = this.googleSearch(query)
|
||||||
|
except: search = ''
|
||||||
|
try: wiki = this.wikipedia(query) if not 'Википедия' in search else ''
|
||||||
|
except: wiki = ''
|
||||||
|
try: gdict = this.googleDictionary(query)
|
||||||
|
except: gdict = []
|
||||||
|
voice = search or (gdict['short'] if gdict else '') or wiki
|
||||||
|
text = (f'Google Search:\n\t{search}' if search else '') + (f'\n\nWikipedia:\n\t{wiki}' if wiki else '') + ('\n\nDictionary:'+gdict['text'] if gdict else '')
|
||||||
|
else:
|
||||||
|
try: search = this.googleSearch(query)
|
||||||
|
except: search = ''
|
||||||
|
voice = text = search
|
||||||
|
return {
|
||||||
|
'type': 'simple',
|
||||||
|
'text': text,
|
||||||
|
'voice': voice,
|
||||||
|
}
|
||||||
|
|
||||||
|
google = Search('Search', {}, ['* вики* *','фильм *','* это','$quest *','{посчитай|сколько будет|корень из} *',], special=True)
|
@ -21,7 +21,7 @@ from .SmallTalk import *
|
|||||||
import datetime, time
|
import datetime, time
|
||||||
import math
|
import math
|
||||||
################################################################################
|
################################################################################
|
||||||
def method(text):
|
def method(params):
|
||||||
return {
|
return {
|
||||||
'type': 'simple',
|
'type': 'simple',
|
||||||
'text': 'Я не понимаю',
|
'text': 'Я не понимаю',
|
||||||
@ -33,7 +33,7 @@ patterns = []
|
|||||||
void = SmallTalk('Undefined', keywords, patterns)
|
void = SmallTalk('Undefined', keywords, patterns)
|
||||||
void.setStart(method)
|
void.setStart(method)
|
||||||
################################################################################
|
################################################################################
|
||||||
def method(text):
|
def method(params):
|
||||||
now = datetime.datetime.now()
|
now = datetime.datetime.now()
|
||||||
hours = now.hour%12 if now.hour else 12
|
hours = now.hour%12 if now.hour else 12
|
||||||
minutes = now.minute
|
minutes = now.minute
|
||||||
@ -112,13 +112,13 @@ keywords = {
|
|||||||
5: ['текущее', 'сейчас', 'время'],
|
5: ['текущее', 'сейчас', 'время'],
|
||||||
1: ['сколько']
|
1: ['сколько']
|
||||||
}
|
}
|
||||||
patterns = ['* который * час *', '* скольк* * (врем|час)* *', '* врем* *']
|
patterns = ['* который * час *', '* скольк* * (врем|час)* *']
|
||||||
ctime = SmallTalk('Current Time', keywords, patterns)
|
ctime = SmallTalk('Current Time', keywords, patterns)
|
||||||
ctime.setStart(method)
|
ctime.setStart(method)
|
||||||
################################################################################
|
################################################################################
|
||||||
# Only for tests
|
# Only for tests
|
||||||
@SmallTalk.background(answer = 'Запуск фонового процесса', voice = 'Запускаю фоновый процесс')
|
@SmallTalk.background(answer = 'Запуск фонового процесса', voice = 'Запускаю фоновый процесс')
|
||||||
def method(text, finish_event):
|
def method(params, finish_event):
|
||||||
time.sleep(10)
|
time.sleep(10)
|
||||||
finish_event.set()
|
finish_event.set()
|
||||||
return {
|
return {
|
||||||
|
2
main.py
2
main.py
@ -33,7 +33,7 @@ while True: # main loop
|
|||||||
if online := set(config.names) & set(text.split(' ')):
|
if online := set(config.names) & set(text.split(' ')):
|
||||||
voids = 0
|
voids = 0
|
||||||
cmd, params = Command.reg_find(text).values()
|
cmd, params = Command.reg_find(text).values()
|
||||||
responce = cmd.start(text)
|
responce = cmd.start(params)
|
||||||
memory.insert(0, {
|
memory.insert(0, {
|
||||||
'text': text,
|
'text': text,
|
||||||
'cmd': cmd,
|
'cmd': cmd,
|
||||||
|
Reference in New Issue
Block a user