From 52823a332be72489bbb261a5749ef699795a0441 Mon Sep 17 00:00:00 2001 From: mariiarazno Date: Wed, 3 Aug 2022 16:08:35 +0200 Subject: [PATCH 01/21] initial implementation --- .gitignore | 1 + .idea/.gitignore | 10 ++ .idea/inspectionProfiles/Project_Default.xml | 15 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/malls-parser-skill.iml | 10 ++ .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + __init__.py | 149 ++++++++++++++++++ locale/en-us/ask_more.dialog | 1 + locale/en-us/cancel.voc | 9 ++ locale/en-us/finished.dialog | 1 + locale/en-us/more_than_one.dialog | 2 + locale/en-us/neon.voc | 4 + locale/en-us/no.voc | 12 ++ locale/en-us/no_lang.dialog | 1 + locale/en-us/repeat.dialog | 1 + locale/en-us/run_mall_parser.intent | 13 ++ locale/en-us/shop_not_found.dialog | 2 + locale/en-us/stop.dialog | 1 + locale/en-us/what_shop.dialog | 1 + locale/en-us/yes.voc | 4 + request_handling.py | 42 +++++ requirements/requirements.txt | 4 + requirements/test_requirements.txt | 1 + test/test_skill.py | 108 +++++++++++++ 26 files changed, 416 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/malls-parser-skill.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 __init__.py create mode 100644 locale/en-us/ask_more.dialog create mode 100644 locale/en-us/cancel.voc create mode 100644 locale/en-us/finished.dialog create mode 100644 locale/en-us/more_than_one.dialog create mode 100644 locale/en-us/neon.voc create mode 100644 locale/en-us/no.voc create mode 100644 locale/en-us/no_lang.dialog create mode 100644 locale/en-us/repeat.dialog create mode 100644 locale/en-us/run_mall_parser.intent create mode 100644 locale/en-us/shop_not_found.dialog create mode 100644 locale/en-us/stop.dialog create mode 100644 locale/en-us/what_shop.dialog create mode 100644 locale/en-us/yes.voc create mode 100644 request_handling.py create mode 100644 requirements/requirements.txt create mode 100644 requirements/test_requirements.txt create mode 100644 test/test_skill.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f21b54 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/venv/ diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..0a8642f --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,10 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# Zeppelin ignored files +/ZeppelinRemoteNotebooks/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..e3a5f75 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,15 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/malls-parser-skill.iml b/.idea/malls-parser-skill.iml new file mode 100644 index 0000000..be848c2 --- /dev/null +++ b/.idea/malls-parser-skill.iml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..4ca4b14 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0ffab4a --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..f0eeabe --- /dev/null +++ b/__init__.py @@ -0,0 +1,149 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +from neon_utils.skills.neon_skill import NeonSkill, LOG +from mycroft.skills.core import intent_file_handler +from .request_handling import RequestHandler + +import os + + +class MallParserSkill(NeonSkill): + + def __init__(self): + + super(MallParserSkill, self).__init__(name="MallParserSkill") + self.script_path = os.path.join(os.path.dirname(__file__), + 'mall') + self.request_handler = object + + def initialize(self): + # When first run or prompt not dismissed, wait for load and prompt user + if self.settings.get('prompt_on_start'): + self.bus.once('mycroft.ready', self._start_mall_parser_prompt) + + @intent_file_handler("run_mall_parser.intent") + def start_mall_parser_intent(self, message): + LOG.info(message.data) + + self._start_mall_parser_prompt(message) + return + + def finish(self): + self.speak_dialog("finished") + + def repeat(self): + self.speak_dialog("repeat") + + def no_lang(self): + self.speak_dialog("no_lang") + + def what_shop(self): + user_request = self.get_response("what_shop") + return user_request + + def select(self, variants): + self.speak_dialog('more_than_one') + choice = self.get_response(variants) + return choice + + def disambiguation_handling(self, shop_info): + found_shops = [] + for shop in shop_info: + found_shops.append(shop['name']) + choice = self.select(found_shops) + selected_shop = [shop for shop in shop_info if choice in shop['name']] + return selected_shop + + def speak_shop_data(self, shop_data): + self.speak(shop_data) + + def user_request_handling(self, message): + tries = 0 + LOG.info(f"Message is {message.data}") + request_lang = message.data['lang'].split('-')[0] + mall_link = message.data['mall_link'] + user_request = message.data['utterance'] + while tries <= 3: + if RequestHandler.existing_lang_check(request_lang, mall_link): + return user_request, mall_link + else: + self.no_lang() + user_request = self.get_response("repeat") + else: + return None, None + + def execute(self, message): + count = 0 + user_request, mall_link = self.user_request_handling(message) + if user_request is None: + self.finish() + else: + user_request = self.what_shop() + while count != 3: + if user_request is not None: + self.speak_dialog(f"I am parsing shops and malls for your request") + shop_info = RequestHandler.get_shop_data(mall_link, user_request) + if shop_info is []: + self.speak_dialog("shop_not_found") + self.repeat() + elif len(shop_info) > 1: + selected_shop = self.disambiguation_handling(shop_info) + self.speak_shop_data(selected_shop) + else: + self.speak_shop_data(shop_info) + start_again = self.ask_yesno("ask_more") + if start_again == "yes": + self.execute(message) + return + else: + self.finish() + else: + user_request = self.get_response("repeat") + count += 1 + + def _start_mall_parser_prompt(self, message): + if self.neon_in_request(message): + LOG.info('Prompting Mall parsing start') + self.make_active() + start_parsing = self.ask_yesno("start") + if start_parsing == "yes": + self.execute(message) + return + else: + repeat_instr = self.ask_yesno("stop") + if repeat_instr == 'yes': + self.speak_dialog('finished') + else: + self.execute(message) + return + + +def create_skill(): + return MallParserSkill() diff --git a/locale/en-us/ask_more.dialog b/locale/en-us/ask_more.dialog new file mode 100644 index 0000000..b2b8db3 --- /dev/null +++ b/locale/en-us/ask_more.dialog @@ -0,0 +1 @@ +Do you want to get another shop info? \ No newline at end of file diff --git a/locale/en-us/cancel.voc b/locale/en-us/cancel.voc new file mode 100644 index 0000000..0c3c271 --- /dev/null +++ b/locale/en-us/cancel.voc @@ -0,0 +1,9 @@ +cancel +stop +quit +stop it +finish +finished +stoped +bye +goodbye \ No newline at end of file diff --git a/locale/en-us/finished.dialog b/locale/en-us/finished.dialog new file mode 100644 index 0000000..17ed73d --- /dev/null +++ b/locale/en-us/finished.dialog @@ -0,0 +1 @@ +Finished. Goodbye! \ No newline at end of file diff --git a/locale/en-us/more_than_one.dialog b/locale/en-us/more_than_one.dialog new file mode 100644 index 0000000..77ccb2d --- /dev/null +++ b/locale/en-us/more_than_one.dialog @@ -0,0 +1,2 @@ +I found more than one shop in your request, please choose one from ... +Which of those you need? \ No newline at end of file diff --git a/locale/en-us/neon.voc b/locale/en-us/neon.voc new file mode 100644 index 0000000..1f1bc13 --- /dev/null +++ b/locale/en-us/neon.voc @@ -0,0 +1,4 @@ +neon +hey neon +hi neon +neon start \ No newline at end of file diff --git a/locale/en-us/no.voc b/locale/en-us/no.voc new file mode 100644 index 0000000..75347ac --- /dev/null +++ b/locale/en-us/no.voc @@ -0,0 +1,12 @@ +no +nope +no way +not +don't +do not +stop +quit +end +none +nothing +no one diff --git a/locale/en-us/no_lang.dialog b/locale/en-us/no_lang.dialog new file mode 100644 index 0000000..d9d0b6e --- /dev/null +++ b/locale/en-us/no_lang.dialog @@ -0,0 +1 @@ +Sorry there is no mall info in your language. \ No newline at end of file diff --git a/locale/en-us/repeat.dialog b/locale/en-us/repeat.dialog new file mode 100644 index 0000000..be33c75 --- /dev/null +++ b/locale/en-us/repeat.dialog @@ -0,0 +1 @@ +I am noe sure I've understood what you said. Repeat, please. \ No newline at end of file diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent new file mode 100644 index 0000000..769e664 --- /dev/null +++ b/locale/en-us/run_mall_parser.intent @@ -0,0 +1,13 @@ +neon start mall parser +neon where is shop +neon find me a shop +where is shop +find me a shop +where is shop location +mall parser +start mall parser +run mall parser +run mall parsing +start mall parsing +where is the store +where is the store situated \ No newline at end of file diff --git a/locale/en-us/shop_not_found.dialog b/locale/en-us/shop_not_found.dialog new file mode 100644 index 0000000..8af425d --- /dev/null +++ b/locale/en-us/shop_not_found.dialog @@ -0,0 +1,2 @@ +Sorry, this store doesn't exist in this mall. +Sorry, I can not find this shop. \ No newline at end of file diff --git a/locale/en-us/stop.dialog b/locale/en-us/stop.dialog new file mode 100644 index 0000000..7510b94 --- /dev/null +++ b/locale/en-us/stop.dialog @@ -0,0 +1 @@ +Do you want me to stop mall parsing? \ No newline at end of file diff --git a/locale/en-us/what_shop.dialog b/locale/en-us/what_shop.dialog new file mode 100644 index 0000000..f877003 --- /dev/null +++ b/locale/en-us/what_shop.dialog @@ -0,0 +1 @@ +What shop are you looking for? \ No newline at end of file diff --git a/locale/en-us/yes.voc b/locale/en-us/yes.voc new file mode 100644 index 0000000..d9ea0c3 --- /dev/null +++ b/locale/en-us/yes.voc @@ -0,0 +1,4 @@ +yes +yeah +yep +aha diff --git a/request_handling.py b/request_handling.py new file mode 100644 index 0000000..9463101 --- /dev/null +++ b/request_handling.py @@ -0,0 +1,42 @@ +import urllib.request as urllib2 +import requests +from bs4 import BeautifulSoup +from neon_utils.skills.neon_skill import LOG + + +class RequestHandler: + + def __init__(self): + self.found_shops = [] + + def existing_lang_check(self, user_lang, url): + link = url+user_lang+'/directory/' + response = requests.get(link) + if response.status_code == 200: + LOG.info('This language is supported') + return True + else: + LOG.info('This language is not supported') + return False + + def get_shop_data(self, url, user_request): + page = urllib2.urlopen(url) + soup = BeautifulSoup(page, 'html.parser') + + for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): + info = shop.find_next(attrs={"class": "tenant-info-container"}) + name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') + if name.lower() in user_request.lower(): + logo = shop.find_next("img").get('src') + hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') + location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') + shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} + self.found_shops.append((shop_data)) + LOG.info(f"{name} {hours} {location} {logo}") + else: + LOG.info('NOTHING FOUND') + return self.found_shops + + def image_extraction(self): + # Todo extract image from web-pade + ... diff --git a/requirements/requirements.txt b/requirements/requirements.txt new file mode 100644 index 0000000..a1c18f3 --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,4 @@ +numpy +neon_utils>=0.17,<2.0.0 +requests +bs4 \ No newline at end of file diff --git a/requirements/test_requirements.txt b/requirements/test_requirements.txt new file mode 100644 index 0000000..55b033e --- /dev/null +++ b/requirements/test_requirements.txt @@ -0,0 +1 @@ +pytest \ No newline at end of file diff --git a/test/test_skill.py b/test/test_skill.py new file mode 100644 index 0000000..e3c85cf --- /dev/null +++ b/test/test_skill.py @@ -0,0 +1,108 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import shutil +import unittest +import pytest + +from os import mkdir +from os.path import dirname, join, exists +from mock import Mock +from ovos_utils.messagebus import FakeBus + +from mycroft.skills.skill_loader import SkillLoader + +from mycroft_bus_client import Message + + +class TestSkill(unittest.TestCase): + + @classmethod + def setUpClass(cls) -> None: + bus = FakeBus() + bus.run_in_thread() + skill_loader = SkillLoader(bus, dirname(dirname(__file__))) + skill_loader.load() + cls.skill = skill_loader.instance + + # Define a directory to use for testing + cls.test_fs = join(dirname(__file__), "skill") + if not exists(cls.test_fs): + mkdir(cls.test_fs) + + # Override the configuration and fs paths to use the test directory + cls.skill.settings_write_path = cls.test_fs + cls.skill.file_system.path = cls.test_fs + cls.skill._init_settings() + cls.skill.initialize() + + # Override speak and speak_dialog to test passed arguments + cls.skill.speak = Mock() + cls.skill.speak_dialog = Mock() + + # TODO: Put any skill method overrides here + + def setUp(self): + self.skill.speak.reset_mock() + self.skill.speak_dialog.reset_mock() + + # TODO: Put any cleanup here that runs before each test case + + @classmethod + def tearDownClass(cls) -> None: + shutil.rmtree(cls.test_fs) + + def test_en_skill_init(self): + real_askyesno = self.skill.ask_yesno + real_execute = self.skill.execute + real_user_request_handling = self.skill.user_request_handling + self.skill.ask_yesno = Mock(return_value="yes") + self.skill.execute = Mock() + self.skill.user_request_handling = Mock() + self.skill._start_mall_parser_prompt( + Message('test', {'utterance': 'start mall parsing', + 'lang': 'en-us', + 'mall_link': "https://www.alamoanacenter.com/"}, + {'context_key': 'MallParsing'})) + self.skill.execute.assert_called_once() + + message = Message('test', {'utterance': 'start mall parsing', + 'lang': 'en-us'}, + {'context_key': 'Instructions'}) + self.skill.user_request_handling(message) + self.skill.ask_yesno.assert_called_once_with("start") + self.skill.execute(message) + + self.skill.execute = real_execute + self.skill.ask_yesno = real_askyesno + self.skill.user_request_handling = real_user_request_handling + + + +if __name__ == '__main__': + pytest.main() From 86c7fe52b71b5dca7108b3f7d36d957d14120141 Mon Sep 17 00:00:00 2001 From: NeonMariia <95533543+NeonMariia@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:11:39 +0200 Subject: [PATCH 02/21] Delete .idea directory --- .idea/.gitignore | 10 ---------- .idea/inspectionProfiles/Project_Default.xml | 15 --------------- .idea/inspectionProfiles/profiles_settings.xml | 6 ------ .idea/malls-parser-skill.iml | 10 ---------- .idea/misc.xml | 4 ---- .idea/modules.xml | 8 -------- .idea/vcs.xml | 6 ------ 7 files changed, 59 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/inspectionProfiles/profiles_settings.xml delete mode 100644 .idea/malls-parser-skill.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 0a8642f..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,10 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Zeppelin ignored files -/ZeppelinRemoteNotebooks/ diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index e3a5f75..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/malls-parser-skill.iml b/.idea/malls-parser-skill.iml deleted file mode 100644 index be848c2..0000000 --- a/.idea/malls-parser-skill.iml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 4ca4b14..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 0ffab4a..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 2307ef52ad63066fc81dc3b8525eb0d291fd9a0f Mon Sep 17 00:00:00 2001 From: mariiarazno Date: Thu, 4 Aug 2022 17:49:20 +0200 Subject: [PATCH 03/21] some functions removal --- __init__.py | 94 ++++++++++------------------- locale/en-us/cancel.voc | 9 --- locale/en-us/neon.voc | 4 -- locale/en-us/no.voc | 12 ---- locale/en-us/run_mall_parser.intent | 16 +---- 5 files changed, 36 insertions(+), 99 deletions(-) delete mode 100644 locale/en-us/cancel.voc delete mode 100644 locale/en-us/neon.voc delete mode 100644 locale/en-us/no.voc diff --git a/__init__.py b/__init__.py index f0eeabe..c711bf3 100644 --- a/__init__.py +++ b/__init__.py @@ -34,14 +34,10 @@ import os -class MallParserSkill(NeonSkill): +class DirectorySkill(NeonSkill): def __init__(self): - - super(MallParserSkill, self).__init__(name="MallParserSkill") - self.script_path = os.path.join(os.path.dirname(__file__), - 'mall') - self.request_handler = object + super(DirectorySkill, self).__init__(name="DirectorySkill") def initialize(self): # When first run or prompt not dismissed, wait for load and prompt user @@ -55,34 +51,10 @@ def start_mall_parser_intent(self, message): self._start_mall_parser_prompt(message) return - def finish(self): - self.speak_dialog("finished") - - def repeat(self): - self.speak_dialog("repeat") - - def no_lang(self): - self.speak_dialog("no_lang") - - def what_shop(self): - user_request = self.get_response("what_shop") - return user_request - - def select(self, variants): - self.speak_dialog('more_than_one') - choice = self.get_response(variants) - return choice - - def disambiguation_handling(self, shop_info): - found_shops = [] - for shop in shop_info: - found_shops.append(shop['name']) - choice = self.select(found_shops) - selected_shop = [shop for shop in shop_info if choice in shop['name']] - return selected_shop - - def speak_shop_data(self, shop_data): - self.speak(shop_data) + def start_again(self, message): + start_again = self.ask_yesno("ask_more") + if start_again == "yes": + self._start_mall_parser_prompt(self, message) def user_request_handling(self, message): tries = 0 @@ -94,39 +66,39 @@ def user_request_handling(self, message): if RequestHandler.existing_lang_check(request_lang, mall_link): return user_request, mall_link else: - self.no_lang() + self.speak_dialog("no_lang") user_request = self.get_response("repeat") else: return None, None - def execute(self, message): + def find_shop(self, user_request, mall_link): count = 0 - user_request, mall_link = self.user_request_handling(message) - if user_request is None: - self.finish() - else: - user_request = self.what_shop() - while count != 3: - if user_request is not None: - self.speak_dialog(f"I am parsing shops and malls for your request") - shop_info = RequestHandler.get_shop_data(mall_link, user_request) - if shop_info is []: - self.speak_dialog("shop_not_found") - self.repeat() - elif len(shop_info) > 1: - selected_shop = self.disambiguation_handling(shop_info) - self.speak_shop_data(selected_shop) - else: - self.speak_shop_data(shop_info) - start_again = self.ask_yesno("ask_more") - if start_again == "yes": - self.execute(message) - return - else: - self.finish() - else: + while count != 3: + if user_request is not None: + self.speak_dialog(f"I am parsing shops and malls for your request") + shop_info = RequestHandler.get_shop_data(mall_link, user_request) + if shop_info is []: + self.speak_dialog("shop_not_found") user_request = self.get_response("repeat") + self.find_shop(user_request, mall_link) count += 1 + elif len(shop_info) > 1: + shop_names = [shop['name'] for shop in shop_info] + selected_shop = self.ask_selection(shop_names, 'more_than_one') + if selected_shop is not None: + selected_shop_info = [shop for shop in shop_info if selected_shop in shop['name']] + shop_str_info = f"{selected_shop_info['name']} {selected_shop_info['hours']} {selected_shop_info['location']}" + self.speak(shop_str_info) + else: + shop_str_info = f"{shop_info['name']} {shop_info['hours']} {shop_info['location']}" + self.speak(shop_str_info) + else: + self.speak_dialog('finish') + + def execute(self, message): + user_request, mall_link = self.user_request_handling(message) + self.find_shop(user_request, mall_link) + self.start_again(message) def _start_mall_parser_prompt(self, message): if self.neon_in_request(message): @@ -146,4 +118,4 @@ def _start_mall_parser_prompt(self, message): def create_skill(): - return MallParserSkill() + return DirectorySkill() diff --git a/locale/en-us/cancel.voc b/locale/en-us/cancel.voc deleted file mode 100644 index 0c3c271..0000000 --- a/locale/en-us/cancel.voc +++ /dev/null @@ -1,9 +0,0 @@ -cancel -stop -quit -stop it -finish -finished -stoped -bye -goodbye \ No newline at end of file diff --git a/locale/en-us/neon.voc b/locale/en-us/neon.voc deleted file mode 100644 index 1f1bc13..0000000 --- a/locale/en-us/neon.voc +++ /dev/null @@ -1,4 +0,0 @@ -neon -hey neon -hi neon -neon start \ No newline at end of file diff --git a/locale/en-us/no.voc b/locale/en-us/no.voc deleted file mode 100644 index 75347ac..0000000 --- a/locale/en-us/no.voc +++ /dev/null @@ -1,12 +0,0 @@ -no -nope -no way -not -don't -do not -stop -quit -end -none -nothing -no one diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent index 769e664..b6a3152 100644 --- a/locale/en-us/run_mall_parser.intent +++ b/locale/en-us/run_mall_parser.intent @@ -1,13 +1,3 @@ -neon start mall parser -neon where is shop -neon find me a shop -where is shop -find me a shop -where is shop location -mall parser -start mall parser -run mall parser -run mall parsing -start mall parsing -where is the store -where is the store situated \ No newline at end of file +where (is|can i find) {{shop}} +where is {{shop}}(| located) +where is {{shop}}(| situated) \ No newline at end of file From 21a64d02dc9138612ac89a8df9d0e1e68635d9f0 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 8 Aug 2022 10:03:53 -0400 Subject: [PATCH 04/21] mall link from settings --- __init__.py | 75 ++++++++++++++++++----------- locale/en-us/repeat.dialog | 2 +- locale/en-us/run_mall_parser.intent | 4 +- locale/en-us/shop_not_found.dialog | 4 +- request_handling.py | 27 ++++++----- 5 files changed, 64 insertions(+), 48 deletions(-) diff --git a/__init__.py b/__init__.py index c711bf3..3678be1 100644 --- a/__init__.py +++ b/__init__.py @@ -31,7 +31,6 @@ from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler -import os class DirectorySkill(NeonSkill): @@ -51,20 +50,28 @@ def start_mall_parser_intent(self, message): self._start_mall_parser_prompt(message) return + def mall_link(self): + mall_link = 'https://www.alamoanacenter.com/' + return self.settings.get("mall_link") or mall_link + def start_again(self, message): start_again = self.ask_yesno("ask_more") if start_again == "yes": self._start_mall_parser_prompt(self, message) + else: + self.speak_dialog('finished') def user_request_handling(self, message): tries = 0 LOG.info(f"Message is {message.data}") request_lang = message.data['lang'].split('-')[0] - mall_link = message.data['mall_link'] - user_request = message.data['utterance'] + user_request = message.data['shop'] + LOG.info(f"{self.mall_link()}") + LOG.info(str(request_lang)) + LOG.info(user_request) while tries <= 3: - if RequestHandler.existing_lang_check(request_lang, mall_link): - return user_request, mall_link + if RequestHandler.existing_lang_check(request_lang, self.mall_link()): + return user_request, self.mall_link() else: self.speak_dialog("no_lang") user_request = self.get_response("repeat") @@ -72,39 +79,49 @@ def user_request_handling(self, message): return None, None def find_shop(self, user_request, mall_link): - count = 0 - while count != 3: - if user_request is not None: - self.speak_dialog(f"I am parsing shops and malls for your request") - shop_info = RequestHandler.get_shop_data(mall_link, user_request) - if shop_info is []: - self.speak_dialog("shop_not_found") - user_request = self.get_response("repeat") - self.find_shop(user_request, mall_link) - count += 1 - elif len(shop_info) > 1: - shop_names = [shop['name'] for shop in shop_info] - selected_shop = self.ask_selection(shop_names, 'more_than_one') - if selected_shop is not None: - selected_shop_info = [shop for shop in shop_info if selected_shop in shop['name']] - shop_str_info = f"{selected_shop_info['name']} {selected_shop_info['hours']} {selected_shop_info['location']}" - self.speak(shop_str_info) - else: - shop_str_info = f"{shop_info['name']} {shop_info['hours']} {shop_info['location']}" + if user_request is not None: + self.speak_dialog(f"I am parsing shops and malls for your request") + shop_info = RequestHandler.get_shop_data(mall_link, user_request) + if len(shop_info) == 0: + self.speak_dialog("shop_not_found") + user_request = self.get_response('repeat') + return 1, user_request + elif len(shop_info) > 1: + shop_names = [shop['name'] for shop in shop_info] + selected_shop = self.ask_selection(shop_names, 'more_than_one') + if selected_shop is not None: + selected_shop_info = [shop for shop in shop_info if selected_shop in shop['name']] + LOG.info(f"{selected_shop_info}") + shop_str_info = f"{selected_shop_info['name']} {selected_shop_info['hours']} {selected_shop_info['location']}" self.speak(shop_str_info) + return 3, None + else: + LOG.info(f"found shop {shop_info}") + shop_str_info = f"{shop_info[0]['name']} {shop_info[0]['hours']} {shop_info[0]['location']}" + self.speak(shop_str_info) + return 3, None else: - self.speak_dialog('finish') + self.speak_dialog('finished') + return 3 def execute(self, message): - user_request, mall_link = self.user_request_handling(message) - self.find_shop(user_request, mall_link) - self.start_again(message) + if message is not None: + user_request, mall_link = self.user_request_handling(message) + count = 0 + while count < 3: + LOG.info(str(user_request)) + LOG.info(str(mall_link)) + new_count, user_request = self.find_shop(user_request, mall_link) + count = count+ new_count + self.start_again(message) + else: + self.speak_dialog('finished') def _start_mall_parser_prompt(self, message): if self.neon_in_request(message): LOG.info('Prompting Mall parsing start') self.make_active() - start_parsing = self.ask_yesno("start") + start_parsing = self.ask_yesno("start mall parsing?") if start_parsing == "yes": self.execute(message) return diff --git a/locale/en-us/repeat.dialog b/locale/en-us/repeat.dialog index be33c75..853f0fc 100644 --- a/locale/en-us/repeat.dialog +++ b/locale/en-us/repeat.dialog @@ -1 +1 @@ -I am noe sure I've understood what you said. Repeat, please. \ No newline at end of file +I am not sure I've understood what you said. Repeat, please. \ No newline at end of file diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent index b6a3152..baa9dc5 100644 --- a/locale/en-us/run_mall_parser.intent +++ b/locale/en-us/run_mall_parser.intent @@ -1,3 +1 @@ -where (is|can i find) {{shop}} -where is {{shop}}(| located) -where is {{shop}}(| situated) \ No newline at end of file +where (is|can i find) {shop} \ No newline at end of file diff --git a/locale/en-us/shop_not_found.dialog b/locale/en-us/shop_not_found.dialog index 8af425d..5ff4fd8 100644 --- a/locale/en-us/shop_not_found.dialog +++ b/locale/en-us/shop_not_found.dialog @@ -1,2 +1,2 @@ -Sorry, this store doesn't exist in this mall. -Sorry, I can not find this shop. \ No newline at end of file +Sorry, this store doesn't exist in this mall. Repeat, please. +Sorry, I can not find this shop. Repeat, please. \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 9463101..5fe08c4 100644 --- a/request_handling.py +++ b/request_handling.py @@ -1,15 +1,12 @@ import urllib.request as urllib2 import requests -from bs4 import BeautifulSoup +import bs4 from neon_utils.skills.neon_skill import LOG class RequestHandler: - def __init__(self): - self.found_shops = [] - - def existing_lang_check(self, user_lang, url): + def existing_lang_check(user_lang, url): link = url+user_lang+'/directory/' response = requests.get(link) if response.status_code == 200: @@ -19,24 +16,28 @@ def existing_lang_check(self, user_lang, url): LOG.info('This language is not supported') return False - def get_shop_data(self, url, user_request): + def get_shop_data(url, user_request): + found_shops = [] page = urllib2.urlopen(url) - soup = BeautifulSoup(page, 'html.parser') - + soup = bs4.BeautifulSoup(page, 'html.parser') + LOG.info(url+user_request) + LOG.info(soup.find_all(attrs={"class": "directory-tenant-card"})) for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): + LOG.info(str(shop)) info = shop.find_next(attrs={"class": "tenant-info-container"}) name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') + LOG.info(str(name)) if name.lower() in user_request.lower(): logo = shop.find_next("img").get('src') hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} - self.found_shops.append((shop_data)) + found_shops.append(shop_data) LOG.info(f"{name} {hours} {location} {logo}") else: LOG.info('NOTHING FOUND') - return self.found_shops + return found_shops - def image_extraction(self): - # Todo extract image from web-pade - ... + # def image_extraction(): + # # Todo extract image from web-pade + # ... From c8992fec6dde830b6864de8f0dd3dfca2e2bf6a1 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sun, 14 Aug 2022 12:11:35 -0400 Subject: [PATCH 05/21] web page scraping, output fixes --- __init__.py | 103 +++++++++--------- __pycache__/__init__.cpython-310.pyc | Bin 0 -> 4106 bytes __pycache__/request_handling.cpython-310.pyc | Bin 0 -> 1975 bytes locale/en-us/another_shop.dialog | 1 + locale/en-us/run_mall_parser.intent | 2 +- locale/en-us/shop_not_found.dialog | 4 +- locale/en-us/unexpected_error.dialog | 1 + request_handling.py | 41 ++++--- requirements/requirements.txt | 3 +- ...st_skill.py => mall_parsing_test_skill.py} | 22 ++-- 10 files changed, 99 insertions(+), 78 deletions(-) create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/request_handling.cpython-310.pyc create mode 100644 locale/en-us/another_shop.dialog create mode 100644 locale/en-us/unexpected_error.dialog rename test/{test_skill.py => mall_parsing_test_skill.py} (87%) diff --git a/__init__.py b/__init__.py index 3678be1..8dccd64 100644 --- a/__init__.py +++ b/__init__.py @@ -54,84 +54,89 @@ def mall_link(self): mall_link = 'https://www.alamoanacenter.com/' return self.settings.get("mall_link") or mall_link - def start_again(self, message): - start_again = self.ask_yesno("ask_more") - if start_again == "yes": - self._start_mall_parser_prompt(self, message) - else: - self.speak_dialog('finished') - def user_request_handling(self, message): - tries = 0 LOG.info(f"Message is {message.data}") request_lang = message.data['lang'].split('-')[0] user_request = message.data['shop'] LOG.info(f"{self.mall_link()}") LOG.info(str(request_lang)) LOG.info(user_request) - while tries <= 3: - if RequestHandler.existing_lang_check(request_lang, self.mall_link()): - return user_request, self.mall_link() - else: - self.speak_dialog("no_lang") - user_request = self.get_response("repeat") + found, link = RequestHandler.existing_lang_check(request_lang, self.mall_link()) + if found: + link = self.mall_link()+request_lang+'/directory/' + LOG.info('new link: '+ link) + return user_request, link else: + self.speak_dialog("no_lang") return None, None + def start_again(self): + start_again = self.ask_yesno("ask_more") + if start_again == "yes": + another_shop = self.get_response('another_shop') + LOG.info('another shop'+another_shop) + return another_shop + elif start_again == "no": + return None + else: + self.speak_dialog('unexpected_error') + return None + def find_shop(self, user_request, mall_link): if user_request is not None: + request_handler = RequestHandler() self.speak_dialog(f"I am parsing shops and malls for your request") - shop_info = RequestHandler.get_shop_data(mall_link, user_request) + shop_info = request_handler.get_shop_data(mall_link, user_request) if len(shop_info) == 0: self.speak_dialog("shop_not_found") user_request = self.get_response('repeat') return 1, user_request elif len(shop_info) > 1: - shop_names = [shop['name'] for shop in shop_info] - selected_shop = self.ask_selection(shop_names, 'more_than_one') - if selected_shop is not None: - selected_shop_info = [shop for shop in shop_info if selected_shop in shop['name']] - LOG.info(f"{selected_shop_info}") - shop_str_info = f"{selected_shop_info['name']} {selected_shop_info['hours']} {selected_shop_info['location']}" - self.speak(shop_str_info) + self.speak_dialog('more_than_one') + for shop in shop_info: + shop_str = 'found shop %s work hours are %s you can find this store on %s' % (shop['name'], shop['hours'], shop['location']) + self.speak(f"found shop {shop['name']} work hours are {shop['hours']} you can find this store on {shop['location']}") return 3, None else: LOG.info(f"found shop {shop_info}") - shop_str_info = f"{shop_info[0]['name']} {shop_info[0]['hours']} {shop_info[0]['location']}" - self.speak(shop_str_info) + self.speak(f"found shop {shop_info[0]['name']} work hours are {shop_info[0]['hours']} you can find this store on {shop_info[0]['location']}") return 3, None else: - self.speak_dialog('finished') - return 3 - - def execute(self, message): - if message is not None: - user_request, mall_link = self.user_request_handling(message) - count = 0 - while count < 3: - LOG.info(str(user_request)) - LOG.info(str(mall_link)) - new_count, user_request = self.find_shop(user_request, mall_link) - count = count+ new_count - self.start_again(message) + return 3, None + + def execute(self, user_request, mall_link): + count = 0 + user_request = user_request + LOG.info('Start execute') + while count < 3: + LOG.info(str(user_request)) + LOG.info(str(mall_link)) + new_count, user_request = self.find_shop(user_request, mall_link) + count = count + new_count + user_request = self.start_again() + LOG.info(str(user_request)) + if user_request is not None: + LOG.info('New execution') + self.execute(user_request, mall_link) else: - self.speak_dialog('finished') + return None def _start_mall_parser_prompt(self, message): - if self.neon_in_request(message): LOG.info('Prompting Mall parsing start') self.make_active() - start_parsing = self.ask_yesno("start mall parsing?") - if start_parsing == "yes": - self.execute(message) - return - else: - repeat_instr = self.ask_yesno("stop") - if repeat_instr == 'yes': - self.speak_dialog('finished') + if message is not None: + LOG.info('new message'+str(message)) + user_request, mall_link = self.user_request_handling(message) + LOG.info(mall_link) + if user_request is not None: + if self.execute(user_request, mall_link) is not None: + LOG.info('executed') + return + else: + self.speak_dialog('finished') else: - self.execute(message) - return + self.speak_dialog('finished') + def create_skill(): diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d13f3d10f9d5270b83d1797817a27ffab8efcfe6 GIT binary patch literal 4106 zcmZ`+&5zs073c6nqO_9sv$i*J9JNW4sLg6qqb<C46wD`?8*fz?}kj$N&uDC=51nkbX^?6NUXS$c31CrRo2>Cc~H za63+OmFD4LoT%_PN@b$-b5Skqd{Hg@L)AT`{jNXGtYIW7$_j+E0{a{5P)H%|Cw|~a zN4hwVt32tQi|VZBBB;&IS(i<@hH+oEWEL;=3ld-AKZr@Dpzy-UZkQQQ#Io6BPK=W}0@hcNOnEqohAVLh}ipxsNMH&C}P9BJe0UfP6_ z1AZZ0us+t=Xq<;x8k#)P`LsPc9qQ~b?`svw)1FuQM&)^&9+}cPQhDh-n3&Sd(xEDM zt0f@`6jt(t)poj#Bk0XVv4vXMBwfW(5>M67&^9#`Eqh0}|C{cv$apn#dP7gr>woB1 zC>)Y@<`31$k}UmesN|5n5D)E3*s-uKoC12N{#&e|>t})W$s_i$Y9>w7cE$AF6=`0zZpng6GVEYSDb9dMwj$&hHwwXc zoW6OS=VSBX{{G{~kNZ&)jj||>hB$`O{b4rRFB@!+Bu-Bt(KdF{wB1ZPeTO=hEyI1k zM!zAwxMD+0Z>@;1*m6nlr1J&pK0pJy#%lh=h@Z;cpTbxt99nCYycz8fhKyJu6B=8nAFN^ye))U+#*o=vu_bmy zlMITI4BHY-8@2zq_37Iy2L8dVhNOu3L--(4qCRs6IxG;TCPF{Wt#dLb8J!JQI2Qmo zff}ZD3Ks#3!2S-;U~rSU2nc6FdW9gf&E|P0t{zt5t#CiF&c&IBxnAMHkoMoKryyqh zG2+nJK*08AvuX^9S_Pk?CY!r}l3YWrwWzhJ_Puvj$Bur%5Ro1H8DNs@g}Z>NEA3rt zu|w6G?^3m1u@lR;LC}+%0NU1Iy7$L!H0l!f!!BKdmgyn|=yHtf9%j0GnoV?fZeGG= z(4lEznjw?OCaKi#p;MFIU*bdJmIJ(o27q&jx~LM1s~uY8Pd`E_mXTFCFVXV^Myg17%I$>_8?TM zmrhCyK~&4I)uJx6r0hZMhYYH3BM9@a!fHXx_KfgM+{sqQE+LI1^+tQRF zz7XBgJBCU@lyu5G&QdTtXIcAn^ zqT1>t*z$@4$o;{&J-_nfJ=&E*NU%Ql;#npbG7twkAq9N zS&4j?c4m7u7MAz|eWs7X7d1F|i`<(Xy#SeTFRcim5o)-R7XrWHlf8oqiJsMU>35 z@l4;rdWx$Mpt^`sBp64?a%~iys4yDl@mEU!n7Z##!8UNOYyia54>?K3SttZjz{Q>v zP*<`s%0>T**H8?u@J_*vP~No35QK5}U(@$iOz_HVyw{)V!*hn1v-A+^893yo6oA3@yQuaA&%gGwEBLmCxU}Rd0iuU=;-$ mOB@KpR!mEI1po2}59!~l^nafuG<4f*y+MgfOKiBUqyGb)*~x$a literal 0 HcmV?d00001 diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a35a46108242e0c46acb9753d90edcc799f74bc7 GIT binary patch literal 1975 zcma)7OK&7K5VpJBkD2M2WS2nLWwnHdghnVcS^*bWAzorZLW4wBTG0};I%6l(Nl*90 zc5gPLo>Ou~`~ZYBIl?dDH~7jaKY&XPgep(6dF&o=tIKwk?W+FjD|gv$Hwm;`pZv3V z6cO?_POdf=CZEBpc0h2#X->v8cIaa4#;(<|m_hI4-q?@*b6XGx))vO0?Hl7}+&Z^K zaRfRyZ;v~1hmuExGwvM`?j1Vu5~mxa>z{x>GU!s{e)s(&=okVCXwZO>!u zfmV>1auPed#a+&h7)0oC|A;_@0S}?~c!M{g_qhXpBGdUn>{UYPZ_i(7upvH#K}~D23K?4^ zGe?s-t({{!b8C0U;m)ojSG7B5+&!j{6ShI@%(F8eJ}nqbTYa8MG1R3z=%4%r2Tt!k z9%X7RPmAp;-4<&wR@G!uN-g-~>HQasMX4_reBJd-c>cx8xZ6UTmeQ%NR5C2NkoYNv zWyMz6WpSedloE7g+)Cz#0X)07T}{dYOwX~FA#Qs`Jj;{@1nrh&I1@UduJ=obma!lcIgladeIs%c#iC! zFxe)6BoY;{1$3MpASjOK-Z7am?azan0q)MstG%7jqVMYlmN_85Gz;or-qfwyKeYQ_ z!2ZEC?9q?E6XE5MQ6Bta_rSUBOU+m0r^YPAh~U(4aq8Z;wF_Aa2f)c^2dvX}XO?B5mR$TTSPV z;?`DK!6G#(CrZ8nXT(gw>2ZYVhFY{P48yd;%?7J(R$Cit*KSXEs?)dN1cmW1r%9Kc zXsHPtSOyNf=C8QGi1zf}4JKUM-gP8a*q|HW)rUMw64OkQamg!Sbz-6<*{jlgVUfrg zc^k!j6zeE1F9SP5N=dv7TktCUyM?6bgl^c30;}&Xo6Z9L-Z>`nU07u^04e!E;zs!% zs_qq{ED}gTu6m1myk|9bY_l2rYADmmfqWCz#|`Afq6*of7W0vO2Q4A|BSeu^V~HhT e<9J!%CVaT~@=uXiY6V0@LK*_57c!!^o&NxN67dEA literal 0 HcmV?d00001 diff --git a/locale/en-us/another_shop.dialog b/locale/en-us/another_shop.dialog new file mode 100644 index 0000000..be4fbfd --- /dev/null +++ b/locale/en-us/another_shop.dialog @@ -0,0 +1 @@ +What shop you are looking for? \ No newline at end of file diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent index baa9dc5..d3bc909 100644 --- a/locale/en-us/run_mall_parser.intent +++ b/locale/en-us/run_mall_parser.intent @@ -1 +1 @@ -where (is|can i find) {shop} \ No newline at end of file +where (is|are|can i find) {shop} \ No newline at end of file diff --git a/locale/en-us/shop_not_found.dialog b/locale/en-us/shop_not_found.dialog index 5ff4fd8..8af425d 100644 --- a/locale/en-us/shop_not_found.dialog +++ b/locale/en-us/shop_not_found.dialog @@ -1,2 +1,2 @@ -Sorry, this store doesn't exist in this mall. Repeat, please. -Sorry, I can not find this shop. Repeat, please. \ No newline at end of file +Sorry, this store doesn't exist in this mall. +Sorry, I can not find this shop. \ No newline at end of file diff --git a/locale/en-us/unexpected_error.dialog b/locale/en-us/unexpected_error.dialog new file mode 100644 index 0000000..b9b134c --- /dev/null +++ b/locale/en-us/unexpected_error.dialog @@ -0,0 +1 @@ +Unexpected input \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 5fe08c4..8ba5ace 100644 --- a/request_handling.py +++ b/request_handling.py @@ -1,41 +1,52 @@ -import urllib.request as urllib2 +from urllib.error import HTTPError import requests import bs4 from neon_utils.skills.neon_skill import LOG +import urllib +import urllib.request +class RequestHandler(): -class RequestHandler: + def __init__(self) -> None: + pass def existing_lang_check(user_lang, url): link = url+user_lang+'/directory/' response = requests.get(link) if response.status_code == 200: LOG.info('This language is supported') - return True + return True, link else: LOG.info('This language is not supported') - return False + return False, link - def get_shop_data(url, user_request): + def parse(self, url): + url = "https://www.alamoanacenter.com/en/directory/" + headers = { + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36' + } + request = urllib.request.Request(url, + headers=headers) + try: + with urllib.request.urlopen(request) as page: + soup = bs4.BeautifulSoup(page.read(), features='lxml') + return soup + except HTTPError: + LOG.info("Failed url parsing") + + + def get_shop_data(self, url, user_request): found_shops = [] - page = urllib2.urlopen(url) - soup = bs4.BeautifulSoup(page, 'html.parser') - LOG.info(url+user_request) - LOG.info(soup.find_all(attrs={"class": "directory-tenant-card"})) + soup = self.parse(url) for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): - LOG.info(str(shop)) + logo = shop.find_next("img").get('src') info = shop.find_next(attrs={"class": "tenant-info-container"}) name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') - LOG.info(str(name)) if name.lower() in user_request.lower(): - logo = shop.find_next("img").get('src') hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} found_shops.append(shop_data) - LOG.info(f"{name} {hours} {location} {logo}") - else: - LOG.info('NOTHING FOUND') return found_shops # def image_extraction(): diff --git a/requirements/requirements.txt b/requirements/requirements.txt index a1c18f3..45ac700 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,5 @@ numpy neon_utils>=0.17,<2.0.0 +bs4 +urllib requests -bs4 \ No newline at end of file diff --git a/test/test_skill.py b/test/mall_parsing_test_skill.py similarity index 87% rename from test/test_skill.py rename to test/mall_parsing_test_skill.py index e3c85cf..51fb2d6 100644 --- a/test/test_skill.py +++ b/test/mall_parsing_test_skill.py @@ -82,22 +82,24 @@ def test_en_skill_init(self): real_execute = self.skill.execute real_user_request_handling = self.skill.user_request_handling self.skill.ask_yesno = Mock(return_value="yes") - self.skill.execute = Mock() - self.skill.user_request_handling = Mock() + #self.skill.execute = Mock() + #self.skill.user_request_handling = Mock() self.skill._start_mall_parser_prompt( - Message('test', {'utterance': 'start mall parsing', - 'lang': 'en-us', - 'mall_link': "https://www.alamoanacenter.com/"}, + Message('test', {'utterance': 'where is apple', + 'shop': 'apple', + 'lang': 'en-us' + }, {'context_key': 'MallParsing'})) self.skill.execute.assert_called_once() - message = Message('test', {'utterance': 'start mall parsing', + message = Message('test', {'utterance': 'where is apple', + 'shop': 'apple', 'lang': 'en-us'}, - {'context_key': 'Instructions'}) + {'context_key': 'MallParsing'}) + self.skill.user_request_handling(message) - self.skill.ask_yesno.assert_called_once_with("start") + #self.skill.ask_yesno.assert_called_once_with("start mall parsing?") self.skill.execute(message) - self.skill.execute = real_execute self.skill.ask_yesno = real_askyesno self.skill.user_request_handling = real_user_request_handling @@ -105,4 +107,4 @@ def test_en_skill_init(self): if __name__ == '__main__': - pytest.main() + unittest.main() From c42bfa94ae94f8b4eaa74d8757148f686cf8b340 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Fri, 19 Aug 2022 12:26:20 -0400 Subject: [PATCH 06/21] level selection, number pronunciation --- __init__.py | 56 +++++++++++++++---- __pycache__/__init__.cpython-310.pyc | Bin 4106 -> 0 bytes __pycache__/request_handling.cpython-310.pyc | Bin 1975 -> 0 bytes locale/en-us/all_locations.dialog | 1 + locale/en-us/found_shop.dialog | 1 + locale/en-us/no_shop_on_level.dialog | 1 + locale/en-us/no_shop_request.dialog | 1 + locale/en-us/run_mall_parser.intent | 8 ++- locale/en-us/shop_by_floor.dialog | 1 + locale/en-us/speak_all_shops.dialog | 1 + locale/en-us/start_parsing.dialog | 1 + locale/en-us/stop.dialog | 2 +- locale/en-us/unexpected_error.dialog | 2 +- locale/en-us/what_shop.dialog | 1 - locale/en-us/which_floor.dialog | 3 + request_handling.py | 41 +++++++++++--- requirements/requirements.txt | 3 +- test/mall_parsing_test_skill.py | 9 ++- 18 files changed, 104 insertions(+), 28 deletions(-) delete mode 100644 __pycache__/__init__.cpython-310.pyc delete mode 100644 __pycache__/request_handling.cpython-310.pyc create mode 100644 locale/en-us/all_locations.dialog create mode 100644 locale/en-us/found_shop.dialog create mode 100644 locale/en-us/no_shop_on_level.dialog create mode 100644 locale/en-us/no_shop_request.dialog create mode 100644 locale/en-us/shop_by_floor.dialog create mode 100644 locale/en-us/speak_all_shops.dialog create mode 100644 locale/en-us/start_parsing.dialog delete mode 100644 locale/en-us/what_shop.dialog create mode 100644 locale/en-us/which_floor.dialog diff --git a/__init__.py b/__init__.py index 8dccd64..4f9df4a 100644 --- a/__init__.py +++ b/__init__.py @@ -37,6 +37,9 @@ class DirectorySkill(NeonSkill): def __init__(self): super(DirectorySkill, self).__init__(name="DirectorySkill") + self.request_handler = RequestHandler() + self.cache = dict() + def initialize(self): # When first run or prompt not dismissed, wait for load and prompt user @@ -50,6 +53,7 @@ def start_mall_parser_intent(self, message): self._start_mall_parser_prompt(message) return + # @property def mall_link(self): mall_link = 'https://www.alamoanacenter.com/' return self.settings.get("mall_link") or mall_link @@ -74,34 +78,64 @@ def start_again(self): start_again = self.ask_yesno("ask_more") if start_again == "yes": another_shop = self.get_response('another_shop') - LOG.info('another shop'+another_shop) - return another_shop + if another_shop is not None: + LOG.info('another shop'+another_shop) + return another_shop + else: + return None elif start_again == "no": + self.speak_dialog('no_shop_request') return None else: self.speak_dialog('unexpected_error') return None + def speak_shops(self, shop_info): + for shop in shop_info: + LOG.info(shop) + location = self.request_handler.location_format(shop['location']) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": shop['hours'], "location": location}) + self.gui.show_image(shop['logo']) + return 3, None + + def more_than_one(self, shop_info): + self.speak_dialog('more_than_one') + speak_all_shops = self.ask_yesno('speak_all_shops') + if speak_all_shops == 'yes': + self.speak_dialog('all_locations') + return self.speak_shops(shop_info) + else: + shop_by_floor = self.ask_yesno('shop_by_floor') + if shop_by_floor == 'yes': + floor = self.get_response('which_floor') + shop = self.request_handler.shop_selection_by_floors(floor, shop_info) + if shop is not None: + return self.speak_shops([shop]) + else: + self.speak_dialog('no_shop_on_level') + return self.speak_shops(shop_info) + else: + self.speak_dialog('all_locations') + return self.speak_shops(shop_info) + def find_shop(self, user_request, mall_link): + LOG.info(str(user_request)) + LOG.info(str(mall_link)) if user_request is not None: - request_handler = RequestHandler() self.speak_dialog(f"I am parsing shops and malls for your request") - shop_info = request_handler.get_shop_data(mall_link, user_request) + LOG.info(f"I am parsing shops and malls for your request") + shop_info = self.request_handler.get_shop_data(mall_link, user_request, self.cache) if len(shop_info) == 0: self.speak_dialog("shop_not_found") user_request = self.get_response('repeat') return 1, user_request elif len(shop_info) > 1: - self.speak_dialog('more_than_one') - for shop in shop_info: - shop_str = 'found shop %s work hours are %s you can find this store on %s' % (shop['name'], shop['hours'], shop['location']) - self.speak(f"found shop {shop['name']} work hours are {shop['hours']} you can find this store on {shop['location']}") - return 3, None + return self.more_than_one(shop_info) else: LOG.info(f"found shop {shop_info}") - self.speak(f"found shop {shop_info[0]['name']} work hours are {shop_info[0]['hours']} you can find this store on {shop_info[0]['location']}") - return 3, None + return self.speak_shops(shop_info) else: + LOG.info(str(None)) return 3, None def execute(self, user_request, mall_link): diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index d13f3d10f9d5270b83d1797817a27ffab8efcfe6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4106 zcmZ`+&5zs073c6nqO_9sv$i*J9JNW4sLg6qqb<C46wD`?8*fz?}kj$N&uDC=51nkbX^?6NUXS$c31CrRo2>Cc~H za63+OmFD4LoT%_PN@b$-b5Skqd{Hg@L)AT`{jNXGtYIW7$_j+E0{a{5P)H%|Cw|~a zN4hwVt32tQi|VZBBB;&IS(i<@hH+oEWEL;=3ld-AKZr@Dpzy-UZkQQQ#Io6BPK=W}0@hcNOnEqohAVLh}ipxsNMH&C}P9BJe0UfP6_ z1AZZ0us+t=Xq<;x8k#)P`LsPc9qQ~b?`svw)1FuQM&)^&9+}cPQhDh-n3&Sd(xEDM zt0f@`6jt(t)poj#Bk0XVv4vXMBwfW(5>M67&^9#`Eqh0}|C{cv$apn#dP7gr>woB1 zC>)Y@<`31$k}UmesN|5n5D)E3*s-uKoC12N{#&e|>t})W$s_i$Y9>w7cE$AF6=`0zZpng6GVEYSDb9dMwj$&hHwwXc zoW6OS=VSBX{{G{~kNZ&)jj||>hB$`O{b4rRFB@!+Bu-Bt(KdF{wB1ZPeTO=hEyI1k zM!zAwxMD+0Z>@;1*m6nlr1J&pK0pJy#%lh=h@Z;cpTbxt99nCYycz8fhKyJu6B=8nAFN^ye))U+#*o=vu_bmy zlMITI4BHY-8@2zq_37Iy2L8dVhNOu3L--(4qCRs6IxG;TCPF{Wt#dLb8J!JQI2Qmo zff}ZD3Ks#3!2S-;U~rSU2nc6FdW9gf&E|P0t{zt5t#CiF&c&IBxnAMHkoMoKryyqh zG2+nJK*08AvuX^9S_Pk?CY!r}l3YWrwWzhJ_Puvj$Bur%5Ro1H8DNs@g}Z>NEA3rt zu|w6G?^3m1u@lR;LC}+%0NU1Iy7$L!H0l!f!!BKdmgyn|=yHtf9%j0GnoV?fZeGG= z(4lEznjw?OCaKi#p;MFIU*bdJmIJ(o27q&jx~LM1s~uY8Pd`E_mXTFCFVXV^Myg17%I$>_8?TM zmrhCyK~&4I)uJx6r0hZMhYYH3BM9@a!fHXx_KfgM+{sqQE+LI1^+tQRF zz7XBgJBCU@lyu5G&QdTtXIcAn^ zqT1>t*z$@4$o;{&J-_nfJ=&E*NU%Ql;#npbG7twkAq9N zS&4j?c4m7u7MAz|eWs7X7d1F|i`<(Xy#SeTFRcim5o)-R7XrWHlf8oqiJsMU>35 z@l4;rdWx$Mpt^`sBp64?a%~iys4yDl@mEU!n7Z##!8UNOYyia54>?K3SttZjz{Q>v zP*<`s%0>T**H8?u@J_*vP~No35QK5}U(@$iOz_HVyw{)V!*hn1v-A+^893yo6oA3@yQuaA&%gGwEBLmCxU}Rd0iuU=;-$ mOB@KpR!mEI1po2}59!~l^nafuG<4f*y+MgfOKiBUqyGb)*~x$a diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc deleted file mode 100644 index a35a46108242e0c46acb9753d90edcc799f74bc7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1975 zcma)7OK&7K5VpJBkD2M2WS2nLWwnHdghnVcS^*bWAzorZLW4wBTG0};I%6l(Nl*90 zc5gPLo>Ou~`~ZYBIl?dDH~7jaKY&XPgep(6dF&o=tIKwk?W+FjD|gv$Hwm;`pZv3V z6cO?_POdf=CZEBpc0h2#X->v8cIaa4#;(<|m_hI4-q?@*b6XGx))vO0?Hl7}+&Z^K zaRfRyZ;v~1hmuExGwvM`?j1Vu5~mxa>z{x>GU!s{e)s(&=okVCXwZO>!u zfmV>1auPed#a+&h7)0oC|A;_@0S}?~c!M{g_qhXpBGdUn>{UYPZ_i(7upvH#K}~D23K?4^ zGe?s-t({{!b8C0U;m)ojSG7B5+&!j{6ShI@%(F8eJ}nqbTYa8MG1R3z=%4%r2Tt!k z9%X7RPmAp;-4<&wR@G!uN-g-~>HQasMX4_reBJd-c>cx8xZ6UTmeQ%NR5C2NkoYNv zWyMz6WpSedloE7g+)Cz#0X)07T}{dYOwX~FA#Qs`Jj;{@1nrh&I1@UduJ=obma!lcIgladeIs%c#iC! zFxe)6BoY;{1$3MpASjOK-Z7am?azan0q)MstG%7jqVMYlmN_85Gz;or-qfwyKeYQ_ z!2ZEC?9q?E6XE5MQ6Bta_rSUBOU+m0r^YPAh~U(4aq8Z;wF_Aa2f)c^2dvX}XO?B5mR$TTSPV z;?`DK!6G#(CrZ8nXT(gw>2ZYVhFY{P48yd;%?7J(R$Cit*KSXEs?)dN1cmW1r%9Kc zXsHPtSOyNf=C8QGi1zf}4JKUM-gP8a*q|HW)rUMw64OkQamg!Sbz-6<*{jlgVUfrg zc^k!j6zeE1F9SP5N=dv7TktCUyM?6bgl^c30;}&Xo6Z9L-Z>`nU07u^04e!E;zs!% zs_qq{ED}gTu6m1myk|9bY_l2rYADmmfqWCz#|`Afq6*of7W0vO2Q4A|BSeu^V~HhT e<9J!%CVaT~@=uXiY6V0@LK*_57c!!^o&NxN67dEA diff --git a/locale/en-us/all_locations.dialog b/locale/en-us/all_locations.dialog new file mode 100644 index 0000000..fbffb2a --- /dev/null +++ b/locale/en-us/all_locations.dialog @@ -0,0 +1 @@ +Here are all possible shop locations. \ No newline at end of file diff --git a/locale/en-us/found_shop.dialog b/locale/en-us/found_shop.dialog new file mode 100644 index 0000000..eac5098 --- /dev/null +++ b/locale/en-us/found_shop.dialog @@ -0,0 +1 @@ +found shop {{name}} work hours are {{hours}} you can find this store on {{location}} \ No newline at end of file diff --git a/locale/en-us/no_shop_on_level.dialog b/locale/en-us/no_shop_on_level.dialog new file mode 100644 index 0000000..69b5d6d --- /dev/null +++ b/locale/en-us/no_shop_on_level.dialog @@ -0,0 +1 @@ +There is no requested shop on your level. Here are all possible locations. \ No newline at end of file diff --git a/locale/en-us/no_shop_request.dialog b/locale/en-us/no_shop_request.dialog new file mode 100644 index 0000000..c2c3b38 --- /dev/null +++ b/locale/en-us/no_shop_request.dialog @@ -0,0 +1 @@ +Ok. I stop mall parsing \ No newline at end of file diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent index d3bc909..6ea1597 100644 --- a/locale/en-us/run_mall_parser.intent +++ b/locale/en-us/run_mall_parser.intent @@ -1 +1,7 @@ -where (is|are|can i find) {shop} \ No newline at end of file +where (is|are|can i find) {shop} +where (is|are) {shop} located +i am looking for {shop} +location of {shop} +where {shop} +show {shop} +show me {shop} \ No newline at end of file diff --git a/locale/en-us/shop_by_floor.dialog b/locale/en-us/shop_by_floor.dialog new file mode 100644 index 0000000..97c1ac0 --- /dev/null +++ b/locale/en-us/shop_by_floor.dialog @@ -0,0 +1 @@ +Do you want to get shop info by floor? \ No newline at end of file diff --git a/locale/en-us/speak_all_shops.dialog b/locale/en-us/speak_all_shops.dialog new file mode 100644 index 0000000..9038294 --- /dev/null +++ b/locale/en-us/speak_all_shops.dialog @@ -0,0 +1 @@ +Do you want me to speak all found shops? \ No newline at end of file diff --git a/locale/en-us/start_parsing.dialog b/locale/en-us/start_parsing.dialog new file mode 100644 index 0000000..ca5ad76 --- /dev/null +++ b/locale/en-us/start_parsing.dialog @@ -0,0 +1 @@ +I am parsing shops and malls for your request \ No newline at end of file diff --git a/locale/en-us/stop.dialog b/locale/en-us/stop.dialog index 7510b94..2874c27 100644 --- a/locale/en-us/stop.dialog +++ b/locale/en-us/stop.dialog @@ -1 +1 @@ -Do you want me to stop mall parsing? \ No newline at end of file +Do you want to exit the mall directory? \ No newline at end of file diff --git a/locale/en-us/unexpected_error.dialog b/locale/en-us/unexpected_error.dialog index b9b134c..f3d2c27 100644 --- a/locale/en-us/unexpected_error.dialog +++ b/locale/en-us/unexpected_error.dialog @@ -1 +1 @@ -Unexpected input \ No newline at end of file +Unexpected answer \ No newline at end of file diff --git a/locale/en-us/what_shop.dialog b/locale/en-us/what_shop.dialog deleted file mode 100644 index f877003..0000000 --- a/locale/en-us/what_shop.dialog +++ /dev/null @@ -1 +0,0 @@ -What shop are you looking for? \ No newline at end of file diff --git a/locale/en-us/which_floor.dialog b/locale/en-us/which_floor.dialog new file mode 100644 index 0000000..557cf91 --- /dev/null +++ b/locale/en-us/which_floor.dialog @@ -0,0 +1,3 @@ +Which floor are you staying on? +On which floor you are? +what is your floor? \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 8ba5ace..a6ae61e 100644 --- a/request_handling.py +++ b/request_handling.py @@ -2,13 +2,20 @@ import requests import bs4 from neon_utils.skills.neon_skill import LOG -import urllib import urllib.request +import lingua_franca +from lingua_franca.format import pronounce_number +lingua_franca.load_language('en') + +import re + + class RequestHandler(): def __init__(self) -> None: pass + def existing_lang_check(user_lang, url): link = url+user_lang+'/directory/' @@ -33,22 +40,42 @@ def parse(self, url): return soup except HTTPError: LOG.info("Failed url parsing") - - def get_shop_data(self, url, user_request): + def location_format(self, location): + floor = re.findall(r'\d+', location) + if len(floor) > 0: + floor = floor[0] + num = pronounce_number(int(floor), ordinals=False) + pronounced = re.sub(r'\d+', num, location) + return pronounced + else: + location + + + def get_shop_data(self, url, user_request, cache): found_shops = [] soup = self.parse(url) for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): logo = shop.find_next("img").get('src') info = shop.find_next(attrs={"class": "tenant-info-container"}) name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') - if name.lower() in user_request.lower(): + if name.lower() in user_request.lower() or user_request.lower() in name.lower(): hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} found_shops.append(shop_data) return found_shops - # def image_extraction(): - # # Todo extract image from web-pade - # ... + + def shop_selection_by_floors(self, user_request, found_shops): + for shop in found_shops: + numbers = re.findall(r'\d+', shop['location']) + if len(numbers) > 0: + numbers = numbers[0] + num = pronounce_number(int(numbers), ordinals=False) + num_ordinal = pronounce_number(int(numbers), ordinals=True) + if num in user_request or num_ordinal in user_request: + return shop + else: + None + diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 45ac700..16fb42a 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,5 +1,6 @@ numpy -neon_utils>=0.17,<2.0.0 +neon-utils~=1.0 bs4 urllib requests +ovos-lingua-franca \ No newline at end of file diff --git a/test/mall_parsing_test_skill.py b/test/mall_parsing_test_skill.py index 51fb2d6..9971c56 100644 --- a/test/mall_parsing_test_skill.py +++ b/test/mall_parsing_test_skill.py @@ -90,7 +90,6 @@ def test_en_skill_init(self): 'lang': 'en-us' }, {'context_key': 'MallParsing'})) - self.skill.execute.assert_called_once() message = Message('test', {'utterance': 'where is apple', 'shop': 'apple', @@ -99,10 +98,10 @@ def test_en_skill_init(self): self.skill.user_request_handling(message) #self.skill.ask_yesno.assert_called_once_with("start mall parsing?") - self.skill.execute(message) - self.skill.execute = real_execute - self.skill.ask_yesno = real_askyesno - self.skill.user_request_handling = real_user_request_handling + # self.skill.execute(message) + # self.skill.execute = real_execute + # self.skill.ask_yesno = real_askyesno + # self.skill.user_request_handling = real_user_request_handling From 49387dd182f0bce4be1358bc4a230275a150cd07 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sat, 20 Aug 2022 13:06:38 -0400 Subject: [PATCH 07/21] github automation, version, readme update, minor fixes --- .github/workflows/publish_release.yml | 40 +++++++ .github/workflows/publish_test_build.yml | 39 +++++++ .github/workflows/pull_master.yml | 19 ++++ .github/workflows/skill_tests.yml | 67 ++++++++++++ .github/workflows/update_skill_json.yml | 33 ++++++ LICENSE.md | 21 ++++ README.md | 26 ++++- __init__.py | 2 +- locale/en-us/which_floor.dialog | 2 +- settingsmeta.yml | 9 ++ setup.py | 100 ++++++++++++++++++ skill.json | 54 ++++++++++ ...ll_parsing_test_skill.py => test_skill.py} | 11 -- version.py | 29 +++++ version_bump.py | 54 ++++++++++ 15 files changed, 491 insertions(+), 15 deletions(-) create mode 100644 .github/workflows/publish_release.yml create mode 100644 .github/workflows/publish_test_build.yml create mode 100644 .github/workflows/pull_master.yml create mode 100644 .github/workflows/skill_tests.yml create mode 100644 .github/workflows/update_skill_json.yml create mode 100644 LICENSE.md create mode 100644 settingsmeta.yml create mode 100644 setup.py create mode 100644 skill.json rename test/{mall_parsing_test_skill.py => test_skill.py} (88%) create mode 100644 version.py create mode 100644 version_bump.py diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml new file mode 100644 index 0000000..12daf2c --- /dev/null +++ b/.github/workflows/publish_release.yml @@ -0,0 +1,40 @@ +# This workflow will generate a release distribution and upload it to PyPI + +name: Publish Build and GitHub Release +on: + push: + branches: + - master + +jobs: + tag_release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Get Version + run: | + VERSION=$(python setup.py --version) + echo "VERSION=${VERSION}" >> $GITHUB_ENV + - uses: ncipollo/release-action@v1 + with: + token: ${{secrets.GITHUB_TOKEN}} + tag: ${{env.VERSION}} + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/publish_test_build.yml b/.github/workflows/publish_test_build.yml new file mode 100644 index 0000000..33c3b4c --- /dev/null +++ b/.github/workflows/publish_test_build.yml @@ -0,0 +1,39 @@ +# This workflow will generate a distribution and upload it to PyPI + +name: Publish Alpha Build +on: + push: + branches: + - dev + paths-ignore: + - 'version.py' + +jobs: + build_and_publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + ref: ${{ github.head_ref }} + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Increment Version + run: | + VER=$(python setup.py --version) + python version_bump.py + - name: Push Version Change + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Increment Version + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{secrets.PYPI_TOKEN}} \ No newline at end of file diff --git a/.github/workflows/pull_master.yml b/.github/workflows/pull_master.yml new file mode 100644 index 0000000..d62646b --- /dev/null +++ b/.github/workflows/pull_master.yml @@ -0,0 +1,19 @@ +# This workflow will generate a PR for changes in dev into master for a skill + +name: Pull to Master +on: + push: + branches: + - dev + workflow_dispatch: + +jobs: + pull_changes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: pull-request-action + uses: repo-sync/pull-request@v2 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + pr_reviewer: 'neonreviewers' \ No newline at end of file diff --git a/.github/workflows/skill_tests.yml b/.github/workflows/skill_tests.yml new file mode 100644 index 0000000..f03c60b --- /dev/null +++ b/.github/workflows/skill_tests.yml @@ -0,0 +1,67 @@ +# This workflow will run unit tests + +name: Test Skill +on: + pull_request: + workflow_dispatch: + +jobs: + build_tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Setup Python + uses: actions/setup-python@v1 + with: + python-version: 3.8 + - name: Install Build Tools + run: | + python -m pip install build wheel + - name: Build Distribution Packages + run: | + python setup.py bdist_wheel + neon_core: + strategy: + matrix: + python-version: [ 3.7, 3.8, 3.9, '3.10' ] + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v2 + - name: Set up python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + sudo apt update + sudo apt install -y gcc libfann-dev swig libssl-dev portaudio19-dev git libpulse-dev + pip install --upgrade pip + pip install pytest mock git+https://github.com/NeonGeckoCom/NeonCore#egg=neon_core + pip install -r requirements/requirements.txt + pip install -r requirements/test_requirements.txt + - name: Test Skill + run: | + pytest test/test_skill.py + ovos-core: + strategy: + matrix: + python-version: [ 3.8 ] + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v2 + - name: Set up python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install Dependencies + run: | + sudo apt install -y gcc libfann-dev swig libssl-dev portaudio19-dev git + pip install --upgrade pip + pip install ovos-core[skills] pytest mock + pip install -r requirements/requirements.txt + pip install -r requirements/test_requirements.txt + - name: Test Skill + run: | + pytest test/test_skill.py \ No newline at end of file diff --git a/.github/workflows/update_skill_json.yml b/.github/workflows/update_skill_json.yml new file mode 100644 index 0000000..5dc9797 --- /dev/null +++ b/.github/workflows/update_skill_json.yml @@ -0,0 +1,33 @@ +# This workflow will run unit tests + +name: Update skill.json +on: + push: + +jobs: + update_skill_json: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + with: + path: action/skill/ + - name: Set up python 3.8 + uses: actions/setup-python@v2 + with: + python-version: 3.8 + - name: Install Dependencies + run: | + sudo apt update + sudo apt install -y gcc git libpulse-dev + pip install --upgrade pip + pip install neon-utils\~=0.17 ovos-skills-manager + - name: Get Updated skill.json + run: | + python action/skill/scripts/update_skill_json.py + - name: Push skill.json Change + uses: stefanzweifel/git-auto-commit-action@v4 + with: + commit_message: Update skill.json + repository: action/skill/ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..cc28779 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# BSD-3 License + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +following conditions are met: +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following +disclaimer in the documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/README.md b/README.md index 2307d99..437cd73 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# malls-parser-skill -Skill for parsing store name and location or a requested product/store +# {title} +## Summary +Skill for mall parsing + +## Description +{Skill parses mall web page and returns user name, location and work hours of requested shop/store} + +## Examples +{Where is apple? Where can I find ABC stores?} + +## Contact Support +Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](https://help.github.com/en/articles/creating-an-issue) + +## Credits + +[NeonGeckoCom](https://github.com/NeonGeckoCom) +{author_name} + +## Category +**category** {categories} + +## Tags +#tag +#tag2 \ No newline at end of file diff --git a/__init__.py b/__init__.py index 4f9df4a..94494a0 100644 --- a/__init__.py +++ b/__init__.py @@ -79,7 +79,7 @@ def start_again(self): if start_again == "yes": another_shop = self.get_response('another_shop') if another_shop is not None: - LOG.info('another shop'+another_shop) + LOG.info(f'another shop {another_shop}') return another_shop else: return None diff --git a/locale/en-us/which_floor.dialog b/locale/en-us/which_floor.dialog index 557cf91..4333de5 100644 --- a/locale/en-us/which_floor.dialog +++ b/locale/en-us/which_floor.dialog @@ -1,3 +1,3 @@ Which floor are you staying on? -On which floor you are? +On which floor are you? what is your floor? \ No newline at end of file diff --git a/settingsmeta.yml b/settingsmeta.yml new file mode 100644 index 0000000..cb6b7e8 --- /dev/null +++ b/settingsmeta.yml @@ -0,0 +1,9 @@ + +skillMetadata: + sections: + - name: Mall Parsing + fields: + - name: prompt_on_start + type: checkbox + label: Start Mall Parsing + value: "true" \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..d77597b --- /dev/null +++ b/setup.py @@ -0,0 +1,100 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from setuptools import setup +from os import getenv, path, walk + +SKILL_NAME = "malls-parser-skill" +SKILL_PKG = SKILL_NAME.replace('-', '_') +# skill_id=package_name:SkillClass +PLUGIN_ENTRY_POINT = f'{SKILL_NAME}.neongeckocom={SKILL_PKG}:MallParsingSkill' + + +def get_requirements(requirements_filename: str): + requirements_file = path.join(path.abspath(path.dirname(__file__)), + requirements_filename) + with open(requirements_file, 'r', encoding='utf-8') as r: + requirements = r.readlines() + requirements = [r.strip() for r in requirements if r.strip() + and not r.strip().startswith("#")] + + for i in range(0, len(requirements)): + r = requirements[i] + if "@" in r: + parts = [p.lower() if p.strip().startswith("git+http") else p + for p in r.split('@')] + r = "@".join(parts) + if getenv("GITHUB_TOKEN"): + if "github.com" in r: + requirements[i] = \ + r.replace("github.com", + f"{getenv('GITHUB_TOKEN')}@github.com") + return requirements + + +def find_resource_files(): + resource_base_dirs = ("locale", "ui", "vocab", "dialog", "regex") + base_dir = path.dirname(__file__) + package_data = ["skill.json"] + for res in resource_base_dirs: + if path.isdir(path.join(base_dir, res)): + for (directory, _, files) in walk(path.join(base_dir, res)): + if files: + package_data.append( + path.join(directory.replace(base_dir, "").lstrip('/'), + '*')) + return package_data + + +with open("README.md", "r") as f: + long_description = f.read() + +with open("./version.py", "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith("__version__"): + if '"' in line: + version = line.split('"')[1] + else: + version = line.split("'")[1] + +setup( + name=f"neon-{SKILL_NAME}", + version=version, + url=f'https://github.com/NeonGeckoCom/{SKILL_NAME}', + license='BSD-3-Clause', + install_requires=get_requirements("requirements/requirements.txt"), + author='Neongecko', + author_email='developers@neon.ai', + long_description=long_description, + long_description_content_type="text/markdown", + package_dir={SKILL_PKG: ""}, + packages=[SKILL_PKG], + package_data={SKILL_PKG: find_resource_files()}, + include_package_data=True, + entry_points={"ovos.plugin.skill": PLUGIN_ENTRY_POINT} +) diff --git a/skill.json b/skill.json new file mode 100644 index 0000000..c1029f0 --- /dev/null +++ b/skill.json @@ -0,0 +1,54 @@ +{ + "title": "MallsParser", + "url": "https://github.com/NeonGeckoCom/malls-parser-skill", + "summary": "Neon Skill for mall parsing", + "short_description": "Neon Skill parses mall web page and returns store on user's request", + "description": "Parses mall web page and returns user name, location and work hours of requested shop/store", + "examples": [ + "where is Apple" + ], + "desktopFile": false, + "warning": "", + "systemDeps": false, + "requirements": { + "python": [ + "numpy", + "neon-utils~=1.0", + "bs4", + "urllib", + "requests", + "ovos-lingua-franca" + ], + "system": {}, + "skill": [] + }, + "incompatible_skills": [], + "platforms": [ + "i386", + "x86_64", + "ia64", + "arm64", + "arm" + ], + "branch": "master", + "license": "BSD-3-Clause", + "icon": "https://0000.us/klatchat/app/files/neon_images/icons/neon_skill.png", + "category": "Information", + "categories": [ + "Information", + "Daily" + ], + "tags": [ + "Neongecko", + "Neon", + "MallsParser", + "Custom" + ], + "credits": [ + "NeonGeckoCom", + "NeonMariia" + ], + "skillname": "malls-parser-skill", + "authorname": "NeonGeckoCom", + "foldername": null +} \ No newline at end of file diff --git a/test/mall_parsing_test_skill.py b/test/test_skill.py similarity index 88% rename from test/mall_parsing_test_skill.py rename to test/test_skill.py index 9971c56..9144380 100644 --- a/test/mall_parsing_test_skill.py +++ b/test/test_skill.py @@ -28,7 +28,6 @@ import shutil import unittest -import pytest from os import mkdir from os.path import dirname, join, exists @@ -78,12 +77,7 @@ def tearDownClass(cls) -> None: shutil.rmtree(cls.test_fs) def test_en_skill_init(self): - real_askyesno = self.skill.ask_yesno - real_execute = self.skill.execute - real_user_request_handling = self.skill.user_request_handling self.skill.ask_yesno = Mock(return_value="yes") - #self.skill.execute = Mock() - #self.skill.user_request_handling = Mock() self.skill._start_mall_parser_prompt( Message('test', {'utterance': 'where is apple', 'shop': 'apple', @@ -97,11 +91,6 @@ def test_en_skill_init(self): {'context_key': 'MallParsing'}) self.skill.user_request_handling(message) - #self.skill.ask_yesno.assert_called_once_with("start mall parsing?") - # self.skill.execute(message) - # self.skill.execute = real_execute - # self.skill.ask_yesno = real_askyesno - # self.skill.user_request_handling = real_user_request_handling diff --git a/version.py b/version.py new file mode 100644 index 0000000..3d53a58 --- /dev/null +++ b/version.py @@ -0,0 +1,29 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +__version__ = "0.0.1a0" diff --git a/version_bump.py b/version_bump.py new file mode 100644 index 0000000..5a01c55 --- /dev/null +++ b/version_bump.py @@ -0,0 +1,54 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import fileinput +from os.path import join, dirname + +with open(join(dirname(__file__), "version.py"), "r", encoding="utf-8") as v: + for line in v.readlines(): + if line.startswith("__version__"): + if '"' in line: + version = line.split('"')[1] + else: + version = line.split("'")[1] + +if "a" not in version: + parts = version.split('.') + parts[-1] = str(int(parts[-1]) + 1) + version = '.'.join(parts) + version = f"{version}a0" +else: + post = version.split("a")[1] + new_post = int(post) + 1 + version = version.replace(f"a{post}", f"a{new_post}") + +for line in fileinput.input(join(dirname(__file__), "version.py"), inplace=True): + if line.startswith("__version__"): + print(f"__version__ = \"{version}\"") + else: + print(line.rstrip('\n')) From e7c3f5f173ea19a6f0fcfe939a035678b2135c33 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sat, 20 Aug 2022 13:11:06 -0400 Subject: [PATCH 08/21] scripts directory --- scripts/update_skill_json.py | 57 ++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 scripts/update_skill_json.py diff --git a/scripts/update_skill_json.py b/scripts/update_skill_json.py new file mode 100644 index 0000000..9e56817 --- /dev/null +++ b/scripts/update_skill_json.py @@ -0,0 +1,57 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Framework +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2022 Neongecko.com Inc. +# Contributors: Daniel McKnight, Guy Daniels, Elon Gasper, Richard Leeds, +# Regina Bloomstine, Casimiro Ferreira, Andrii Pernatii, Kirill Hrymailo +# BSD-3 License +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import json +from os.path import dirname, join +from pprint import pprint +from neon_utils.packaging_utils import build_skill_spec + +skill_dir = dirname(dirname(__file__)) + + +def get_skill_json(): + print(f"skill_dir={skill_dir}") + skill_json = join(skill_dir, "skill.json") + skill_spec = build_skill_spec(skill_dir) + pprint(skill_spec) + try: + with open(skill_json) as f: + current = json.load(f) + except Exception as e: + print(e) + current = None + if current != skill_spec: + print("Skill Updated. Writing skill.json") + with open(skill_json, 'w+') as f: + json.dump(skill_spec, f, indent=4) + else: + print("No changes to skill.json") + + +if __name__ == "__main__": + get_skill_json() From 4da50cd3b76dcd8cdada6321fe709ef34f96bc41 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sat, 20 Aug 2022 17:12:18 +0000 Subject: [PATCH 09/21] Update skill.json --- skill.json | 33 ++++++++++++++------------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/skill.json b/skill.json index c1029f0..aa2aa42 100644 --- a/skill.json +++ b/skill.json @@ -1,23 +1,21 @@ { - "title": "MallsParser", + "title": "{title}", "url": "https://github.com/NeonGeckoCom/malls-parser-skill", - "summary": "Neon Skill for mall parsing", - "short_description": "Neon Skill parses mall web page and returns store on user's request", - "description": "Parses mall web page and returns user name, location and work hours of requested shop/store", - "examples": [ - "where is Apple" - ], + "summary": "Skill for mall parsing", + "short_description": "Skill for mall parsing", + "description": "{Skill parses mall web page and returns user name, location and work hours of requested shop/store}", + "examples": [], "desktopFile": false, "warning": "", "systemDeps": false, "requirements": { "python": [ - "numpy", - "neon-utils~=1.0", "bs4", - "urllib", + "neon-utils~=1.0", + "numpy", + "ovos-lingua-franca", "requests", - "ovos-lingua-franca" + "urllib" ], "system": {}, "skill": [] @@ -33,20 +31,17 @@ "branch": "master", "license": "BSD-3-Clause", "icon": "https://0000.us/klatchat/app/files/neon_images/icons/neon_skill.png", - "category": "Information", + "category": "category** {categories}", "categories": [ - "Information", - "Daily" + "category** {categories}" ], "tags": [ - "Neongecko", - "Neon", - "MallsParser", - "Custom" + "tag", + "tag2" ], "credits": [ "NeonGeckoCom", - "NeonMariia" + "{author_name}" ], "skillname": "malls-parser-skill", "authorname": "NeonGeckoCom", From a12c5e93200d29372422efdc3c1447c318016953 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sat, 20 Aug 2022 13:14:31 -0400 Subject: [PATCH 10/21] urllib requirement removal --- requirements/requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 16fb42a..1de0bd9 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,6 +1,5 @@ numpy neon-utils~=1.0 bs4 -urllib requests ovos-lingua-franca \ No newline at end of file From 9dd577205b6d2b7909e5546019f90baa17570be5 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Sat, 20 Aug 2022 17:16:20 +0000 Subject: [PATCH 11/21] Update skill.json --- skill.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skill.json b/skill.json index aa2aa42..4a5af7b 100644 --- a/skill.json +++ b/skill.json @@ -14,8 +14,7 @@ "neon-utils~=1.0", "numpy", "ovos-lingua-franca", - "requests", - "urllib" + "requests" ], "system": {}, "skill": [] From 2f26e594fcd49c6790a7598d057a3ce4d579ce0b Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 22 Aug 2022 13:52:07 -0400 Subject: [PATCH 12/21] gui test fixes --- __init__.py | 2 +- test/test_skill.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/__init__.py b/__init__.py index 94494a0..3f1682f 100644 --- a/__init__.py +++ b/__init__.py @@ -95,7 +95,7 @@ def speak_shops(self, shop_info): LOG.info(shop) location = self.request_handler.location_format(shop['location']) self.speak_dialog('found_shop', {"name": shop['name'], "hours": shop['hours'], "location": location}) - self.gui.show_image(shop['logo']) + # self.gui.show_image(shop['logo']) return 3, None def more_than_one(self, shop_info): diff --git a/test/test_skill.py b/test/test_skill.py index 9144380..074e11f 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -78,6 +78,7 @@ def tearDownClass(cls) -> None: def test_en_skill_init(self): self.skill.ask_yesno = Mock(return_value="yes") + self.skill.gui._pages2uri = Mock() self.skill._start_mall_parser_prompt( Message('test', {'utterance': 'where is apple', 'shop': 'apple', From eebd273d0d99ae3a29f21a1fb4732d5134443145 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 25 Aug 2022 05:13:40 -0400 Subject: [PATCH 13/21] readme and dialogs fixes --- README.md | 5 +++-- locale/en-us/found_shop.dialog | 2 +- locale/en-us/no_shop_request.dialog | 2 +- locale/en-us/start_parsing.dialog | 2 +- locale/en-us/unexpected_error.dialog | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 437cd73..59b03c1 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ Skill for mall parsing {Skill parses mall web page and returns user name, location and work hours of requested shop/store} ## Examples -{Where is apple? Where can I find ABC stores?} +{- where is apple? +- where can I find ABC stores?} ## Contact Support Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](https://help.github.com/en/articles/creating-an-issue) @@ -17,7 +18,7 @@ Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](h {author_name} ## Category -**category** {categories} +**Information** {categories} ## Tags #tag diff --git a/locale/en-us/found_shop.dialog b/locale/en-us/found_shop.dialog index eac5098..18d51bc 100644 --- a/locale/en-us/found_shop.dialog +++ b/locale/en-us/found_shop.dialog @@ -1 +1 @@ -found shop {{name}} work hours are {{hours}} you can find this store on {{location}} \ No newline at end of file +I found shop {{name}} with hours of {{hours}}. You can find this store on {{location}}. \ No newline at end of file diff --git a/locale/en-us/no_shop_request.dialog b/locale/en-us/no_shop_request.dialog index c2c3b38..6ca56f4 100644 --- a/locale/en-us/no_shop_request.dialog +++ b/locale/en-us/no_shop_request.dialog @@ -1 +1 @@ -Ok. I stop mall parsing \ No newline at end of file +Okay. I will stop mall parsing. \ No newline at end of file diff --git a/locale/en-us/start_parsing.dialog b/locale/en-us/start_parsing.dialog index ca5ad76..4071f9b 100644 --- a/locale/en-us/start_parsing.dialog +++ b/locale/en-us/start_parsing.dialog @@ -1 +1 @@ -I am parsing shops and malls for your request \ No newline at end of file +I am parsing shops and malls for your request. \ No newline at end of file diff --git a/locale/en-us/unexpected_error.dialog b/locale/en-us/unexpected_error.dialog index f3d2c27..aed994a 100644 --- a/locale/en-us/unexpected_error.dialog +++ b/locale/en-us/unexpected_error.dialog @@ -1 +1 @@ -Unexpected answer \ No newline at end of file +Sorry, I'm not sure what you mean. \ No newline at end of file From ec66a10d66aa0ef8e7ccf448956309eccf505df1 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 25 Aug 2022 09:15:04 +0000 Subject: [PATCH 14/21] Update skill.json --- skill.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skill.json b/skill.json index 4a5af7b..9a9df75 100644 --- a/skill.json +++ b/skill.json @@ -4,7 +4,9 @@ "summary": "Skill for mall parsing", "short_description": "Skill for mall parsing", "description": "{Skill parses mall web page and returns user name, location and work hours of requested shop/store}", - "examples": [], + "examples": [ + "where can i find abc stores?}" + ], "desktopFile": false, "warning": "", "systemDeps": false, @@ -30,9 +32,9 @@ "branch": "master", "license": "BSD-3-Clause", "icon": "https://0000.us/klatchat/app/files/neon_images/icons/neon_skill.png", - "category": "category** {categories}", + "category": "Information** {categories}", "categories": [ - "category** {categories}" + "Information** {categories}" ], "tags": [ "tag", From 6011a15aade51c96a19eeb0b5659e60ab8ed8a61 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 25 Aug 2022 05:16:50 -0400 Subject: [PATCH 15/21] readme fixes --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 59b03c1..f6a7a30 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Skill for mall parsing ## Description -{Skill parses mall web page and returns user name, location and work hours of requested shop/store} +{Skill parses mall web page and returns user name, location and work hours of requested shop, store} ## Examples {- where is apple? @@ -15,7 +15,7 @@ Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](h ## Credits [NeonGeckoCom](https://github.com/NeonGeckoCom) -{author_name} +[NeonMariia](https://github.com/neonmariia) ## Category **Information** {categories} From e4b0666c3f913a41830b59a2aab3b937a90ba423 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 25 Aug 2022 09:18:06 +0000 Subject: [PATCH 16/21] Update skill.json --- skill.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skill.json b/skill.json index 9a9df75..bf1dd65 100644 --- a/skill.json +++ b/skill.json @@ -3,7 +3,7 @@ "url": "https://github.com/NeonGeckoCom/malls-parser-skill", "summary": "Skill for mall parsing", "short_description": "Skill for mall parsing", - "description": "{Skill parses mall web page and returns user name, location and work hours of requested shop/store}", + "description": "{Skill parses mall web page and returns user name, location and work hours of requested shop, store}", "examples": [ "where can i find abc stores?}" ], @@ -42,7 +42,7 @@ ], "credits": [ "NeonGeckoCom", - "{author_name}" + "NeonMariia" ], "skillname": "malls-parser-skill", "authorname": "NeonGeckoCom", From 4017c896165ba78dd2b6e87b1a59c974995e938d Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Fri, 26 Aug 2022 05:33:22 -0400 Subject: [PATCH 17/21] readme update --- README.md | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index f6a7a30..eed73de 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# {title} +# {Malls parser skill neon} ## Summary Skill for mall parsing ## Description -{Skill parses mall web page and returns user name, location and work hours of requested shop, store} +Skill parses mall web page and returns user name, location and work hours of requested shop, store ## Examples -{- where is apple? -- where can I find ABC stores?} +- where is apple? +- where can I find ABC stores? ## Contact Support Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](https://help.github.com/en/articles/creating-an-issue) @@ -18,8 +18,4 @@ Use the [link](https://neongecko.com/ContactUs) or [submit an issue on GitHub](h [NeonMariia](https://github.com/neonmariia) ## Category -**Information** {categories} - -## Tags -#tag -#tag2 \ No newline at end of file +**Information** From 3bc7f29bd99986f3ab2c59ea14a6ffcb5001eee0 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Fri, 26 Aug 2022 09:37:48 +0000 Subject: [PATCH 18/21] Update skill.json --- skill.json | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/skill.json b/skill.json index bf1dd65..b7713c3 100644 --- a/skill.json +++ b/skill.json @@ -1,11 +1,12 @@ { - "title": "{title}", + "title": "{Malls parser skill neon}", "url": "https://github.com/NeonGeckoCom/malls-parser-skill", "summary": "Skill for mall parsing", "short_description": "Skill for mall parsing", - "description": "{Skill parses mall web page and returns user name, location and work hours of requested shop, store}", + "description": "Skill parses mall web page and returns user name, location and work hours of requested shop, store", "examples": [ - "where can i find abc stores?}" + "where is apple?", + "where can i find abc stores?" ], "desktopFile": false, "warning": "", @@ -32,14 +33,11 @@ "branch": "master", "license": "BSD-3-Clause", "icon": "https://0000.us/klatchat/app/files/neon_images/icons/neon_skill.png", - "category": "Information** {categories}", + "category": "Information", "categories": [ - "Information** {categories}" - ], - "tags": [ - "tag", - "tag2" + "Information" ], + "tags": [], "credits": [ "NeonGeckoCom", "NeonMariia" From 277dc53ead1493ebdaaccc0e16fca896c6c80a47 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 29 Aug 2022 12:46:35 -0400 Subject: [PATCH 19/21] time refactoring --- __init__.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/__init__.py b/__init__.py index 3f1682f..7aa28aa 100644 --- a/__init__.py +++ b/__init__.py @@ -30,6 +30,7 @@ from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler +import re @@ -94,8 +95,10 @@ def speak_shops(self, shop_info): for shop in shop_info: LOG.info(shop) location = self.request_handler.location_format(shop['location']) - self.speak_dialog('found_shop', {"name": shop['name'], "hours": shop['hours'], "location": location}) - # self.gui.show_image(shop['logo']) + hours = re.sub('(\d+)am(.+\d)pm', r'\1 A M\2 P M', shop['hours']) + self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) + print({"name": shop['name'], "hours": hours, "location": location}) + #self.gui.show_image(shop['logo']) return 3, None def more_than_one(self, shop_info): From dcded11fe52d77a759cd1e7c6e20e4b7e51294c1 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 14 Sep 2022 14:36:47 -0400 Subject: [PATCH 20/21] initiall time checker implementation --- __init__.py | 59 +++++++++-- __pycache__/__init__.cpython-310.pyc | Bin 0 -> 4661 bytes __pycache__/request_handling.cpython-310.pyc | Bin 0 -> 4026 bytes hashed_stores.txt | 1 + locale/en-us/data/saved_shops.pickle | 0 request_handling.py | 97 ++++++++++++++++--- testing.py | 57 +++++++++++ 7 files changed, 192 insertions(+), 22 deletions(-) create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/request_handling.cpython-310.pyc create mode 100644 hashed_stores.txt create mode 100644 locale/en-us/data/saved_shops.pickle create mode 100644 testing.py diff --git a/__init__.py b/__init__.py index 7aa28aa..d7c7e8c 100644 --- a/__init__.py +++ b/__init__.py @@ -26,12 +26,12 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import re -from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler -from .request_handling import RequestHandler -import re +from neon_utils.skills.neon_skill import LOG, NeonSkill +from .request_handling import RequestHandler class DirectorySkill(NeonSkill): @@ -39,7 +39,7 @@ class DirectorySkill(NeonSkill): def __init__(self): super(DirectorySkill, self).__init__(name="DirectorySkill") self.request_handler = RequestHandler() - self.cache = dict() + self.from_hashed_stores = [] def initialize(self): @@ -95,9 +95,11 @@ def speak_shops(self, shop_info): for shop in shop_info: LOG.info(shop) location = self.request_handler.location_format(shop['location']) + #add pronounce_number hours = re.sub('(\d+)am(.+\d)pm', r'\1 A M\2 P M', shop['hours']) self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) - print({"name": shop['name'], "hours": hours, "location": location}) + self.speak_dialog({"name": shop['name'], "hours": hours, "location": location}) + self.time_calculation(self, hours, shop['name']) #self.gui.show_image(shop['logo']) return 3, None @@ -121,13 +123,53 @@ def more_than_one(self, shop_info): self.speak_dialog('all_locations') return self.speak_shops(shop_info) + def time_calculation(self, work_time, shop_name): + # add logic if shop opens and closes not at am-pm time period + """ + Calculates time difference between user's current time + and shop working hours. Returns certain prompt. + If user is before opening speaks how much time + is left for waiting. + If shop closes in one hour speaks how many minutes left. + If user in open hours, tells that shop is open. + If user after closing hour, tells that shop is closed, + tells opening time. + Args: + work_time (str): work hours from found shop data + shop_name (str): shop name from found shop data + + Examples: + work time 9am-10pm + user's time 8am + Ptompt: 'Shop is closed now. Opens in 1 hour' + """ + day_time, hour, min = self.request_handler.curent_time_extraction() + parse_time = work_time.split('-') + open_time = int(re.sub('[^\d+]', '',parse_time[0])) + close_time = int(re.sub('[^\d+]', '',parse_time[1])) + # time left + wait_h = open_time-hour-1 + wait_min = 60-min + if day_time[1]=='am' and houropen_time and hour=close_time: + self.speak(f'{shop_name} is closed now. Shop opens at {open_time}') + else: + # change this else variant + self.speak_dialog(f'{shop_name} is open now.') + + def find_shop(self, user_request, mall_link): LOG.info(str(user_request)) LOG.info(str(mall_link)) if user_request is not None: - self.speak_dialog(f"I am parsing shops and malls for your request") - LOG.info(f"I am parsing shops and malls for your request") - shop_info = self.request_handler.get_shop_data(mall_link, user_request, self.cache) + self.speak_dialog(f"I am parsing shops and malls in your request") + LOG.info(f"I am parsing shops and malls in your request") + shop_info = self.request_handler.get_shop_data(mall_link, user_request) if len(shop_info) == 0: self.speak_dialog("shop_not_found") user_request = self.get_response('repeat') @@ -150,6 +192,7 @@ def execute(self, user_request, mall_link): LOG.info(str(mall_link)) new_count, user_request = self.find_shop(user_request, mall_link) count = count + new_count + # here is some logic problem (big pause after 3 tries) user_request = self.start_again() LOG.info(str(user_request)) if user_request is not None: diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e687c07317744b897684d5a790a02f85ee880e12 GIT binary patch literal 4661 zcmaJ_&u<&Y72eq&K2Wept>#GZr7Zpv=+Hb z&n#n$pwNR11Srr;552erM4%S~J@hXq(0`%Fz4qikpqHRP`@J_yNu-lf*x8xc`SsrS zzW3fJYc}f^e*f6J(f{WS%la2JF8*9JevC(dib7ga46T6P_E3!Mz!p?@hR(sY} zXuHxIRz}sJDy;jKtjOwfOIBm=#0h-q%i41*s7bMBb?dLNqSfn);&PJaahivNWEh7B zQ7VVA>e_|#>1X$gS})Ghy~AWUd?hd(qg%g>pG;z%|I7^R(<&E#PT9~oDzOy^X$AI| z)<8%h?dN{rNJqLjg&TX)I}zrDCnBhnr>n}kT*G%?He{1e+mh>e*JWE?!h20_$jf*) z?rbVhpgO%%D6i_jG_TP@d1o9c z9jiOsx4YPJXMCi{gx#=nTq~=1kQMXSW zY2)qA+0yo4ZLEu6eGGxdd6=aTCsO&eIXddAY>@A$IFd(Qukdx8=SjM+3uiyh3+K^9 z7jBmJ<6_%P2}!8X$P}98R0Bs)?X%(%YDJwin?%E88s9;iWo+12g!|v=_KJ+>Bd1r@ zB)z!9jcnRV*Spa){up7m1nx~`(Zg`1>tCXg0yJGtGiZtIeZwbLA8LnVODUM;y zj3VS1*Ji|Zr#HQEkmqB4Z+G|U)2BPpFdAi18uf9ESnc$)(QZ*=Jcdbn2#GeZlDdqd zlumt%I+l^)zI*7`WJ0cJi|N*i2xm(!>7A_9!^3+23o4{{$(ny*{n= zq`Pe$+k}ktP;(e3>(rfFFDycc+POEgPZ%%$idpEdoK|PBUm7{LAKJ(6u{U$oFHFzO z-h_YLw2nRAzdG|ad9|4{^9J@FSpYmHTR;sutoNpC4-CYeM0W~z7^VAPi93oM3b=fb zji+n7a^ZM(r}Z>`+97@3>jc#_3u%1U?)ADgHYBx;d5X*qu4Ft+a@J?z=v=WE2`bD*qbY8f9*m`)=C6A^Ebai13HGq&dFxs zdk<~(7J39>4!mt?O@z9hlNWMl_L*$}8{#Bpc$m>8G-YMMRBlxlt*+k-{74^$Blz6H zIf`}Bh|(-SfN~hIX4&dct5eu%7OVp+YAv)Ev?po&Y>apU=M9gaDY&;9EsEvRtZ2Z$ zVXPi!DRfRE8fd?RTGuhoVa<>^+K-Y{b!o^7m&mpC@aQcRb#~!CbnOb)CU@={jbB^Q z_=Q!M7)I>!@i4;%sLaGC)~|MsZRSSl-^jZ|$6^~+4^FwKp0p+PfNgW<*o7u560DMy zZENPv99jL7LvbJnr!MxLfN4)`>4PJ+Uf~b3ev~I!I$gi@P~PrFqgy+-AIk1{G;KV5 zxARfw!Na?q&pQta?;x8f9n{%0S>cL~42{uC?NYIW;-&qHblU6s7j(OZo?v~kRXE7h zD9Q_)FxHbtid0{CV+BXs^$Y?vhLn7_>fkFcZ8lj(qF6Hwqv8737^zz*2!poR5L@CZ z5ShM)1Cx@cEa5<&?#$l*hz4}Z_RjGI7GK)A13TX0Z!TFbYVdY*cy82S<%AFPPoq9U zk3-f@_&f$?Kt{}M_%-AyV9WbF*V|vrM(+38ipUX{`@oW-Nel^dq<;wNaj|Z+Kp~pN zQnzsWph@k8)^*Wjojy7W2g58=!P?VI0+6xvTMHt3k#6vhMf<@h?;`>y*Lj@zb z#S0-F0?A-H;bKX>@Nnv9oLlp35w4niV89wzsz8~PxZ=t9IHi8a+OrsWB4b-WP z9O?QBot9M4C#zfhIKrXvF&_Ol6dZB@(s{_q;jl~#j)|c7jG+P$CP0y!BWM{kg4YJ= zDl-QuFgZQ~j)NV;UnPi+0sTx+C@RN!hpzew#`!ZiNc56%V1+ZsXr0#1zvOVA<~*_r zYWltDJ3sG4qYh&#gTq?TVqoAfK{1LT6&1tOb43nj zy2gFRbE@y5r(3^3(Ql)}AkGjs$xHHTlsOg7FiwpD5MXQ%NU$i8Qa7pKe1lP6#q zk`y^K*m;6+x`U#H;ABv4c|eyjr*|6|wUA+Uz0UFg@T9m_r6^LJ?!}BWhBdDXQhX(l?%Kyzu&fIS*<` zT|%y|#wp4RUf^MRAv&?L34k^r@c-j`1`0$jdwO++UgfS!9+DJJif3Z|4i#2FNtIK3 zZb1=-P9GZ>azJt-%1jNaBtMDFj>rT$kojYV$s9g_su91?XWH-a3Os`I2a0d3>epMP zvZvQS=X(zE@c286C` ziWJH;S(ra1bLWVx>Yr{Lf5pSO896r}QHgRh$`&Y3XxT9Ne@}0%=>8k;T-`R^Ug=I7 zhDAL@q9iByH$Yf4!tlu?8k#SB#Z;SgG@FX+R4_+b2(l{U)0xd|X)G8Ww`i94+kTAb z5nhmpBCDcH9^azBiwcY7TMgTzqmQ literal 0 HcmV?d00001 diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fcca678c458c4fd0880538d6ce20d78cf3e6d3bc GIT binary patch literal 4026 zcmaJ^TW=f372eqkxui&mmK9$T$68yotk{VY*T}`O-Nu%SN`dP|gT;!o6jxgA z(lblTUaWuuHrl)xMiHP7MIWfg^p_OqKiJnk1^od9{80Bhvy?2+Zb|Ifx$n$(&iT%n zxL&Vn_}z5g>HcF*)BZ`7gFge6&+*775M1Ld)CSC9jLLebJGy!sj)Aukngh$RRG%4^ z24$x_upL{~t*|nvI@QU%8K}*!-;G9tbzkJVA9ZP|gTMXbUPr61IgD#K;t_6e^NEQ=TDHyQ@?~<@)YUBbKVP)1`O$Q5@_&+w7b{96eq{(yJ@Lu}g!*dt?tLRV4 zkG?Eg6MI}KJp7*O+tyn4N_wuJBt!XWr?b7i-S$Fn5POl=^`pcW?QT5i_|X9krqyrB z3cuJuf4Y4;PGO;5=VJSO^UQ6p8$?Mg`=2#`iOFFT4b3~N&EGbc&byc0GFELbU0%A>{^;Z8s@H>0 zOlHAr%6;F1x}{@=4+kL{?4A!RfNInE>mEFfH=*$+ISmxrs^%qB!(crxPmCuoPc)d9 z(Gw5-C^y#SM^61q-y0=CZxpV^qhW3ed`FO@DAN+hP=cG2MF_GD@fHap4C25IAMT84 z)0Sl^=Y1EW7J<67Tg#x)J zXBqejwxnnJV_4RfDc*%W=_kp3!C2qfaNeTTb=N}^?O zDz2RFsqt)n2S9<|0RrGMk88wRRjAwr#iXWT10L!)hsP&i% zx;DmG*R;nBTvW0aY*gE2<0`LXOgSrURxoC^7_(z%wyMt+_05@aZC4xDcmrcAS!G<`)xOtAKG5W_ zC>5;_H1We^?va;s)x4@|pYN+((2A;cCaYxCY$hx7qklDbYGB&g4Eti*{m(NKdo5$H z+ITK2skT4AdRCap-(E43g_ZQgw?sb8u7^;)Ljf7X@enHhq~AxxRaVnt;u8!;_NBMagMa$*5LQ`Ju}>6vLSbDh z$W^a|2y)={;Z?;S^u1GBdVb2B5LjJWN!ece3VB*6ucELwSusS~T*a7X51Acr2GMIy znp@q_lQMl{KM7b&{K$)v#jYnfc-!)lL?Ds}0|aR)y6GE}esYn;ZX6|E5cwjVJJ>AZ zZE+DBPmfJn`|(J~1EWt)+V>dZWfyrU<)*o%=7=_yL1GYZ#I43liWisATsU!sA^;Kx zU*_{Y1SaHM2hu)qmIi(R;n+ltF1XRDs8ZxVOvHj(K0z_JKm)-rx59XvvQTdbp5TJ~ zx>Y|AP<}ycH4c7>_#vNBAS<0n@jk|g&q#btf`XDDZ3;@<)RCOZzV-^`U!fjl0rf+= zy^3~u9-^wxBf(i()Oc>%RBPaWiZ$5@b_^_xp5NQ7hW_{em7e;4CUb~>C`=G_ zDT9kp$aJ{X1r>-WaDjeKY6xB?$cV_rK=4}5kX<5pnJRcutH~`&efH}mRWD^0`Gjm| zCFL6zGDAo5v#Q|Sh zAh`2#aeI-15OZcwb0-0En!rye{E^!u^+hOj*B`km7|MH?DM@1#*oyE*Alr!jreXd& z{csA_|Fup_7j$`CL2iT0pG+E;i>m7mVm>M^8m@bP-CkWxKfSv{j2L7rFfGWbHjBdz;p*)17DJxjo@UT`ylaRBabY wM3J1-!`S00pQpP

8_U>kgaSR|@d1QuZTHK-8cmGWbGMwobPITc0=n3y1Up#{d8T literal 0 HcmV?d00001 diff --git a/hashed_stores.txt b/hashed_stores.txt new file mode 100644 index 0000000..5525224 --- /dev/null +++ b/hashed_stores.txt @@ -0,0 +1 @@ +{'name': 'Apple', 'hours': '10am – 8pm', 'location': "Mall Level 2, near Macy's", 'logo': 'https://placewise.imgix.net/images/api/stores/10327.svg'} diff --git a/locale/en-us/data/saved_shops.pickle b/locale/en-us/data/saved_shops.pickle new file mode 100644 index 0000000..e69de29 diff --git a/request_handling.py b/request_handling.py index a6ae61e..293fa38 100644 --- a/request_handling.py +++ b/request_handling.py @@ -1,20 +1,25 @@ from urllib.error import HTTPError import requests import bs4 +from os import path from neon_utils.skills.neon_skill import LOG import urllib.request import lingua_franca +from lingua_franca import parse from lingua_franca.format import pronounce_number lingua_franca.load_language('en') +from datetime import datetime +import pytz import re class RequestHandler(): def __init__(self) -> None: - pass + self.hashing_file = path.join(path.abspath(path.dirname(__file__)), + 'hashed_stores.txt') def existing_lang_check(user_lang, url): @@ -42,6 +47,19 @@ def parse(self, url): LOG.info("Failed url parsing") def location_format(self, location): + """ + Extracts numbers from string. + Substitutes numerals from shop location data to words + + Args: + locaion (str): location from mall parsing + + Returns: + pronounced (str): location with pronounced numerals + + Examples: + 'level 1' -> 'level one' + """ floor = re.findall(r'\d+', location) if len(floor) > 0: floor = floor[0] @@ -51,20 +69,71 @@ def location_format(self, location): else: location + def save_stores_data(self, store_info): + # initial hashing implementation + with open(self.hashing_file, 'a+', encoding="utf-8") as hashed_file: + hashed_file.write(str(store_info)+'\n') + + def curent_time_extraction(self): + """ + Defines current time in utc timezone + Format: hour:minutes part of day (1:23 pm) + + Returns: + day_time (list): contains splited time + numerals and part of the day + day_time -> ['07:19', 'am'] + hour (int): current hour + min (int): current minute + """ + now = datetime.utcnow().replace(tzinfo=pytz.utc).strftime("%H:%M %p") + day_time = now.lower().split(' ') + exact_time = day_time[0].split(':') + hour, min = int(exact_time[0]), int(exact_time[1]) + return day_time, hour, min - def get_shop_data(self, url, user_request, cache): - found_shops = [] - soup = self.parse(url) - for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): - logo = shop.find_next("img").get('src') - info = shop.find_next(attrs={"class": "tenant-info-container"}) - name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') - if name.lower() in user_request.lower() or user_request.lower() in name.lower(): - hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') - location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') - shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} - found_shops.append(shop_data) - return found_shops + def clear_request(self, user_request): + """ + Cleares user request from unnecessary after intent is trigered but + shop name was not recognized. + + Args: + user_request (str): utterance from stt + + Returns: + user_request (str): cleared utterance + + Examples: + 'find starbucks' -> 'starbucks' + """ + cleaned_request = re.sub('find|fine|where|is|are|I|i|can|could|help|me|looking|look|for|am', '', user_request) + cleaned_request = cleaned_request.strip() + return cleaned_request + + def get_shop_data(self, url, user_request): + # Change hashing to json file + LOG.info("Using cached stores data") + with open(self.hashing_file, 'r+', encoding="utf-8") as from_hashed_file: + self.from_hashed_stores = from_hashed_file.readlines() + found_shops = [shop for shop in self.from_hashed_stores if str(user_request) in shop] + if len(found_shops) != 0: + LOG.info("Shop found in cached stores data") + return found_shops + else: + found_shops = [] + soup = self.parse(url) + for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): + logo = shop.find_next("img").get('src') + info = shop.find_next(attrs={"class": "tenant-info-container"}) + name = info.find_next(attrs={"class": "tenant-info-row"}).text.strip().strip('\n') + if name.lower() in user_request.lower() or user_request.lower() in name.lower(): + hours = info.find_next(attrs={"class": "tenant-hours-container"}).text.strip('\n') + location = info.find_next(attrs={"tenant-location-container"}).text.strip('\n') + shop_data = {'name': name, 'hours': hours, 'location': location, 'logo': logo} + found_shops.append(shop_data) + for shop in found_shops: + self.save_stores_data(shop) + return found_shops def shop_selection_by_floors(self, user_request, found_shops): diff --git a/testing.py b/testing.py new file mode 100644 index 0000000..f79c688 --- /dev/null +++ b/testing.py @@ -0,0 +1,57 @@ +import datetime +import lingua_franca +from lingua_franca import parse, format +lingua_franca.load_language('en') +import pickle +import os +import re +import pytz + +#working code +from datetime import datetime + + +# make plural wait_h-1 hours +def curent_time_extraction(): + now = datetime.utcnow().replace(tzinfo=pytz.utc).strftime("%H:%M %p") + day_time = now.lower().split(' ') + print(day_time) + exact_time = day_time[0].split(':') + hour, min = int(exact_time[0]), int(exact_time[1]) + return hour, min + +shop_name = 'Apple' + +def time_calculation(time, shop_name): + hour, min = curent_time_extraction() + hour, min = int(8), int(59) + day_time = ['time', 'am'] + time = '9am - 8pm' + + parse_time = time.split('-') + open_time = int(re.sub('[^\d+]', '',parse_time[0])) + close_time = int(re.sub('[^\d+]', '',parse_time[1])) + print(int(open_time)) + print(int(close_time)) + if day_time[1]=='am' and hour Date: Wed, 2 Nov 2022 22:33:26 -0700 Subject: [PATCH 21/21] Update another_shop.dialog --- locale/en-us/another_shop.dialog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/en-us/another_shop.dialog b/locale/en-us/another_shop.dialog index be4fbfd..876ac05 100644 --- a/locale/en-us/another_shop.dialog +++ b/locale/en-us/another_shop.dialog @@ -1 +1 @@ -What shop you are looking for? \ No newline at end of file +What store you are looking for?