From 5e2f6780caf3415f592f0d8381441f09cc7f7094 Mon Sep 17 00:00:00 2001 From: NeonMariia <95533543+NeonMariia@users.noreply.github.com> Date: Thu, 8 Sep 2022 20:05:45 +0200 Subject: [PATCH 01/23] Initial skill implementation (#1) * initial implementation * Delete .idea directory * some functions removal * mall link from settings * web page scraping, output fixes * level selection, number pronunciation * github automation, version, readme update, minor fixes * scripts directory * Update skill.json * urllib requirement removal * Update skill.json * gui test fixes * readme and dialogs fixes * Update skill.json * readme fixes * Update skill.json * readme update * Update skill.json * time refactoring Co-authored-by: NeonMariia --- .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 +++++ .gitignore | 1 + LICENSE.md | 21 +++ README.md | 23 ++- __init__.py | 180 +++++++++++++++++++++++ locale/en-us/all_locations.dialog | 1 + locale/en-us/another_shop.dialog | 1 + locale/en-us/ask_more.dialog | 1 + locale/en-us/finished.dialog | 1 + locale/en-us/found_shop.dialog | 1 + locale/en-us/more_than_one.dialog | 2 + locale/en-us/no_lang.dialog | 1 + locale/en-us/no_shop_on_level.dialog | 1 + locale/en-us/no_shop_request.dialog | 1 + locale/en-us/repeat.dialog | 1 + locale/en-us/run_mall_parser.intent | 7 + locale/en-us/shop_by_floor.dialog | 1 + locale/en-us/shop_not_found.dialog | 2 + locale/en-us/speak_all_shops.dialog | 1 + locale/en-us/start_parsing.dialog | 1 + locale/en-us/stop.dialog | 1 + locale/en-us/unexpected_error.dialog | 1 + locale/en-us/which_floor.dialog | 3 + locale/en-us/yes.voc | 4 + request_handling.py | 81 ++++++++++ requirements/requirements.txt | 5 + requirements/test_requirements.txt | 1 + scripts/update_skill_json.py | 57 +++++++ settingsmeta.yml | 9 ++ setup.py | 100 +++++++++++++ skill.json | 48 ++++++ test/test_skill.py | 99 +++++++++++++ version.py | 29 ++++ version_bump.py | 54 +++++++ 38 files changed, 936 insertions(+), 2 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 .gitignore create mode 100644 LICENSE.md create mode 100644 __init__.py create mode 100644 locale/en-us/all_locations.dialog create mode 100644 locale/en-us/another_shop.dialog create mode 100644 locale/en-us/ask_more.dialog create mode 100644 locale/en-us/finished.dialog create mode 100644 locale/en-us/found_shop.dialog create mode 100644 locale/en-us/more_than_one.dialog create mode 100644 locale/en-us/no_lang.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/repeat.dialog create mode 100644 locale/en-us/run_mall_parser.intent create mode 100644 locale/en-us/shop_by_floor.dialog create mode 100644 locale/en-us/shop_not_found.dialog create mode 100644 locale/en-us/speak_all_shops.dialog create mode 100644 locale/en-us/start_parsing.dialog create mode 100644 locale/en-us/stop.dialog create mode 100644 locale/en-us/unexpected_error.dialog create mode 100644 locale/en-us/which_floor.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 scripts/update_skill_json.py create mode 100644 settingsmeta.yml create mode 100644 setup.py create mode 100644 skill.json create mode 100644 test/test_skill.py 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/.gitignore b/.gitignore new file mode 100644 index 0000000..9f21b54 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/venv/ 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..eed73de 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,21 @@ -# malls-parser-skill -Skill for parsing store name and location or a requested product/store +# {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 + +## 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) +[NeonMariia](https://github.com/neonmariia) + +## Category +**Information** diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..7aa28aa --- /dev/null +++ b/__init__.py @@ -0,0 +1,180 @@ +# 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 re + + + +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 + 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 + + # @property + def mall_link(self): + mall_link = 'https://www.alamoanacenter.com/' + return self.settings.get("mall_link") or mall_link + + def user_request_handling(self, message): + 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) + 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') + if another_shop is not None: + LOG.info(f'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']) + 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): + 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: + 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) + 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: + return self.more_than_one(shop_info) + else: + LOG.info(f"found shop {shop_info}") + return self.speak_shops(shop_info) + else: + LOG.info(str(None)) + 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: + return None + + def _start_mall_parser_prompt(self, message): + LOG.info('Prompting Mall parsing start') + self.make_active() + 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.speak_dialog('finished') + + + +def create_skill(): + return DirectorySkill() 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/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/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/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/found_shop.dialog b/locale/en-us/found_shop.dialog new file mode 100644 index 0000000..18d51bc --- /dev/null +++ b/locale/en-us/found_shop.dialog @@ -0,0 +1 @@ +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/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/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/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..6ca56f4 --- /dev/null +++ b/locale/en-us/no_shop_request.dialog @@ -0,0 +1 @@ +Okay. I will stop mall parsing. \ 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..853f0fc --- /dev/null +++ b/locale/en-us/repeat.dialog @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..6ea1597 --- /dev/null +++ b/locale/en-us/run_mall_parser.intent @@ -0,0 +1,7 @@ +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/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/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..4071f9b --- /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 new file mode 100644 index 0000000..2874c27 --- /dev/null +++ b/locale/en-us/stop.dialog @@ -0,0 +1 @@ +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 new file mode 100644 index 0000000..aed994a --- /dev/null +++ b/locale/en-us/unexpected_error.dialog @@ -0,0 +1 @@ +Sorry, I'm not sure what you mean. \ 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..4333de5 --- /dev/null +++ b/locale/en-us/which_floor.dialog @@ -0,0 +1,3 @@ +Which floor are you staying on? +On which floor are you? +what is your floor? \ 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..a6ae61e --- /dev/null +++ b/request_handling.py @@ -0,0 +1,81 @@ +from urllib.error import HTTPError +import requests +import bs4 +from neon_utils.skills.neon_skill import LOG +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/' + response = requests.get(link) + if response.status_code == 200: + LOG.info('This language is supported') + return True, link + else: + LOG.info('This language is not supported') + return False, link + + 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 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() 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 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 new file mode 100644 index 0000000..1de0bd9 --- /dev/null +++ b/requirements/requirements.txt @@ -0,0 +1,5 @@ +numpy +neon-utils~=1.0 +bs4 +requests +ovos-lingua-franca \ 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/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() 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..b7713c3 --- /dev/null +++ b/skill.json @@ -0,0 +1,48 @@ +{ + "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", + "examples": [ + "where is apple?", + "where can i find abc stores?" + ], + "desktopFile": false, + "warning": "", + "systemDeps": false, + "requirements": { + "python": [ + "bs4", + "neon-utils~=1.0", + "numpy", + "ovos-lingua-franca", + "requests" + ], + "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" + ], + "tags": [], + "credits": [ + "NeonGeckoCom", + "NeonMariia" + ], + "skillname": "malls-parser-skill", + "authorname": "NeonGeckoCom", + "foldername": null +} \ No newline at end of file diff --git a/test/test_skill.py b/test/test_skill.py new file mode 100644 index 0000000..074e11f --- /dev/null +++ b/test/test_skill.py @@ -0,0 +1,99 @@ +# 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 + +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): + 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', + 'lang': 'en-us' + }, + {'context_key': 'MallParsing'})) + + message = Message('test', {'utterance': 'where is apple', + 'shop': 'apple', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + + self.skill.user_request_handling(message) + + + +if __name__ == '__main__': + unittest.main() 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 02d03778e493af08f5dcd2ea30fd04cec66abe0a Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Thu, 8 Sep 2022 18:06:29 +0000 Subject: [PATCH 02/23] Increment Version --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index 3d53a58..e47fc1c 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # 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" +__version__ = "0.0.1a1" From 5ce5676d8851a9c0515f70cf6d70ffd19fe44ee4 Mon Sep 17 00:00:00 2001 From: mariiarazno Date: Sun, 18 Sep 2022 14:43:17 +0200 Subject: [PATCH 03/23] ask user for sort celection, time selection functions, better user request and shop names match, docstrings --- __init__.py | 183 +++++++++++++++++++---- cached_stores.json | 0 locale/en-us/speak_all_shops.dialog | 1 - request_handling.py | 216 +++++++++++++++++++++++----- 4 files changed, 340 insertions(+), 60 deletions(-) create mode 100644 cached_stores.json delete mode 100644 locale/en-us/speak_all_shops.dialog diff --git a/__init__.py b/__init__.py index 7aa28aa..c17c600 100644 --- a/__init__.py +++ b/__init__.py @@ -40,6 +40,7 @@ def __init__(self): super(DirectorySkill, self).__init__(name="DirectorySkill") self.request_handler = RequestHandler() self.cache = dict() + self.url = "https://www.alamoanacenter.com/en/directory/" def initialize(self): @@ -50,7 +51,6 @@ def initialize(self): @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 @@ -92,51 +92,186 @@ def start_again(self): return None def speak_shops(self, shop_info): + """ + Speaks shop info that was found. + Substitutes time format for better pronunciation. + speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) + Shows shop label image. + Args: + shop_info (list): found shops on user's + request + """ for shop in shop_info: LOG.info(shop) location = self.request_handler.location_format(shop['location']) - hours = re.sub('(\d+)am(.+\d)pm', r'\1 A M\2 P M', shop['hours']) + 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}) + LOG.info({"name": shop['name'], "hours": 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) + def location_selection(self, shop_info): + """ + If there are several shops in found shops list + and user wants to get shop info on the certain + floor. If shop on that floor exists speaks + this shop info. Else speaks all shops info. + Args: + shop_info (list): found shops on user's + request + Returns: + 3, None (to ask for another shop info) + """ + LOG.info(f"Shop by location selection {shop_info}") + floor = self.get_response('which_floor') + shops = self.request_handler.shop_selection_by_floors(floor, shop_info) + if shops: + self.speak_shops(shops) + return 3, None 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) + self.speak_dialog('no_shop_on_level') + self.speak_shops(shop_info) + return 3, None + + def open_shops_search(self, shop_info): + """ + Selects open shops. Collects the list of + open shops else return empty list. + Args: + shop_info (list): found shops on user's + request + Returns: + shop_info (list): open shops + """ + open_shops = [] + day_time, hour, min = self.request_handler.curent_time_extraction() + for shop in shop_info: + parse_time = shop['time'].split('-') + open_time = int(re.sub('[^\d+]', '', parse_time[0])) + close_time = int(re.sub('[^\d+]', '', parse_time[1])) + if open_time <= hour < close_time: + open_shops.append(shop) + return open_shops + + + def time_calculation(self, shop_info, open): + # 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. + If shop is closed and user is before opening speaks how + much time is left for waiting. If user's time is evening + speaks when the shop opens in the morning. In other cases + just speaks shops info. + If user in open hours, speak the shop info. If shop open + and closes in one hour speaks how many minutes left. + Args: + shop_info (list): found shops on user's request + open (boolean): True - if shop is open + Returns: + 3, None (to ask for another shop info) + Examples: + work time 9am-10pm + user's time 8am + Prompt: 'Shop is closed now. Opens in 1 hour' + """ + for shop in shop_info: + work_time = shop['time'] + shop_name = shop['name'] + 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 open: + if day_time[1] == 'pm' and 0 >= (close_time - hour) <= 1: + self.speak(f'{shop_name} closes in {wait_min} minutes') + self.speak_shops([shop]) else: - self.speak_dialog('all_locations') - return self.speak_shops(shop_info) + if day_time[1] == 'am' and hour < open_time: + self.speak(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') + elif hour >= close_time: + self.speak(f'{shop_name} is closed now. Shop opens at {open_time}') + self.speak_shops([shop]) + return 3, None + + def shops_by_time_selection(self, shop_info): + """ + If user chose to select shops by time or + use like default selection. Selects open + shops. + Args: + shop_info (list): found shops on user's + request + Returns: + shop_info (list): open shops + """ + LOG.info(f"Shop by time selection {shop_info}") + open_shops = self.open_shops_search(shop_info) + if len(open_shops) >= 1: + return self.time_calculation(open_shops, True) + else: + return self.time_calculation(shop_info, False) def find_shop(self, user_request, mall_link): + """ + When the intent is matched, user_request + variable contains the name of the shop. + The matching function get_shop_data() is + used to find the shop name in cache or + on the mall page. + If user's request is not None this function + can return several shops, one shop or empty + list. + If no shop was found asks user to repeat. + returns 1, user_request to continue the + execution loop in self.execute(). + If there are several shops asks user what way + of sorting to choose: time, level, nothing. + If 'time' - finds open shops. If open shops + list is not empty speaks open shops, else + tells time difference between user and shops' + work hours. + If 'location' - asks what level user is interested + in. If shops were found speaks shops' info, + else tells that there is no shop on that level + and speaks all found shops. + If 'no' - sorts by time. + If nothing matched in the answer - sorts by time. + If there was one shop found speaks this + shop info. Returns 3, None to stop current + shop search. + Location and time sorting functions return + 3, None to stop current shop search. + """ 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) + 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') return 1, user_request elif len(shop_info) > 1: - return self.more_than_one(shop_info) + self.speak_dialog('more_than_one') + # ask for the way of selection: time, location, nothing + sorting_selection = self.get_response('Do you want to select' + 'store by time or location?') + if sorting_selection.contains('time'): + return self.shops_by_time_selection(shop_info) + elif sorting_selection.contains('location'): + return self.location_selection(shop_info) + elif sorting_selection.contains('no'): + return self.shop_by_time_selection(shop_info) + else: + return self.shop_by_time_selection(shop_info) else: LOG.info(f"found shop {shop_info}") - return self.speak_shops(shop_info) + self.speak_shops(shop_info) + return 3, None else: LOG.info(str(None)) return 3, None diff --git a/cached_stores.json b/cached_stores.json new file mode 100644 index 0000000..e69de29 diff --git a/locale/en-us/speak_all_shops.dialog b/locale/en-us/speak_all_shops.dialog deleted file mode 100644 index 9038294..0000000 --- a/locale/en-us/speak_all_shops.dialog +++ /dev/null @@ -1 +0,0 @@ -Do you want me to speak all found shops? \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index a6ae61e..e5f026f 100644 --- a/request_handling.py +++ b/request_handling.py @@ -5,19 +5,90 @@ import urllib.request import lingua_franca +from lingua_franca import parse from lingua_franca.format import pronounce_number lingua_franca.load_language('en') import re +from os import path +import json + +from datetime import datetime +import pytz class RequestHandler(): def __init__(self) -> None: - pass - + self.caching_file = path.join(path.abspath(path.dirname(__file__)), + 'cached_stores.json') + + def find_cached_stores(self, user_request: str): + """ + Check shop name existence in cache keys + Args: + user_request (str): shop from user's message + Returns: + if file is empty -> None, {} + if shop wasn't found -> None, read data + if shop found -> store_info (list), read data + Examples: + [ + {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, + {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} + ] + """ + with open(self.caching_file, 'r') as readfile: + file_length = os.stat(self.caching_file).st_size + if file_length == 0: + LOG.info('No shop in cache') + return None, {} + else: + data = json.load(readfile) + found_key = [key for key in data.keys() if key in user_request or user_request in key] + if user_request is []: + LOG.info("Shop doesn't exist in cache") + return None, data + else: + LOG.info('Shop exists') + return data[found_key], data + + def caching_stores(self, data, store_info: list): + """ + Saves dictionary to JSON file + key - shop name, value: list with shops info + if file is empty -> creates dictionary + if file contains info -> updates data dictionary + with the new shop info + Args: + data (dict): existing shops info + store_info (list): scraped store info + Returns: + store_info (list): scraped store info + Examples: + {"ABS stores": [ + {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, + {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} + ]} + """ + if data == {}: + data = {store_info['name']: store_info} + else: + new_store = {store_info['name']: store_info} + data.update(new_store) + with open(self.caching_file, 'w') as outfile: + json.dump(data, outfile) + return store_info - def existing_lang_check(user_lang, url): + def existing_lang_check(user_lang: str, url): + """ + Check existence of user's language + on the mall web-page + Args: + user_lang (str): user's lang in ISO 639-1 + Returns: + bool: True if lang exists + """ link = url+user_lang+'/directory/' response = requests.get(link) if response.status_code == 200: @@ -27,21 +98,40 @@ def existing_lang_check(user_lang, url): LOG.info('This language is not supported') return False, link - 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 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 location_format(self, location): + """ + Finds all digits in store's location and + formats them to numeral words. + Args: + location (str): location info + from shops info + Returns: + if digits were found: + pronounced (str): utterance with + pronounced digits + else: + location (str): not changed utterance + Examples: + 'level 1' -> 'level one' + """ floor = re.findall(r'\d+', location) if len(floor) > 0: floor = floor[0] @@ -49,25 +139,25 @@ def location_format(self, location): 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() 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 - + return location def shop_selection_by_floors(self, user_request, found_shops): + """ + If there are several shops in found shops list + and user agrees to select shop by floor. + Finds all digits in store's location and + formats them to ordinal and cardinal numerals. + Matches formated numerals with user's request. + If shop was found appends it to the new found + list. + Args: + user_request (str): floor from user + found_shops (list): found shops on user's + request + Returns: + shops_by_floor (list): shops that was found by floor + """ + shops_by_floor = [] for shop in found_shops: numbers = re.findall(r'\d+', shop['location']) if len(numbers) > 0: @@ -75,7 +165,63 @@ def shop_selection_by_floors(self, user_request, found_shops): 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 + shops_by_floor.append(shop) + return shops_by_floor + + def parse(self, url): + 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): + """ + Check existence of user's request store in cache + if shop was found returns list with shop info, + else does parsing of mall's web-page. + Matches the name of existing stores with user's + request. If store was found, returns list with + stores' info and does caching, else returns empty + list. + on the mall web-page + Args: + url (str): mall link from hardcoded in init.py + user_request (str): utterance from stt parsing + Returns: + : found_shops (list): found shops' info + """ + # search for store existence in cache + found_shops, data = self.find_cached_stores(user_request) + if found_shops is not None: + return found_shops + else: + # parsing mall web-page + found_shops = [] + soup = self.parse(url) + # loop through store names + 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') + # matching store names with user's request + 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) + if found_shops: + # caching if shop was found + self.caching_stores(data, found_shops) + return found_shops else: - None + # return empty list + return found_shops From 3ff0050c57c70effb8da53661449f6756c66ab0c Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 19 Sep 2022 05:46:29 -0400 Subject: [PATCH 04/23] adding new requirements --- request_handling.py | 28 ++++++++++++++++++++++++++++ requirements/requirements.txt | 5 ++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/request_handling.py b/request_handling.py index e5f026f..d64749b 100644 --- a/request_handling.py +++ b/request_handling.py @@ -1,3 +1,31 @@ +# 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 urllib.error import HTTPError import requests import bs4 diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1de0bd9..bfbdb20 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -2,4 +2,7 @@ numpy neon-utils~=1.0 bs4 requests -ovos-lingua-franca \ No newline at end of file +ovos-lingua-franca +pytz +datetime +urllib \ No newline at end of file From 0a822fe5e1e3d84b2c2369794b6372ba14bf7ec8 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 19 Sep 2022 09:47:41 +0000 Subject: [PATCH 05/23] Update skill.json --- skill.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/skill.json b/skill.json index b7713c3..3c46a30 100644 --- a/skill.json +++ b/skill.json @@ -14,10 +14,13 @@ "requirements": { "python": [ "bs4", + "datetime", "neon-utils~=1.0", "numpy", "ovos-lingua-franca", - "requests" + "pytz", + "requests", + "urllib" ], "system": {}, "skill": [] From ae70a0916b92a3aec22f198165b20d8fa4b5612a Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 19 Sep 2022 06:22:11 -0400 Subject: [PATCH 06/23] caching fixes, urllib removal --- __pycache__/__init__.cpython-310.pyc | Bin 0 -> 9723 bytes __pycache__/request_handling.cpython-310.pyc | Bin 0 -> 8157 bytes cached_stores.json | 1 + request_handling.py | 7 ++++++- requirements/requirements.txt | 3 +-- 5 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/request_handling.cpython-310.pyc diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0ae3357b4725033406e602cd381491acd47ef830 GIT binary patch literal 9723 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 jmDaX<-q$ZTCgm>p<_O_Dk)Tt=0bqNh#ka 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..77759b880d88415aa5d85909cb40d189aeefe703 GIT binary patch literal 8157 zcmdT}OK%(36`m(Kq$pZe96u5_xmDvzY$VExLLpX=qAfwcoDzwcSvl>mOZH_w`7^}il?*`@l;zXo~ozy z^p>8rm0D%dX0!}EHP7f(S{2czd)3}tYfhFN&Tk~Q>y;aAv?A3dxTBowv z7Fr9aGrZHi)2-84-C}F;HOa2n)lVe5dS7WhWGmZJW9}=gRBFgc<@MXQfAlKn0pCJN zef!pHc<6nLhYm6eeIEG1z;8RIKj`f^JW(7U^;+MGx_BD46*-aHbJ{fB?lUDP^K zOSUB(!+Be^wNEr0gzjh^#V*-plyuv0N+{7HH5|yCXsg-tC>eI$KJ|&zs@MzmX*{d; zqWut_bM_hgVLWU0S^FHG^I}^UlKMN&M*}B}UblSP!+J~Hi3Z%?YReO=qkI{^SMUoT zLlQ}cGJXnvYNQ>>N3ty+DY0bgwuIACwuXystKD^MGmHZ6gw4G$@EavglO)<+;QC3~ z+6n1YlCte`-|9JuVVWJ+b4)X-(O}o#6=gcU(D6E7O8hj2f4#Zh4SLRc&*H9Yt%rA9 z&kIpPx+boHuZg;Ko^H(S(#|khvwuLZaD6v2%`3E%5|Tw(k*g1e=cbRLnfFU-gV5n- zHdkB0i2RR68~D-9g?~g6NwKttgSfAJ{?15=ltY!uNAjLFQe)Lt?kIdI(hp0vDuh#W zrH=Bhq(F={Nt5oYGL9}P9~!Z8B#*RTNgdhN?b0VktQ}U4r0ddsd8B_T?MefQ-w^YZ z(|KY@#<6+mJqESMTC86{dc4nu6u!TY9`yenezu0It5|=-ZC6?L_e$4k-(g`l=rg(_ z=G=3`$boz_*Jt92*&XK~Ec(C1cf-x1M3YROA6p3{-q=j%>+ql_T9-rCbHdQtb&7%S z2qB!A$?Y(b59Wr<>Gh)nwswte1-`S&J{}b#>6PdxR=jV8{&K`RP)K{+nLC!vpoi9p zV~Wm9NG`;s6R;HzyKbzq<3nG)XZ8A?Gqck7r>6dRiH>e*lP$gU@^(6HxU|Za2nVS4 zhgNTG<8r@8bzac6A~*1OnVNOmFjAJ#^4EPpE9mBQMmQY_zf1(uy?B=>-{ z1vHDTv9gA_Bc9Q?)=p`WiH>izeR{}yk9xKqm+#y$i#h zqh1Ud0T#VHiA7Y`(5$-+ZgrO4@gHD2$Hm)<8df~C|?ZA&L*H0%SgcT)8JaDg;7XDhMNfli7V zUGR=lTGJ>G|6Is-N*Bf?A?V~5o)$BhH1UDYND+v*v3+L;+-<`CA6JB8M>&U}3NQzn zmw^3^?OW`bE6=TM6l*zAzITGa+hn)-05qbL4o=NLF>E-u4npd*!CMd3zxoqSY52(P zE;vWN1L7aypx+O8;hB0mJxD>~l@ zc+ZM9SvMH)&7SKI2z5dKBL$*6%f{#h4QX0)0*Krj+AtvI5h?zUShG z#k-3+2xd?EgSY2!a1aJ(qgV&YH|GD=!NnS2LEc}!{Osn&bIYr2+3GEi*%`f*Hp^CA zpKL*XN>n%X0vyI{pY(i+UT~;N(U9!l(75#a=B1nLQvYk23G(-~y!jQ8z($3WZv*6u z+J3N~l)2OQthNJB_8=NUNTYUQfXsL3>=NAz_Tf9~;>Z$}R+ti_M5V#-decL0Rh@fQ zI}!(;XteZ1#o}hg`z$sG2@u^(=U&7y1}6WPSWrlTh4VE6!$%JAyQ!GQA zb?Xn4;(P-<72*JNv)x@cB94$+VFIm;<&(4&*U}Lp9TF<`h}vW##r6Ykhs~nfacC@N z%eL|HWL-Jo|F9W7m*}tIM`|E!V5@@e4UBj@qz%tjXP{r z$2*=C^i%l&Grj!ZV#XB17s&iCK*}{4=Kir6DW4fOMeIp!3B!eW_mSC^K<0_mZ;Z4| z)HM=ph`SDP*CFmI#Qkcl+bYBzG#%njwVJI%+)G8dB+BKuMDAU9IW7w~?`o_b>X{>; z#VM{gCu#c|9TI6skcD3ej}|ytFxbpFPi2xQdT|{Dq-5inwaXoVJ2V1OAlT)UH}4!U zamHiC_^%Z%yKw>GZ7aS0Tni@@@TL{P;D%T^nIK4Df&GHrXS^gc`Qzo`7-*LaL5XbB zRv&IBFv5kc3j`S=uR!>T#VOE{jORG6`+^o_@5&WWoJ*?f)LhI(+LwBv>2c>4FAaI5 zbzo)b2Bt=3FDU9x#0xQjxpQEq@6TsX=Z(4+ysu*G`Rx@maspybet)Wg{5526!-~8? zRc})A7A0fFE2YQRFvcEuGB>fkR(*OwaO-jWNg20=xNOL8VY=2FN@jNNv&wm%`kSfD z!TJ{(!6^;4;4lS&N)~@I9$A8 z!LtHZaF2*r2>%!%of3+kyF4I;NQ@V0|NKhY?J z!+~Cs2>YbOJd5#cQ0-4Cc@GJN^mjprLJXUc98UE4&-jJpFKUYRz)%&r^gvPSGKHI0 z{#jEOWaYu|%#5Ij@bwg!&trlD%>RKxEFtOw`uzbU0o()en1Rga33dg{lMjc8GibI> zKAer{4LFMo@ZTPl;?g0$BOJ-_>2S{@0QnxG(snuRGcsaBl#R5!hX^w0eaWt%ZzZlA zmXGjeN&)_Fr@6EivA!T1TOE%TvPSr8p7oOc<4U>-wZo`S*@ycHu?DBaKoQKm%4o`Z$3_JdHKa1c*eeWE^-U zULvB2xr~vYLB1>4T<*P&aXL2<_^ z8IZ?3kX7s{jUFtEB@63CVNbDdD8nifYtKiE$ayhx+PwUyE;%$VBV@vhC!aMnJ6%{$ z@@{P+z4XBa-(HG$k{P0X=O0z_ISL||(bOo)&$<|TqTSj2HlvmvOfkt;qPIOO42KUF ze*PLhX;^-=Mphr-qFYhK;jFv8UGO{J9zLA46QN$q97Kl?Pfb>HI6885cs8pe=O&!! zdm*dOsoS^-j{hZScA|?R!Uj@^c{gZO`S;Ku;G|^`|1=?Rh$K_aC%2+Ktw$Qgu7<`Ks* zi`E34!|x-hpEiiFs#W=nLI&}o%#`!;S&Cc<(^yleb$D?G_ogOmD4cw}twx=`){*w$ zgJ9J(dx1SjKMtDaM+3`CTR44%z$HaooD~I5kf*)#rzv@rk{?k*!8LJ|+@;*<>4YNj zyn=*|Dx{+_q^eTa>W|eQM>KBHxL`b5tBW3uGx#VdtV`3O@4%_KXwn29AW1Iz4%h;W ziWfH1uaiwtBl79jgnBkU(|1l`mSmnjQVuM$L(Y>WmYSTIs%xf{Q;;BZ$5#ju|Dmv2 z%99?YMbI}^5uX$uJb#{ww6l}8oER^p=X#Btei3`DT{J}G5vfuW5l91JBoU3ob5Z>l Dz~ppz literal 0 HcmV?d00001 diff --git a/cached_stores.json b/cached_stores.json index e69de29..d8e4975 100644 --- a/cached_stores.json +++ b/cached_stores.json @@ -0,0 +1 @@ +{"Apple": {"name": "Apple", "hours": "10am \u2013 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/10327.svg"}} \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index d64749b..a2034b3 100644 --- a/request_handling.py +++ b/request_handling.py @@ -38,6 +38,7 @@ lingua_franca.load_language('en') import re +import os from os import path import json @@ -100,7 +101,11 @@ def caching_stores(self, data, store_info: list): ]} """ if data == {}: - data = {store_info['name']: store_info} + for store in store_info: + data = {store['name']: store} + if data != {}: + new_store = {store['name']: store} + data.update(new_store) else: new_store = {store_info['name']: store_info} data.update(new_store) diff --git a/requirements/requirements.txt b/requirements/requirements.txt index bfbdb20..1736d55 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -4,5 +4,4 @@ bs4 requests ovos-lingua-franca pytz -datetime -urllib \ No newline at end of file +datetime \ No newline at end of file From 060cb618d943598a58bb3cdbaac0a86f48324876 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 19 Sep 2022 10:24:01 +0000 Subject: [PATCH 07/23] Update skill.json --- skill.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/skill.json b/skill.json index 3c46a30..7014e3e 100644 --- a/skill.json +++ b/skill.json @@ -19,8 +19,7 @@ "numpy", "ovos-lingua-franca", "pytz", - "requests", - "urllib" + "requests" ], "system": {}, "skill": [] From 50937b37b146575853782d766e1f0d185ce34d34 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 19 Sep 2022 11:12:12 -0400 Subject: [PATCH 08/23] time parsing, cache writing fixes --- __init__.py | 59 +++++++++++-------- __pycache__/__init__.cpython-310.pyc | Bin 9723 -> 0 bytes __pycache__/request_handling.cpython-310.pyc | Bin 8157 -> 0 bytes cached_stores.json | 2 +- locale/en-us/found_shop.dialog | 2 +- locale/en-us/more_than_one.dialog | 3 +- locale/en-us/run_mall_parser.intent | 3 +- request_handling.py | 39 +++++++----- test/test_skill.py | 15 ++--- 9 files changed, 73 insertions(+), 50 deletions(-) delete mode 100644 __pycache__/__init__.cpython-310.pyc delete mode 100644 __pycache__/request_handling.cpython-310.pyc diff --git a/__init__.py b/__init__.py index c17c600..830e095 100644 --- a/__init__.py +++ b/__init__.py @@ -61,19 +61,23 @@ def mall_link(self): def user_request_handling(self, message): 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) - 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") + if message.data == {}: + self.speak('Message is empty') return None, None + else: + 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) + 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") @@ -145,7 +149,8 @@ def open_shops_search(self, shop_info): open_shops = [] day_time, hour, min = self.request_handler.curent_time_extraction() for shop in shop_info: - parse_time = shop['time'].split('-') + parse_time = shop['hours'].split('-') + LOG.info(f'Parse time {parse_time}') open_time = int(re.sub('[^\d+]', '', parse_time[0])) close_time = int(re.sub('[^\d+]', '', parse_time[1])) if open_time <= hour < close_time: @@ -175,7 +180,7 @@ def time_calculation(self, shop_info, open): Prompt: 'Shop is closed now. Opens in 1 hour' """ for shop in shop_info: - work_time = shop['time'] + work_time = shop['hours'] shop_name = shop['name'] day_time, hour, min = self.request_handler.curent_time_extraction() parse_time = work_time.split('-') @@ -251,6 +256,8 @@ def find_shop(self, user_request, mall_link): 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) + LOG.info(f"I found {len(shop_info)} shops") + LOG.info(f"shop list: {shop_info}") if len(shop_info) == 0: self.speak_dialog("shop_not_found") user_request = self.get_response('repeat') @@ -259,15 +266,21 @@ def find_shop(self, user_request, mall_link): self.speak_dialog('more_than_one') # ask for the way of selection: time, location, nothing sorting_selection = self.get_response('Do you want to select' - 'store by time or location?') - if sorting_selection.contains('time'): - return self.shops_by_time_selection(shop_info) - elif sorting_selection.contains('location'): - return self.location_selection(shop_info) - elif sorting_selection.contains('no'): - return self.shop_by_time_selection(shop_info) - else: - return self.shop_by_time_selection(shop_info) + 'store by work hours or location?') + if sorting_selection: + LOG.info(f'Users answer on sorting options: {sorting_selection}') + if 'time' in sorting_selection or 'hour' in sorting_selection: + LOG.info('Time sorting selected') + return self.shops_by_time_selection(shop_info) + elif 'location' in sorting_selection: + LOG.info('Location sorting selected') + return self.location_selection(shop_info) + elif 'no' in sorting_selection: + LOG.info('No sorting selected. Sorting by time on default.') + return self.shops_by_time_selection(shop_info) + else: + LOG.info('Nothing matched. Sorting by time on default.') + return self.shops_by_time_selection(shop_info) else: LOG.info(f"found shop {shop_info}") self.speak_shops(shop_info) diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 0ae3357b4725033406e602cd381491acd47ef830..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9723 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 jmDaX<-q$ZTCgm>p<_O_Dk)Tt=0bqNh#ka diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc deleted file mode 100644 index 77759b880d88415aa5d85909cb40d189aeefe703..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8157 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 Dz~ppz diff --git a/cached_stores.json b/cached_stores.json index d8e4975..d0def46 100644 --- a/cached_stores.json +++ b/cached_stores.json @@ -1 +1 @@ -{"Apple": {"name": "Apple", "hours": "10am \u2013 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/10327.svg"}} \ No newline at end of file +{"ABC Stores": [{"name": "ABC Stores", "hours": "9am - 9pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png"}, {"name": "ABC Stores", "hours": "10am - 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png"}], "Apple": [{"name": "Apple", "hours": "10am - 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/10327.svg"}]} \ No newline at end of file diff --git a/locale/en-us/found_shop.dialog b/locale/en-us/found_shop.dialog index 18d51bc..73f3567 100644 --- a/locale/en-us/found_shop.dialog +++ b/locale/en-us/found_shop.dialog @@ -1 +1 @@ -I found shop {{name}} with hours of {{hours}}. You can find this store on {{location}}. \ No newline at end of file +I found {{name}} with hours of {{hours}}. You can find this store on {{location}}. \ No newline at end of file diff --git a/locale/en-us/more_than_one.dialog b/locale/en-us/more_than_one.dialog index 77ccb2d..70532f4 100644 --- a/locale/en-us/more_than_one.dialog +++ b/locale/en-us/more_than_one.dialog @@ -1,2 +1 @@ -I found more than one shop in your request, please choose one from ... -Which of those you need? \ No newline at end of file +I found more than one shop in your request. \ 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 6ea1597..ccbf4d9 100644 --- a/locale/en-us/run_mall_parser.intent +++ b/locale/en-us/run_mall_parser.intent @@ -4,4 +4,5 @@ i am looking for {shop} location of {shop} where {shop} show {shop} -show me {shop} \ No newline at end of file +show me {shop} +find {shop} \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index a2034b3..761f362 100644 --- a/request_handling.py +++ b/request_handling.py @@ -67,20 +67,29 @@ def find_cached_stores(self, user_request: str): {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} ] """ - with open(self.caching_file, 'r') as readfile: + with open(self.caching_file, 'r', encoding='utf-8') as readfile: file_length = os.stat(self.caching_file).st_size + LOG.info(file_length) if file_length == 0: - LOG.info('No shop in cache') + LOG.info('Cache is empty') + readfile.close() return None, {} else: data = json.load(readfile) - found_key = [key for key in data.keys() if key in user_request or user_request in key] - if user_request is []: + LOG.info(data) + found_key = [key for key in data.keys() + if key.lower() in user_request.lower() + or user_request.lower() in key.lower()] + LOG.info(f'found key {found_key}') + if len(found_key) >=1 : + store_name = str(found_key[0]) + LOG.info(f'Shop exists {data[store_name]}') + readfile.close() + return data[store_name], data + else: LOG.info("Shop doesn't exist in cache") + readfile.close() return None, data - else: - LOG.info('Shop exists') - return data[found_key], data def caching_stores(self, data, store_info: list): """ @@ -100,17 +109,15 @@ def caching_stores(self, data, store_info: list): {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} ]} """ + LOG.info(f'data from JSON {data}') if data == {}: - for store in store_info: - data = {store['name']: store} - if data != {}: - new_store = {store['name']: store} - data.update(new_store) + data = {store_info[0]['name']: store_info} else: - new_store = {store_info['name']: store_info} - data.update(new_store) - with open(self.caching_file, 'w') as outfile: + data[store_info[0]['name']] = store_info + LOG.info(f'Updated {data}') + with open(self.caching_file, "w+", encoding='utf-8') as outfile: json.dump(data, outfile) + outfile.close() return store_info def existing_lang_check(user_lang: str, url): @@ -234,6 +241,7 @@ def get_shop_data(self, url, user_request): # search for store existence in cache found_shops, data = self.find_cached_stores(user_request) if found_shops is not None: + LOG.info(f"found_shops: {found_shops}") return found_shops else: # parsing mall web-page @@ -250,6 +258,7 @@ def get_shop_data(self, url, user_request): 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'I parsed: {found_shops}') if found_shops: # caching if shop was found self.caching_stores(data, found_shops) diff --git a/test/test_skill.py b/test/test_skill.py index 074e11f..19cfb3b 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -80,18 +80,19 @@ 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', + Message('test', {'utterance': 'find Apple', + 'shop': 'Apple', 'lang': 'en-us' }, {'context_key': 'MallParsing'})) - message = Message('test', {'utterance': 'where is apple', - 'shop': 'apple', - 'lang': 'en-us'}, - {'context_key': 'MallParsing'}) + + # message = Message('test', {'utterance': 'find ABC stores', + # 'shop': 'ABC stores', + # 'lang': 'en-us'}, + # {'context_key': 'MallParsing'}) - self.skill.user_request_handling(message) + # self.skill.user_request_handling(message) From 221d6e370dd787abcb85b771c21dfc7654d1382f Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Mon, 19 Sep 2022 10:25:43 -0700 Subject: [PATCH 09/23] Fix skill class reference in setup.py (#4) Addresses #3 Co-authored-by: Daniel McKnight --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d77597b..10759cd 100644 --- a/setup.py +++ b/setup.py @@ -29,10 +29,10 @@ from setuptools import setup from os import getenv, path, walk -SKILL_NAME = "malls-parser-skill" +SKILL_NAME = "skill-directory" SKILL_PKG = SKILL_NAME.replace('-', '_') # skill_id=package_name:SkillClass -PLUGIN_ENTRY_POINT = f'{SKILL_NAME}.neongeckocom={SKILL_PKG}:MallParsingSkill' +PLUGIN_ENTRY_POINT = f'{SKILL_NAME}.neongeckocom={SKILL_PKG}:DirectorySkill' def get_requirements(requirements_filename: str): From 3f051550d3ead600331ac66c9c6ed9a0ac4f4a7f Mon Sep 17 00:00:00 2001 From: NeonDaniel Date: Mon, 19 Sep 2022 17:26:29 +0000 Subject: [PATCH 10/23] Increment Version --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index e47fc1c..cc11eeb 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # 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.1a1" +__version__ = "0.0.1a2" From c53f98ff469565d87c431da31f4c9c5ec7fb9994 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 20 Sep 2022 09:30:45 -0400 Subject: [PATCH 11/23] local time, gui, caching fixes --- __init__.py | 17 ++++-- __pycache__/__init__.cpython-310.pyc | Bin 0 -> 10396 bytes __pycache__/request_handling.cpython-310.pyc | Bin 0 -> 9135 bytes cached_stores.json | 1 - request_handling.py | 61 ++++++++++++------- requirements/requirements.txt | 4 +- test/test_skill.py | 28 +++++---- 7 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/request_handling.cpython-310.pyc delete mode 100644 cached_stores.json diff --git a/__init__.py b/__init__.py index 830e095..03a54e7 100644 --- a/__init__.py +++ b/__init__.py @@ -111,7 +111,7 @@ def speak_shops(self, shop_info): 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}) LOG.info({"name": shop['name'], "hours": hours, "location": location}) - #self.gui.show_image(shop['logo']) + self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) def location_selection(self, shop_info): """ @@ -148,6 +148,7 @@ def open_shops_search(self, shop_info): """ open_shops = [] day_time, hour, min = self.request_handler.curent_time_extraction() + LOG.info(f"User's time {day_time, hour, min}") for shop in shop_info: parse_time = shop['hours'].split('-') LOG.info(f'Parse time {parse_time}') @@ -181,21 +182,28 @@ def time_calculation(self, shop_info, open): """ for shop in shop_info: work_time = shop['hours'] + LOG.info(f'work_time {work_time}') shop_name = shop['name'] day_time, hour, min = self.request_handler.curent_time_extraction() parse_time = work_time.split('-') + LOG.info(f'parse_time {parse_time}') 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 open: + if open is True: if day_time[1] == 'pm' and 0 >= (close_time - hour) <= 1: - self.speak(f'{shop_name} closes in {wait_min} minutes') + self.speak(f'{shop_name} closes in {wait_min} minutes.') + else: + self.speak(f'{shop_name} is open.') self.speak_shops([shop]) else: if day_time[1] == 'am' and hour < open_time: - self.speak(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') + if wait_h == 0: + self.speak(f'{shop_name} is closed now. Opens in {wait_min} minutes') + else: + self.speak(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') elif hour >= close_time: self.speak(f'{shop_name} is closed now. Shop opens at {open_time}') self.speak_shops([shop]) @@ -291,7 +299,6 @@ def find_shop(self, user_request, mall_link): 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)) diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..326748105c284fdf348fd92e80fe422f737be1ed GIT binary patch literal 10396 zcmcgyOK;>>b|zV@D%L|%wYpns+3hyR@mMNYt#;cO#8I4yJ$_HzvSIbWK}|axsxGCf z7K>D0vaA+EVFI*)0g{<4vdG3Wfi##|c#%~W$s)+F$cozl0lW^fX#_~Vb1unxsM|>p zAQI}5yu8oz{LZ-y7Z#ct{``OH_W$8^P5T#mnEo^I@FsrIM<|3QbYC0kJ$+>K44s}0 zzcMm=ChjZ3^sA#_2W>U@P7NWr{a(9Y3=ljHc`Y{tf$%|M;{Q#4BU~IY!2f1DB8Yt>-hM1%k|w+=mu^dMD^rWKOEiif?J|k%dM`JRip8d zCwYv_a-3l>j2%bTFl4qQ6~zlqWTqJQNC^nEvq9OrB8 z@vFB6;mEr+a^-O7-inCD5uQ-qJaT2^$(vkvtC(-==tPn@uHzS3D3)~#|4ltvoh95> z(c=OINnYQk?k}Q9wfou`_>1(D$-6)y1K0Icisq8cr@@3G#AT zT)|z|B+3q5f0%e%c#ep|ZR3h={%5i_x5r7#WJQjH=^3_^g7gjaF8=7AWQTYE3~plV zJ=O0U=a8IKJFBFi1L@|j_O-s#H6=C9%wez}f}<|>y@raeAv;uGA9+#a9(eQXzs3Zb zUJGP}l=U_A%6HS$O+8tk+s)JZ&2VsL4@57apF#(ii`d6eBh{H>$@Bk>cxMgfhd&Gs zv7@UPNv@#CcPDMCnB@%DZKFOS5i<3To?M&TLOJ9N-HC%${Fv_lh#R82V9I}`{Yp_E zTo`JHSP1b!aGj{^YQ!C3u4!kcsGh?Tt!bwQ*VS=XVg6}n)pPADjW|coaV<3-F(3JD zh3o5QRtmYMmgmMjks2FB{O3oAuaW$iJni<4Jn)%|cB2&@_lnqzx*b-JZ8ahUV_7&5X^EW`ScJYO-I z5Q#&~Jxoz66r7*x#Z%YX=~-@c=!`-MgMH#fS=$Z5cmNVH>lX4S zo1W~<2*TbHm_| zfq_(iNBjJ(Q-f6}H;d#HKx2IkN&%HK&#JJyUZ$& zZUeE_`|c6ZHmiaYzPHnDOi6r;8ZQ;|JBmG-L7Eqh_arF}iDD#JtX^Dgv!8+7vIdr2;sM+rx2A!&?05YL3sqVQs8_BP@5(;^e**rg z7msByr%kVI+B;$3*&84WEHj&g8Ou&PnW$^>>@E>w@1)daFv5q0hh*Em*70E2A2>{+ zuZ?fWqVM!Ng;xMl_Q5p2Tc48rSI|bX_+KN!Ipt{1-iaD0l5bEOb_%9)-oK9uIA~%} zuS(sbIj@i{m%3CA7^73PjH1(S!YRWsuydd?`OchsxY&C}_9@;dy~J0O&;CR|jM=0h-97=dcd`^Xz|Byqkt(`4Dj zMw0fA*g+^ys4V3R;9=7GgyKt8{kZnyKYhDss3Su1jySS6ck!V@XJp!c#04gfq@tynavI?y#)oJ>k90CQ#bw$Dqzp4 zz=a|Dq|U|PB5oS^vGsolv0|L`_hj7(7oD2y4Gh9Fx!fa#g8C}71bV?*Qai1mLUw^c zVf#-T+@qQ{iXOSNBN>!72ph4qH)m=ad2K6gruAz;PS>>4wrHjnt_#3Vt+6IkXd2py zHW2SfSZRw|vc6QMBO1_}5$Q|NhVQY`D)84rx`1-=SL~4jtE^wjcY@$2tsJnIdSK+E zcQE>I(|X#3{{1#>3k%PGUG}`hBQED7lJ`?HT}s=S>2m&-bv0PTD6PY){JXN%m9z<( zu(w3r)ZF4Kw0gx|1Y-)}w%>OB{@8a*q$h^^`*4}aIuO!3_Mq$vAK8#%5@VM4vOD=o zQX=~}l!u6E6pAZVyYzI$V%~INme5`bFM+aLOn28U~{};_ua^w;KBQ2 z*tL8Z>P;P|9Wop~t$~K4aSI&FJtk8s2Va5%G%S}$6k+1DjW5rN%OLOv=uH_R(@hlZTS{VnEU}1Fku>7`ELkq+|3N2{?-&5 zlSaX?E!^gW+sX`gl&n2L4rM88Xbt#t=-I;CcgH?VOM!>C>}iZuc3_+HEecQX z{|EYBjz=o=gui)na8b9P6iSoOsJ)isC#x);#n#L1*{m*i-$NWqJW;8eJGjd#J|vGq z!aRw?U`M`3jpl@Cm5D)6pLt%6{m*C{5z6kgntGf5jpU^{DS1lQ8JI$_jeaaFKKM++ z5+e|uQ1?&`vVc=#))W;{b}7@^82uXI8mg3++tgWq-+Iw)O(q;$m-Rk{m2z#|g)*he;tqf$yTvQE)?#YPv?mJ>jC1?l+y5N5eV6FdRDf1l2( z>?u{x69tsgw~uDBRJqkyj|wkLn?tr##nzk_DArR>-gko{h?>b|Y$DZA;2NpotFjNp zWRv}3)&K^I;aZXOqaZ#~CJY;s9+D4JJyg&Z*|%pYPBb7I1ChYy4ZLzb@1fTpGZhhQ z!~eoI;K*)EReKxVnYEzf6LT|@6&a(Fv4TkPTVQ5pWHz-dOo zY>*}(gyv)9!^y&n>o0N)vE1!?x^M#$+>p@7HBF>gB^FphfuZ0O9 z*&+>eg9saXdT;Pnt(wx@rJ~GipVX~fJ8Opm>$wNC3rtJSJ%&{0fzWIh6*f;+{ZXF9 zW=@mnm4aI+Ht9Yk*vfI9GLNG4{P;V`%@1vN1iK<5QU~TCPe$g3GWya!p)ex%>xuPY z&h(_F8YeB@D~b5F&3LxANHq>9jf^rKX4RBEV8`8CppzX3In4lP*k1CzcS4$glcW>e z0|G(1;QJ?%42xJM|Nb!f&W{i)0k$f(L4FE-4msN$ZQGei!MXBe ztt4?VMERahy^~jVz^~j`6mI~r@|pskUF?Au$nCNiBv@YWQtc?nD4 z9HdHCF3flpxl3<3m3f!$-lT#NxysSY4XUuIpuA7-QofH#wsRgBHOQ*5+xk~@hzRj7 zKEKGPTa>(90kCcZUekFOUk%gfFyK}vU`5%)38eXNoh3?gF4D>4{PA=-%>~$0V(h=f z+&EI_qh)mrjT}z}d5$gtf~SHh{&*lY^224$gW^D$@bL~{09Rm=IPA=bN zsA1+1q?+n9j`|FNKfkE%uzV*AyY%G+lJVrFJW0_&vao|w-= z*i`T)NT$?L#hY|Tkq`$#l1&1O9f=KpF_SIipFH&eE%43MBrLi_O|DQ;NAX?!A{&K; z4}PqAt%^O?EYrGTb*yD;75}f|`d+8Qt@~&=^9LHK(5Hr*SbK!CfB7tKyE?a@nwA$p z(`M~HzF&$xhrhDgAu1cFuIch8)UnyS9^lJ62cXc8w$#^ITU^8UlG?Jy^)45mL*^w)&A^2`;Kh=KPe)+!u2^XQj 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..8135e80aa8d29ef07726a7658296fe1bb9332be1 GIT binary patch literal 9135 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 literal 0 HcmV?d00001 diff --git a/cached_stores.json b/cached_stores.json deleted file mode 100644 index d0def46..0000000 --- a/cached_stores.json +++ /dev/null @@ -1 +0,0 @@ -{"ABC Stores": [{"name": "ABC Stores", "hours": "9am - 9pm", "location": "Street Level 1, near Centerstage", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png"}, {"name": "ABC Stores", "hours": "10am - 8pm", "location": "Street Level 1, in the Ewa Wing", "logo": "https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png"}], "Apple": [{"name": "Apple", "hours": "10am - 8pm", "location": "Mall Level 2, near Macy's", "logo": "https://placewise.imgix.net/images/api/stores/10327.svg"}]} \ No newline at end of file diff --git a/request_handling.py b/request_handling.py index 761f362..731185d 100644 --- a/request_handling.py +++ b/request_handling.py @@ -39,20 +39,20 @@ import re import os +import os, sys, stat from os import path import json from datetime import datetime -import pytz class RequestHandler(): def __init__(self) -> None: self.caching_file = path.join(path.abspath(path.dirname(__file__)), - 'cached_stores.json') + r'cached_stores.json') - def find_cached_stores(self, user_request: str): + def find_cached_stores(self, user_request: str, url): """ Check shop name existence in cache keys Args: @@ -67,14 +67,12 @@ def find_cached_stores(self, user_request: str): {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} ] """ - with open(self.caching_file, 'r', encoding='utf-8') as readfile: - file_length = os.stat(self.caching_file).st_size - LOG.info(file_length) - if file_length == 0: - LOG.info('Cache is empty') - readfile.close() - return None, {} - else: + if os.path.isfile(self.caching_file) == False: + LOG.info("Cache file doesn't exist") + self.caching_stores_in_mall(url) + self.find_cached_stores(user_request, url) + else: + with open(self.caching_file, 'r', encoding='utf-8') as readfile: data = json.load(readfile) LOG.info(data) found_key = [key for key in data.keys() @@ -84,13 +82,36 @@ def find_cached_stores(self, user_request: str): if len(found_key) >=1 : store_name = str(found_key[0]) LOG.info(f'Shop exists {data[store_name]}') - readfile.close() return data[store_name], data else: LOG.info("Shop doesn't exist in cache") - readfile.close() return None, data + def caching_stores_in_mall(self, url): + shop_cache = {} + 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') + 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} + if name in shop_cache.keys(): + shop_cache[name].append(shop_data) + else: + shop_cache[name] = [shop_data] + # os.umask(0) + # os.open(self.caching_file, 777) + # os.chmod(self.caching_file, 777) + # with open(self.caching_file, "w+", encoding='utf-8') as outfile: + with open(self.caching_file, "w", encoding='utf-8') as outfile: + LOG.info(f'Writable {outfile.writable()}') + json.dump(shop_cache, outfile, ensure_ascii=False) + os.chmod(self.caching_file, 777) + LOG.info("Created mall's cache") + + def caching_stores(self, data, store_info: list): """ Saves dictionary to JSON file @@ -110,13 +131,10 @@ def caching_stores(self, data, store_info: list): ]} """ LOG.info(f'data from JSON {data}') - if data == {}: - data = {store_info[0]['name']: store_info} - else: - data[store_info[0]['name']] = store_info + data[store_info[0]['name']] = store_info LOG.info(f'Updated {data}') with open(self.caching_file, "w+", encoding='utf-8') as outfile: - json.dump(data, outfile) + json.dump(data, outfile, ensure_ascii=False) outfile.close() return store_info @@ -150,7 +168,8 @@ def curent_time_extraction(self): hour (int): current hour min (int): current minute """ - now = datetime.utcnow().replace(tzinfo=pytz.utc).strftime("%H:%M %p") + now = datetime.today().strftime("%H:%M %p") + LOG.info(f'now {now}') day_time = now.lower().split(' ') exact_time = day_time[0].split(':') hour, min = int(exact_time[0]), int(exact_time[1]) @@ -239,8 +258,8 @@ def get_shop_data(self, url, user_request): : found_shops (list): found shops' info """ # search for store existence in cache - found_shops, data = self.find_cached_stores(user_request) - if found_shops is not None: + found_shops, data = self.find_cached_stores(user_request, url) + if found_shops: LOG.info(f"found_shops: {found_shops}") return found_shops else: diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 1736d55..92d68cd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -3,5 +3,5 @@ neon-utils~=1.0 bs4 requests ovos-lingua-franca -pytz -datetime \ No newline at end of file + +datetime diff --git a/test/test_skill.py b/test/test_skill.py index 19cfb3b..d4fbc9a 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -80,19 +80,23 @@ 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': 'find Apple', - 'shop': 'Apple', - 'lang': 'en-us' - }, - {'context_key': 'MallParsing'})) - - - # message = Message('test', {'utterance': 'find ABC stores', - # 'shop': 'ABC stores', - # 'lang': 'en-us'}, - # {'context_key': 'MallParsing'}) + Message('test', {'utterance': 'find ABC stores', + 'shop': 'ABC stores', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + ) + # Message('test', {'utterance': 'find Jams World', + # 'shop': 'Jams World', + # 'lang': 'en-us' + # }, + # {'context_key': 'MallParsing'})) + + message = Message('test', {'utterance': 'find ABC stores', + 'shop': 'ABC stores', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) - # self.skill.user_request_handling(message) + self.skill.user_request_handling(message) From 84ef63923a7ab874a38f75d435bf6c492cc68952 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 20 Sep 2022 13:32:32 +0000 Subject: [PATCH 12/23] Update skill.json --- skill.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/skill.json b/skill.json index 7014e3e..1394d21 100644 --- a/skill.json +++ b/skill.json @@ -1,6 +1,6 @@ { "title": "{Malls parser skill neon}", - "url": "https://github.com/NeonGeckoCom/malls-parser-skill", + "url": "https://github.com/NeonGeckoCom/skill-directory", "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", @@ -18,7 +18,6 @@ "neon-utils~=1.0", "numpy", "ovos-lingua-franca", - "pytz", "requests" ], "system": {}, @@ -44,7 +43,7 @@ "NeonGeckoCom", "NeonMariia" ], - "skillname": "malls-parser-skill", + "skillname": "skill-directory", "authorname": "NeonGeckoCom", "foldername": null } \ No newline at end of file From e804fb9b37eaca6e3d6f787060e5082877a46287 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 21 Sep 2022 09:45:48 -0400 Subject: [PATCH 13/23] cache file creation fixes --- __init__.py | 22 +++++++---- __pycache__/__init__.cpython-310.pyc | Bin 10396 -> 0 bytes __pycache__/request_handling.cpython-310.pyc | Bin 9135 -> 0 bytes request_handling.py | 37 +++++++++---------- 4 files changed, 33 insertions(+), 26 deletions(-) delete mode 100644 __pycache__/__init__.cpython-310.pyc delete mode 100644 __pycache__/request_handling.cpython-310.pyc diff --git a/__init__.py b/__init__.py index 03a54e7..4d1d567 100644 --- a/__init__.py +++ b/__init__.py @@ -27,6 +27,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from os import path from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler @@ -41,6 +42,8 @@ def __init__(self): self.request_handler = RequestHandler() self.cache = dict() self.url = "https://www.alamoanacenter.com/en/directory/" + from os import path + self.path = 'cached_stores.json' def initialize(self): @@ -61,8 +64,7 @@ def mall_link(self): def user_request_handling(self, message): LOG.info(f"Message is {message.data}") - if message.data == {}: - self.speak('Message is empty') + if message.data == {} or message is None: return None, None else: request_lang = message.data['lang'].split('-')[0] @@ -192,7 +194,7 @@ def time_calculation(self, shop_info, open): # time left wait_h = open_time - hour - 1 wait_min = 60 - min - if open is True: + if open: if day_time[1] == 'pm' and 0 >= (close_time - hour) <= 1: self.speak(f'{shop_name} closes in {wait_min} minutes.') else: @@ -263,7 +265,8 @@ def find_shop(self, user_request, 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) + file_path = self.file_system.path + shop_info = self.request_handler.get_shop_data(mall_link, user_request, file_path) LOG.info(f"I found {len(shop_info)} shops") LOG.info(f"shop list: {shop_info}") if len(shop_info) == 0: @@ -300,7 +303,7 @@ def find_shop(self, user_request, mall_link): def execute(self, user_request, mall_link): count = 0 LOG.info('Start execute') - while count < 3: + while count < 3 and user_request is not None and mall_link is not None: LOG.info(str(user_request)) LOG.info(str(mall_link)) new_count, user_request = self.find_shop(user_request, mall_link) @@ -314,8 +317,11 @@ def execute(self, user_request, mall_link): return None def _start_mall_parser_prompt(self, message): + if self.neon_in_request(message): LOG.info('Prompting Mall parsing start') self.make_active() + self.path = self.file_system.path + LOG.info(self.path) if message is not None: LOG.info('new message'+str(message)) user_request, mall_link = self.user_request_handling(message) @@ -326,8 +332,10 @@ def _start_mall_parser_prompt(self, message): return else: self.speak_dialog('finished') - else: - self.speak_dialog('finished') + else: + self.speak_dialog('finished') + else: + return diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 326748105c284fdf348fd92e80fe422f737be1ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10396 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 diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc deleted file mode 100644 index 8135e80aa8d29ef07726a7658296fe1bb9332be1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9135 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 diff --git a/request_handling.py b/request_handling.py index 731185d..5e23ffb 100644 --- a/request_handling.py +++ b/request_handling.py @@ -26,6 +26,7 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +import pickle from urllib.error import HTTPError import requests import bs4 @@ -49,10 +50,9 @@ class RequestHandler(): def __init__(self) -> None: - self.caching_file = path.join(path.abspath(path.dirname(__file__)), - r'cached_stores.json') + self.caching_file = '' - def find_cached_stores(self, user_request: str, url): + def find_cached_stores(self, user_request: str, url, file_path): """ Check shop name existence in cache keys Args: @@ -69,12 +69,11 @@ def find_cached_stores(self, user_request: str, url): """ if os.path.isfile(self.caching_file) == False: LOG.info("Cache file doesn't exist") - self.caching_stores_in_mall(url) - self.find_cached_stores(user_request, url) + self.caching_stores_in_mall(file_path, url) + self.find_cached_stores(user_request, url, file_path) else: with open(self.caching_file, 'r', encoding='utf-8') as readfile: data = json.load(readfile) - LOG.info(data) found_key = [key for key in data.keys() if key.lower() in user_request.lower() or user_request.lower() in key.lower()] @@ -87,7 +86,9 @@ def find_cached_stores(self, user_request: str, url): LOG.info("Shop doesn't exist in cache") return None, data - def caching_stores_in_mall(self, url): + def caching_stores_in_mall(self, file_path, url): + self.caching_file = file_path+'/cached_stores.json' + LOG.info(self.caching_file) shop_cache = {} soup = self.parse(url) for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): @@ -101,12 +102,8 @@ def caching_stores_in_mall(self, url): shop_cache[name].append(shop_data) else: shop_cache[name] = [shop_data] - # os.umask(0) - # os.open(self.caching_file, 777) - # os.chmod(self.caching_file, 777) - # with open(self.caching_file, "w+", encoding='utf-8') as outfile: - with open(self.caching_file, "w", encoding='utf-8') as outfile: - LOG.info(f'Writable {outfile.writable()}') + with open(self.caching_file, + 'w+') as outfile: json.dump(shop_cache, outfile, ensure_ascii=False) os.chmod(self.caching_file, 777) LOG.info("Created mall's cache") @@ -133,9 +130,9 @@ def caching_stores(self, data, store_info: list): LOG.info(f'data from JSON {data}') data[store_info[0]['name']] = store_info LOG.info(f'Updated {data}') - with open(self.caching_file, "w+", encoding='utf-8') as outfile: - json.dump(data, outfile, ensure_ascii=False) - outfile.close() + with open(self.caching_file, + 'w+') as outfile: + json.dump(data, outfile, ensure_ascii=False) return store_info def existing_lang_check(user_lang: str, url): @@ -241,7 +238,7 @@ def parse(self, url): LOG.info("Failed url parsing") - def get_shop_data(self, url, user_request): + def get_shop_data(self, url, user_request, file_path): """ Check existence of user's request store in cache if shop was found returns list with shop info, @@ -258,7 +255,9 @@ def get_shop_data(self, url, user_request): : found_shops (list): found shops' info """ # search for store existence in cache - found_shops, data = self.find_cached_stores(user_request, url) + LOG.info(file_path) + found_shops, data = self.find_cached_stores(user_request, url, file_path) + LOG.info(found_shops) if found_shops: LOG.info(f"found_shops: {found_shops}") return found_shops @@ -280,7 +279,7 @@ def get_shop_data(self, url, user_request): LOG.info(f'I parsed: {found_shops}') if found_shops: # caching if shop was found - self.caching_stores(data, found_shops) + found_shops = self.caching_stores(data, found_shops) return found_shops else: # return empty list From 33cb620dcdbed3375c94f02aa6ff3fc81c6be710 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Wed, 21 Sep 2022 17:12:08 -0400 Subject: [PATCH 14/23] Nonetype error fixes --- __init__.py | 5 +++-- request_handling.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/__init__.py b/__init__.py index 4d1d567..cdb12d0 100644 --- a/__init__.py +++ b/__init__.py @@ -189,8 +189,8 @@ def time_calculation(self, shop_info, open): day_time, hour, min = self.request_handler.curent_time_extraction() parse_time = work_time.split('-') LOG.info(f'parse_time {parse_time}') - open_time = int(re.sub('[^\d+]', '', parse_time[0])) - close_time = int(re.sub('[^\d+]', '', parse_time[1])) + open_time = int(re.sub(r'[^\d+]', '', parse_time[0])) + close_time = int(re.sub(r'[^\d+]', '', parse_time[1])) # time left wait_h = open_time - hour - 1 wait_min = 60 - min @@ -299,6 +299,7 @@ def find_shop(self, user_request, mall_link): else: LOG.info(str(None)) return 3, None + return 3, None def execute(self, user_request, mall_link): count = 0 diff --git a/request_handling.py b/request_handling.py index 5e23ffb..c78f8f5 100644 --- a/request_handling.py +++ b/request_handling.py @@ -70,7 +70,7 @@ def find_cached_stores(self, user_request: str, url, file_path): if os.path.isfile(self.caching_file) == False: LOG.info("Cache file doesn't exist") self.caching_stores_in_mall(file_path, url) - self.find_cached_stores(user_request, url, file_path) + return self.find_cached_stores(user_request, url, file_path) else: with open(self.caching_file, 'r', encoding='utf-8') as readfile: data = json.load(readfile) From be9d9893f9cd3a6a3ea80dce6d200844fbd7158b Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Thu, 22 Sep 2022 08:23:38 -0400 Subject: [PATCH 15/23] time parsing fixes --- __init__.py | 44 +++++++++++++++----------- requirements/requirements.txt | 1 - test/test_skill.py | 59 +++++++++++++++++++++++------------ 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/__init__.py b/__init__.py index cdb12d0..b9848f7 100644 --- a/__init__.py +++ b/__init__.py @@ -138,7 +138,7 @@ def location_selection(self, shop_info): self.speak_shops(shop_info) return 3, None - def open_shops_search(self, shop_info): + def open_shops_search(self, shop_info, day_time, hour, min): """ Selects open shops. Collects the list of open shops else return empty list. @@ -149,20 +149,22 @@ def open_shops_search(self, shop_info): shop_info (list): open shops """ open_shops = [] - day_time, hour, min = self.request_handler.curent_time_extraction() LOG.info(f"User's time {day_time, hour, min}") for shop in shop_info: - parse_time = shop['hours'].split('-') - LOG.info(f'Parse time {parse_time}') - open_time = int(re.sub('[^\d+]', '', parse_time[0])) - close_time = int(re.sub('[^\d+]', '', parse_time[1])) + parse_time = re.findall(r'(\d+)+[am|pm]', shop['hours']) + LOG.info(f'Parsed time {parse_time}') + open_time = int(parse_time[0]) + close_time = int(parse_time[1]) if open_time <= hour < close_time: open_shops.append(shop) + elif day_time[1] == 'am' and open_time <= hour: + open_shops.append(shop) return open_shops - def time_calculation(self, shop_info, open): + def time_calculation(self, shop_info, open, day_time, hour, min): # add logic if shop opens and closes not at am-pm time period + # change to speak dialog """ Calculates time difference between user's current time and shop working hours. @@ -184,30 +186,37 @@ def time_calculation(self, shop_info, open): """ for shop in shop_info: work_time = shop['hours'] + normalized_time = re.findall(r'(\d+)[am|pm]', work_time) + open_time = int(normalized_time[0]) + close_time = int(normalized_time[1]) LOG.info(f'work_time {work_time}') shop_name = shop['name'] - day_time, hour, min = self.request_handler.curent_time_extraction() parse_time = work_time.split('-') LOG.info(f'parse_time {parse_time}') - open_time = int(re.sub(r'[^\d+]', '', parse_time[0])) - close_time = int(re.sub(r'[^\d+]', '', parse_time[1])) # time left wait_h = open_time - hour - 1 wait_min = 60 - min if open: - if day_time[1] == 'pm' and 0 >= (close_time - hour) <= 1: + if day_time[1] == 'pm' and 0 < (close_time - hour) <= 1: + LOG.info(f'{shop_name} closes in {wait_min} minutes.') self.speak(f'{shop_name} closes in {wait_min} minutes.') else: + LOG.info(f'{shop_name} is open.') self.speak(f'{shop_name} is open.') + LOG.info([shop]) self.speak_shops([shop]) else: if day_time[1] == 'am' and hour < open_time: if wait_h == 0: + LOG.info(f'{shop_name} is closed now. Opens in {wait_min} minutes') self.speak(f'{shop_name} is closed now. Opens in {wait_min} minutes') else: + LOG.info(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') self.speak(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') elif hour >= close_time: + LOG.info(f'{shop_name} is closed now. Shop opens at {open_time}') self.speak(f'{shop_name} is closed now. Shop opens at {open_time}') + LOG.info([shop]) self.speak_shops([shop]) return 3, None @@ -223,11 +232,12 @@ def shops_by_time_selection(self, shop_info): shop_info (list): open shops """ LOG.info(f"Shop by time selection {shop_info}") - open_shops = self.open_shops_search(shop_info) + day_time, hour, min = self.request_handler.curent_time_extraction() + open_shops = self.open_shops_search(shop_info, day_time, hour, min) if len(open_shops) >= 1: - return self.time_calculation(open_shops, True) + return self.time_calculation(open_shops, True, day_time, hour, min) else: - return self.time_calculation(shop_info, False) + return self.time_calculation(shop_info, False, day_time, hour, min) def find_shop(self, user_request, mall_link): """ @@ -260,8 +270,8 @@ def find_shop(self, user_request, mall_link): Location and time sorting functions return 3, None to stop current shop search. """ - LOG.info(str(user_request)) - LOG.info(str(mall_link)) + LOG.info(f'user_request {user_request}') + LOG.info(f'mall_link {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") @@ -305,8 +315,6 @@ def execute(self, user_request, mall_link): count = 0 LOG.info('Start execute') while count < 3 and user_request is not None and mall_link is not None: - 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() diff --git a/requirements/requirements.txt b/requirements/requirements.txt index 92d68cd..5a2a1bd 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -3,5 +3,4 @@ neon-utils~=1.0 bs4 requests ovos-lingua-franca - datetime diff --git a/test/test_skill.py b/test/test_skill.py index d4fbc9a..42baef6 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -76,28 +76,47 @@ def setUp(self): def tearDownClass(cls) -> None: shutil.rmtree(cls.test_fs) - 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': 'find ABC stores', - 'shop': 'ABC stores', - 'lang': 'en-us'}, - {'context_key': 'MallParsing'}) - ) - # Message('test', {'utterance': 'find Jams World', - # 'shop': 'Jams World', - # 'lang': 'en-us' - # }, - # {'context_key': 'MallParsing'})) + # 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': 'find ABC stores', + # 'shop': 'ABC stores', + # 'lang': 'en-us'}, + # {'context_key': 'MallParsing'}) + # ) + # # Message('test', {'utterance': 'find Jams World', + # # 'shop': 'Jams World', + # # 'lang': 'en-us' + # # }, + # # {'context_key': 'MallParsing'})) - message = Message('test', {'utterance': 'find ABC stores', - 'shop': 'ABC stores', - 'lang': 'en-us'}, - {'context_key': 'MallParsing'}) + # message = Message('test', {'utterance': 'find ABC stores', + # 'shop': 'ABC stores', + # 'lang': 'en-us'}, + # {'context_key': 'MallParsing'}) - self.skill.user_request_handling(message) - + # self.skill.user_request_handling(message) + + def test_en_time_extraction(self): + shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + day_time, hour, min = ['10:15', 'am'], 10, 15 + result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + self.assertEqual(shop_info, result_shops) + + day_time, hour, min = ['9:15', 'am'], 9, 15 + result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) + self.assertEqual(shop_info[0], result_shops[0]) + + def test_en_time_extraction(self): + shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + # day_time, hour, min = ['10:15', 'am'], 10, 15 + # print(self.skill.time_calculation(shop_info, True, day_time, hour, min)) + + # day_time, hour, min = ['11:15', 'pm'], 11, 15 + # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) if __name__ == '__main__': From 26e7813c2bee3d9f52f9d89e773d314ad1a517a2 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Fri, 23 Sep 2022 08:05:06 -0400 Subject: [PATCH 16/23] adding tests, current time fixes --- __init__.py | 1 + request_handling.py | 3 ++- test/test_skill.py | 20 ++++++++------------ 3 files changed, 11 insertions(+), 13 deletions(-) diff --git a/__init__.py b/__init__.py index b9848f7..eb1d2b0 100644 --- a/__init__.py +++ b/__init__.py @@ -233,6 +233,7 @@ def shops_by_time_selection(self, shop_info): """ LOG.info(f"Shop by time selection {shop_info}") day_time, hour, min = self.request_handler.curent_time_extraction() + # day_time, hour, min = ['11:15', 'pm'], 11, 15 open_shops = self.open_shops_search(shop_info, day_time, hour, min) if len(open_shops) >= 1: return self.time_calculation(open_shops, True, day_time, hour, min) diff --git a/request_handling.py b/request_handling.py index c78f8f5..ccebaaf 100644 --- a/request_handling.py +++ b/request_handling.py @@ -165,7 +165,8 @@ def curent_time_extraction(self): hour (int): current hour min (int): current minute """ - now = datetime.today().strftime("%H:%M %p") + now = datetime.now().time().strftime("%I:%M %p") + # now = datetime.today().strftime("%H:%M %p") LOG.info(f'now {now}') day_time = now.lower().split(' ') exact_time = day_time[0].split(':') diff --git a/test/test_skill.py b/test/test_skill.py index 42baef6..458dc57 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -85,17 +85,11 @@ def tearDownClass(cls) -> None: # 'lang': 'en-us'}, # {'context_key': 'MallParsing'}) # ) - # # Message('test', {'utterance': 'find Jams World', - # # 'shop': 'Jams World', - # # 'lang': 'en-us' - # # }, - # # {'context_key': 'MallParsing'})) # message = Message('test', {'utterance': 'find ABC stores', # 'shop': 'ABC stores', # 'lang': 'en-us'}, # {'context_key': 'MallParsing'}) - # self.skill.user_request_handling(message) def test_en_time_extraction(self): @@ -109,13 +103,15 @@ def test_en_time_extraction(self): result_shops = self.skill.open_shops_search(shop_info, day_time, hour, min) self.assertEqual(shop_info[0], result_shops[0]) - def test_en_time_extraction(self): - shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, - {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] - # day_time, hour, min = ['10:15', 'am'], 10, 15 - # print(self.skill.time_calculation(shop_info, True, day_time, hour, min)) + # def test_en_time_extraction(self): + # shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 8pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}, + # {'name': 'ABC Stores', 'hours': '10am – 9pm', 'location': 'Street Level 1, in the Ewa Wing', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937946329-abcstores.png'}] + + # day_time, hour, min = ['9:15', 'pm'], 9, 15 + # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) - # day_time, hour, min = ['11:15', 'pm'], 11, 15 + # day_time, hour, min = ['8:15', 'pm'], 8, 15 # print(self.skill.time_calculation(shop_info, False, day_time, hour, min)) From b90f345987e97c50804f61aa42cafdcf1a1f7cdc Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Mon, 26 Sep 2022 11:05:23 -0400 Subject: [PATCH 17/23] time pronunciation changes, docstring to functions, new dialogs and vocabs --- .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 ------ __init__.py | 101 +++++++++++------- .../en-us/{ => dialog}/all_locations.dialog | 0 locale/en-us/{ => dialog}/another_shop.dialog | 0 locale/en-us/{ => dialog}/ask_more.dialog | 0 locale/en-us/dialog/choose_selection.dialog | 1 + locale/en-us/dialog/closed_now.dialog | 1 + locale/en-us/dialog/closing_minutes.dialog | 1 + locale/en-us/{ => dialog}/finished.dialog | 0 locale/en-us/{ => dialog}/found_shop.dialog | 0 .../en-us/{ => dialog}/more_than_one.dialog | 0 locale/en-us/{ => dialog}/no_lang.dialog | 0 .../{ => dialog}/no_shop_on_level.dialog | 0 .../en-us/{ => dialog}/no_shop_request.dialog | 0 locale/en-us/dialog/open_now.dialog | 1 + locale/en-us/dialog/opening_hours.dialog | 1 + locale/en-us/dialog/opening_minutes.dialog | 1 + .../en-us/{ => dialog}/run_mall_parser.intent | 0 .../en-us/{ => dialog}/shop_by_floor.dialog | 0 locale/en-us/dialog/shop_not_found.dialog | 2 + .../en-us/{ => dialog}/start_parsing.dialog | 0 locale/en-us/{ => dialog}/stop.dialog | 0 .../{ => dialog}/unexpected_error.dialog | 0 locale/en-us/{ => dialog}/which_floor.dialog | 0 locale/en-us/repeat.dialog | 1 - locale/en-us/shop_not_found.dialog | 2 - locale/en-us/vocab/location.voc | 3 + locale/en-us/vocab/no.voc | 5 + locale/en-us/vocab/time.voc | 4 + locale/en-us/{ => vocab}/yes.voc | 0 request_handling.py | 29 +++-- test/test_skill.py | 28 ++--- 36 files changed, 118 insertions(+), 261 deletions(-) delete mode 100644 .github/workflows/publish_release.yml delete mode 100644 .github/workflows/publish_test_build.yml delete mode 100644 .github/workflows/pull_master.yml delete mode 100644 .github/workflows/skill_tests.yml delete mode 100644 .github/workflows/update_skill_json.yml rename locale/en-us/{ => dialog}/all_locations.dialog (100%) rename locale/en-us/{ => dialog}/another_shop.dialog (100%) rename locale/en-us/{ => dialog}/ask_more.dialog (100%) create mode 100644 locale/en-us/dialog/choose_selection.dialog create mode 100644 locale/en-us/dialog/closed_now.dialog create mode 100644 locale/en-us/dialog/closing_minutes.dialog rename locale/en-us/{ => dialog}/finished.dialog (100%) rename locale/en-us/{ => dialog}/found_shop.dialog (100%) rename locale/en-us/{ => dialog}/more_than_one.dialog (100%) rename locale/en-us/{ => dialog}/no_lang.dialog (100%) rename locale/en-us/{ => dialog}/no_shop_on_level.dialog (100%) rename locale/en-us/{ => dialog}/no_shop_request.dialog (100%) create mode 100644 locale/en-us/dialog/open_now.dialog create mode 100644 locale/en-us/dialog/opening_hours.dialog create mode 100644 locale/en-us/dialog/opening_minutes.dialog rename locale/en-us/{ => dialog}/run_mall_parser.intent (100%) rename locale/en-us/{ => dialog}/shop_by_floor.dialog (100%) create mode 100644 locale/en-us/dialog/shop_not_found.dialog rename locale/en-us/{ => dialog}/start_parsing.dialog (100%) rename locale/en-us/{ => dialog}/stop.dialog (100%) rename locale/en-us/{ => dialog}/unexpected_error.dialog (100%) rename locale/en-us/{ => dialog}/which_floor.dialog (100%) delete mode 100644 locale/en-us/repeat.dialog delete mode 100644 locale/en-us/shop_not_found.dialog create mode 100644 locale/en-us/vocab/location.voc create mode 100644 locale/en-us/vocab/no.voc create mode 100644 locale/en-us/vocab/time.voc rename locale/en-us/{ => vocab}/yes.voc (100%) diff --git a/.github/workflows/publish_release.yml b/.github/workflows/publish_release.yml deleted file mode 100644 index 12daf2c..0000000 --- a/.github/workflows/publish_release.yml +++ /dev/null @@ -1,40 +0,0 @@ -# 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 deleted file mode 100644 index 33c3b4c..0000000 --- a/.github/workflows/publish_test_build.yml +++ /dev/null @@ -1,39 +0,0 @@ -# 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 deleted file mode 100644 index d62646b..0000000 --- a/.github/workflows/pull_master.yml +++ /dev/null @@ -1,19 +0,0 @@ -# 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 deleted file mode 100644 index f03c60b..0000000 --- a/.github/workflows/skill_tests.yml +++ /dev/null @@ -1,67 +0,0 @@ -# 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 deleted file mode 100644 index 5dc9797..0000000 --- a/.github/workflows/update_skill_json.yml +++ /dev/null @@ -1,33 +0,0 @@ -# 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/__init__.py b/__init__.py index eb1d2b0..d9bf819 100644 --- a/__init__.py +++ b/__init__.py @@ -31,6 +31,10 @@ from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler +# from .request_handling import existing_lang_check, get_shop_data,\ +# shop_selection_by_floors,\ +# location_format,\ +# curent_time_extraction import re @@ -42,7 +46,6 @@ def __init__(self): self.request_handler = RequestHandler() self.cache = dict() self.url = "https://www.alamoanacenter.com/en/directory/" - from os import path self.path = 'cached_stores.json' @@ -67,7 +70,7 @@ def user_request_handling(self, message): if message.data == {} or message is None: return None, None else: - request_lang = message.data['lang'].split('-')[0] + request_lang = self.lang.split('-')[0] user_request = message.data['shop'] LOG.info(f"{self.mall_link()}") LOG.info(str(request_lang)) @@ -82,27 +85,38 @@ def user_request_handling(self, message): return None, None def start_again(self): + """ + Asks yes/no question whether user wants to + get another shop info, after Neon gave the + information about previously selected shop. + If user's answer 'yes': asks what shop is + needed. Returns user's answer. + If 'no', speaks corresponding dialog. + If some other answer, speaks corresponding + dialog + Returns: + None (if no shop in request, if user's + answer is 'no', if user gives some other + answer) + """ start_again = self.ask_yesno("ask_more") if start_again == "yes": another_shop = self.get_response('another_shop') if another_shop is not None: LOG.info(f'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 + return None def speak_shops(self, shop_info): """ Speaks shop info that was found. Substitutes time format for better pronunciation. speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) - Shows shop label image. + Shows shop label image in gui. Args: shop_info (list): found shops on user's request @@ -110,7 +124,7 @@ def speak_shops(self, shop_info): for shop in shop_info: LOG.info(shop) location = self.request_handler.location_format(shop['location']) - hours = re.sub('(\d+)am(.+\d)pm', r'\1 A M \2 P M', shop['hours']) + hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) LOG.info({"name": shop['name'], "hours": hours, "location": location}) self.gui.show_image(shop['logo'], caption=f'{hours} {location}', title=shop['name']) @@ -132,11 +146,10 @@ def location_selection(self, shop_info): shops = self.request_handler.shop_selection_by_floors(floor, shop_info) if shops: self.speak_shops(shops) - return 3, None else: self.speak_dialog('no_shop_on_level') self.speak_shops(shop_info) - return 3, None + return 3, None def open_shops_search(self, shop_info, day_time, hour, min): """ @@ -168,17 +181,29 @@ def time_calculation(self, shop_info, open, day_time, hour, min): """ Calculates time difference between user's current time and shop working hours. - If shop is closed and user is before opening speaks how - much time is left for waiting. If user's time is evening - speaks when the shop opens in the morning. In other cases - just speaks shops info. - If user in open hours, speak the shop info. If shop open - and closes in one hour speaks how many minutes left. + If 'open' argument is True: + If user one hour or less before closing: speaks how + many minutes left. Speaks shop info. + Else speaks corresponging dialog. + Speaks shop info. + If 'open' argument is False: + Speaks corresponding dialog. + If user is one hour or less before opening hours + speaks how much time is left for waiting. + If user's time is 'am' and user is before opening + hours, speaks how many hours and minutes left + waiting. + If user's time is evening (pm) speaks when the shop + opens in the morning. + Speaks shop info. Args: shop_info (list): found shops on user's request open (boolean): True - if shop is open + day_time (str): user's current day time (am|pm) + hour (int): user's current hour + min (int): user's current minute Returns: - 3, None (to ask for another shop info) + 3, None (to ask for another shop info) Examples: work time 9am-10pm user's time 8am @@ -199,23 +224,23 @@ def time_calculation(self, shop_info, open, day_time, hour, min): if open: if day_time[1] == 'pm' and 0 < (close_time - hour) <= 1: LOG.info(f'{shop_name} closes in {wait_min} minutes.') - self.speak(f'{shop_name} closes in {wait_min} minutes.') + self.speak_dialog('closing_minutes', {"shop_name": shop_name, "wait_min": wait_min}) else: LOG.info(f'{shop_name} is open.') - self.speak(f'{shop_name} is open.') + self.speak_dialog('open_now', {'shop_name': shop_name}) LOG.info([shop]) self.speak_shops([shop]) else: if day_time[1] == 'am' and hour < open_time: if wait_h == 0: LOG.info(f'{shop_name} is closed now. Opens in {wait_min} minutes') - self.speak(f'{shop_name} is closed now. Opens in {wait_min} minutes') + self.speak_dialog('opening_minutes', {"shop_name": shop_name, "wait_min": wait_min}) else: LOG.info(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') - self.speak(f'{shop_name} is closed now. Opens in {wait_h} hour and {wait_min} minutes') + self.speak_dialog('opening_hours', {"shop_name": shop_name, "wait_h": wait_h, "wait_min": wait_min}) elif hour >= close_time: LOG.info(f'{shop_name} is closed now. Shop opens at {open_time}') - self.speak(f'{shop_name} is closed now. Shop opens at {open_time}') + self.speak_dialog('closed_now', {'shop_name': shop_name, 'open_time': open_time}) LOG.info([shop]) self.speak_shops([shop]) return 3, None @@ -223,13 +248,18 @@ def time_calculation(self, shop_info, open, day_time, hour, min): def shops_by_time_selection(self, shop_info): """ If user chose to select shops by time or - use like default selection. Selects open - shops. + use like default selection. Gets user's + current time. Selects open shops. Args: shop_info (list): found shops on user's request Returns: - shop_info (list): open shops + time_calculation function with True + in 'open' argument. + time_calculation function with False + in 'open' argument. (if list + of open shops is 0) + """ LOG.info(f"Shop by time selection {shop_info}") day_time, hour, min = self.request_handler.curent_time_extraction() @@ -274,30 +304,29 @@ def find_shop(self, user_request, mall_link): LOG.info(f'user_request {user_request}') LOG.info(f'mall_link {mall_link}') if user_request is not None: - self.speak_dialog(f"I am parsing shops and malls for your request") + self.speak_dialog("start_parsing") LOG.info(f"I am parsing shops and malls for your request") file_path = self.file_system.path + LOG.info(f'file_path {file_path}') shop_info = self.request_handler.get_shop_data(mall_link, user_request, file_path) LOG.info(f"I found {len(shop_info)} shops") LOG.info(f"shop list: {shop_info}") if len(shop_info) == 0: - self.speak_dialog("shop_not_found") - user_request = self.get_response('repeat') + user_request = self.get_response('shop_not_found') return 1, user_request elif len(shop_info) > 1: self.speak_dialog('more_than_one') # ask for the way of selection: time, location, nothing - sorting_selection = self.get_response('Do you want to select' - 'store by work hours or location?') + sorting_selection = self.get_response('choose_selection') if sorting_selection: LOG.info(f'Users answer on sorting options: {sorting_selection}') - if 'time' in sorting_selection or 'hour' in sorting_selection: + if self.voc_match(sorting_selection, "time"): LOG.info('Time sorting selected') return self.shops_by_time_selection(shop_info) - elif 'location' in sorting_selection: + elif self.voc_match(sorting_selection, "location"): LOG.info('Location sorting selected') return self.location_selection(shop_info) - elif 'no' in sorting_selection: + elif self.voc_match(sorting_selection, "no"): LOG.info('No sorting selected. Sorting by time on default.') return self.shops_by_time_selection(shop_info) else: @@ -306,10 +335,6 @@ def find_shop(self, user_request, mall_link): else: LOG.info(f"found shop {shop_info}") self.speak_shops(shop_info) - return 3, None - else: - LOG.info(str(None)) - return 3, None return 3, None def execute(self, user_request, mall_link): @@ -330,8 +355,6 @@ def _start_mall_parser_prompt(self, message): if self.neon_in_request(message): LOG.info('Prompting Mall parsing start') self.make_active() - self.path = self.file_system.path - LOG.info(self.path) if message is not None: LOG.info('new message'+str(message)) user_request, mall_link = self.user_request_handling(message) diff --git a/locale/en-us/all_locations.dialog b/locale/en-us/dialog/all_locations.dialog similarity index 100% rename from locale/en-us/all_locations.dialog rename to locale/en-us/dialog/all_locations.dialog diff --git a/locale/en-us/another_shop.dialog b/locale/en-us/dialog/another_shop.dialog similarity index 100% rename from locale/en-us/another_shop.dialog rename to locale/en-us/dialog/another_shop.dialog diff --git a/locale/en-us/ask_more.dialog b/locale/en-us/dialog/ask_more.dialog similarity index 100% rename from locale/en-us/ask_more.dialog rename to locale/en-us/dialog/ask_more.dialog diff --git a/locale/en-us/dialog/choose_selection.dialog b/locale/en-us/dialog/choose_selection.dialog new file mode 100644 index 0000000..23ac161 --- /dev/null +++ b/locale/en-us/dialog/choose_selection.dialog @@ -0,0 +1 @@ +Do you want to select store by work hours or location? \ No newline at end of file diff --git a/locale/en-us/dialog/closed_now.dialog b/locale/en-us/dialog/closed_now.dialog new file mode 100644 index 0000000..d095784 --- /dev/null +++ b/locale/en-us/dialog/closed_now.dialog @@ -0,0 +1 @@ +{{shop_name}} is closed now. Opens at {{open_time}}. \ No newline at end of file diff --git a/locale/en-us/dialog/closing_minutes.dialog b/locale/en-us/dialog/closing_minutes.dialog new file mode 100644 index 0000000..46d9f8b --- /dev/null +++ b/locale/en-us/dialog/closing_minutes.dialog @@ -0,0 +1 @@ +{{shop_name}} is open now. Closes in {{wait_min}} minutes. \ No newline at end of file diff --git a/locale/en-us/finished.dialog b/locale/en-us/dialog/finished.dialog similarity index 100% rename from locale/en-us/finished.dialog rename to locale/en-us/dialog/finished.dialog diff --git a/locale/en-us/found_shop.dialog b/locale/en-us/dialog/found_shop.dialog similarity index 100% rename from locale/en-us/found_shop.dialog rename to locale/en-us/dialog/found_shop.dialog diff --git a/locale/en-us/more_than_one.dialog b/locale/en-us/dialog/more_than_one.dialog similarity index 100% rename from locale/en-us/more_than_one.dialog rename to locale/en-us/dialog/more_than_one.dialog diff --git a/locale/en-us/no_lang.dialog b/locale/en-us/dialog/no_lang.dialog similarity index 100% rename from locale/en-us/no_lang.dialog rename to locale/en-us/dialog/no_lang.dialog diff --git a/locale/en-us/no_shop_on_level.dialog b/locale/en-us/dialog/no_shop_on_level.dialog similarity index 100% rename from locale/en-us/no_shop_on_level.dialog rename to locale/en-us/dialog/no_shop_on_level.dialog diff --git a/locale/en-us/no_shop_request.dialog b/locale/en-us/dialog/no_shop_request.dialog similarity index 100% rename from locale/en-us/no_shop_request.dialog rename to locale/en-us/dialog/no_shop_request.dialog diff --git a/locale/en-us/dialog/open_now.dialog b/locale/en-us/dialog/open_now.dialog new file mode 100644 index 0000000..3421d4c --- /dev/null +++ b/locale/en-us/dialog/open_now.dialog @@ -0,0 +1 @@ +{{shop_name}} is open. \ No newline at end of file diff --git a/locale/en-us/dialog/opening_hours.dialog b/locale/en-us/dialog/opening_hours.dialog new file mode 100644 index 0000000..3bcb8ce --- /dev/null +++ b/locale/en-us/dialog/opening_hours.dialog @@ -0,0 +1 @@ +{{shop_name}} is closed now. Opens in {{wait_h}} hour and {{wait_min}} minutes' \ No newline at end of file diff --git a/locale/en-us/dialog/opening_minutes.dialog b/locale/en-us/dialog/opening_minutes.dialog new file mode 100644 index 0000000..ce55fcc --- /dev/null +++ b/locale/en-us/dialog/opening_minutes.dialog @@ -0,0 +1 @@ +{{shop_name}} is closed now. Opens in {{wait_min}} minutes. \ No newline at end of file diff --git a/locale/en-us/run_mall_parser.intent b/locale/en-us/dialog/run_mall_parser.intent similarity index 100% rename from locale/en-us/run_mall_parser.intent rename to locale/en-us/dialog/run_mall_parser.intent diff --git a/locale/en-us/shop_by_floor.dialog b/locale/en-us/dialog/shop_by_floor.dialog similarity index 100% rename from locale/en-us/shop_by_floor.dialog rename to locale/en-us/dialog/shop_by_floor.dialog diff --git a/locale/en-us/dialog/shop_not_found.dialog b/locale/en-us/dialog/shop_not_found.dialog new file mode 100644 index 0000000..859f286 --- /dev/null +++ b/locale/en-us/dialog/shop_not_found.dialog @@ -0,0 +1,2 @@ +Sorry, this store doesn't exist in this mall. I am not sure I've understood what you said. Repeat, please. +Sorry, I can not find this shop. I am not sure I've understood what you said. Repeat, please. \ No newline at end of file diff --git a/locale/en-us/start_parsing.dialog b/locale/en-us/dialog/start_parsing.dialog similarity index 100% rename from locale/en-us/start_parsing.dialog rename to locale/en-us/dialog/start_parsing.dialog diff --git a/locale/en-us/stop.dialog b/locale/en-us/dialog/stop.dialog similarity index 100% rename from locale/en-us/stop.dialog rename to locale/en-us/dialog/stop.dialog diff --git a/locale/en-us/unexpected_error.dialog b/locale/en-us/dialog/unexpected_error.dialog similarity index 100% rename from locale/en-us/unexpected_error.dialog rename to locale/en-us/dialog/unexpected_error.dialog diff --git a/locale/en-us/which_floor.dialog b/locale/en-us/dialog/which_floor.dialog similarity index 100% rename from locale/en-us/which_floor.dialog rename to locale/en-us/dialog/which_floor.dialog diff --git a/locale/en-us/repeat.dialog b/locale/en-us/repeat.dialog deleted file mode 100644 index 853f0fc..0000000 --- a/locale/en-us/repeat.dialog +++ /dev/null @@ -1 +0,0 @@ -I am not sure I've understood what you said. Repeat, please. \ No newline at end of file diff --git a/locale/en-us/shop_not_found.dialog b/locale/en-us/shop_not_found.dialog deleted file mode 100644 index 8af425d..0000000 --- a/locale/en-us/shop_not_found.dialog +++ /dev/null @@ -1,2 +0,0 @@ -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/vocab/location.voc b/locale/en-us/vocab/location.voc new file mode 100644 index 0000000..c96c5a1 --- /dev/null +++ b/locale/en-us/vocab/location.voc @@ -0,0 +1,3 @@ +location +floor +level \ No newline at end of file diff --git a/locale/en-us/vocab/no.voc b/locale/en-us/vocab/no.voc new file mode 100644 index 0000000..f5b08b0 --- /dev/null +++ b/locale/en-us/vocab/no.voc @@ -0,0 +1,5 @@ +not +no +nope +none +don't \ No newline at end of file diff --git a/locale/en-us/vocab/time.voc b/locale/en-us/vocab/time.voc new file mode 100644 index 0000000..48d1c38 --- /dev/null +++ b/locale/en-us/vocab/time.voc @@ -0,0 +1,4 @@ +time +hour +hours +working hours \ No newline at end of file diff --git a/locale/en-us/yes.voc b/locale/en-us/vocab/yes.voc similarity index 100% rename from locale/en-us/yes.voc rename to locale/en-us/vocab/yes.voc diff --git a/request_handling.py b/request_handling.py index ccebaaf..96823ff 100644 --- a/request_handling.py +++ b/request_handling.py @@ -26,7 +26,6 @@ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import pickle from urllib.error import HTTPError import requests import bs4 @@ -34,14 +33,11 @@ import urllib.request import lingua_franca -from lingua_franca import parse from lingua_franca.format import pronounce_number lingua_franca.load_language('en') import re import os -import os, sys, stat -from os import path import json from datetime import datetime @@ -87,8 +83,27 @@ def find_cached_stores(self, user_request: str, url, file_path): return None, data def caching_stores_in_mall(self, file_path, url): + """ + Creates caching file in the current class. + Creates empty dictionary for cache. Parses + all shops info. Creates dict key from shop + name. Value list of dicts with current shop + info. + If shop name already exists in created dict + append current shop dict to existing + list. + Writes created dict to created JSON file. + Args: + file_path (str): new file path + url (str): malls url + Examples: + {"ABS stores": [ + {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, + {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} + ]} + """ self.caching_file = file_path+'/cached_stores.json' - LOG.info(self.caching_file) + LOG.info(f'caching_file {self.caching_file}') shop_cache = {} soup = self.parse(url) for shop in soup.find_all(attrs={"class": "directory-tenant-card"}): @@ -103,7 +118,7 @@ def caching_stores_in_mall(self, file_path, url): else: shop_cache[name] = [shop_data] with open(self.caching_file, - 'w+') as outfile: + 'w+') as outfile: json.dump(shop_cache, outfile, ensure_ascii=False) os.chmod(self.caching_file, 777) LOG.info("Created mall's cache") @@ -131,7 +146,7 @@ def caching_stores(self, data, store_info: list): data[store_info[0]['name']] = store_info LOG.info(f'Updated {data}') with open(self.caching_file, - 'w+') as outfile: + 'w+') as outfile: json.dump(data, outfile, ensure_ascii=False) return store_info diff --git a/test/test_skill.py b/test/test_skill.py index 458dc57..411ad8f 100644 --- a/test/test_skill.py +++ b/test/test_skill.py @@ -76,21 +76,21 @@ def setUp(self): def tearDownClass(cls) -> None: shutil.rmtree(cls.test_fs) - # 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': 'find ABC stores', - # 'shop': 'ABC stores', - # 'lang': 'en-us'}, - # {'context_key': 'MallParsing'}) - # ) + 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': 'find ABC stores', + 'shop': 'ABC stores', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + ) - # message = Message('test', {'utterance': 'find ABC stores', - # 'shop': 'ABC stores', - # 'lang': 'en-us'}, - # {'context_key': 'MallParsing'}) - # self.skill.user_request_handling(message) + message = Message('test', {'utterance': 'find ABC stores', + 'shop': 'ABC stores', + 'lang': 'en-us'}, + {'context_key': 'MallParsing'}) + self.skill.user_request_handling(message) def test_en_time_extraction(self): shop_info = [{'name': 'ABC Stores', 'hours': '9am – 9pm', 'location': 'Street Level 1, near Centerstage', 'logo': 'https://gizmostorageprod.blob.core.windows.net/tenant-logos/1615937914061-abcstores.png'}, From 7e169e6c5f708bb0d9240d13bc65a1bc7930e6d3 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 27 Sep 2022 13:52:22 -0400 Subject: [PATCH 18/23] changes in RequestHandler class --- __init__.py | 18 +- __pycache__/__init__.cpython-310.pyc | Bin 0 -> 12084 bytes __pycache__/request_handling.cpython-310.pyc | Bin 0 -> 7524 bytes .../dialog/{ => en}/all_locations.dialog | 0 .../en-us/dialog/{ => en}/another_shop.dialog | 0 locale/en-us/dialog/{ => en}/ask_more.dialog | 0 .../dialog/{ => en}/choose_selection.dialog | 0 .../en-us/dialog/{ => en}/closed_now.dialog | 0 .../dialog/{ => en}/closing_minutes.dialog | 0 locale/en-us/dialog/{ => en}/finished.dialog | 0 locale/en-us/dialog/en/found_shop.dialog | 1 + .../dialog/{ => en}/more_than_one.dialog | 0 locale/en-us/dialog/{ => en}/no_lang.dialog | 0 .../dialog/{ => en}/no_shop_on_level.dialog | 0 .../dialog/{ => en}/no_shop_request.dialog | 0 locale/en-us/dialog/{ => en}/open_now.dialog | 0 .../dialog/{ => en}/opening_hours.dialog | 0 .../dialog/{ => en}/opening_minutes.dialog | 0 .../dialog/{ => en}/shop_by_floor.dialog | 0 .../dialog/{ => en}/shop_not_found.dialog | 0 .../dialog/{ => en}/start_parsing.dialog | 0 locale/en-us/dialog/{ => en}/stop.dialog | 0 .../dialog/{ => en}/unexpected_error.dialog | 0 .../en-us/dialog/{ => en}/which_floor.dialog | 0 locale/en-us/dialog/found_shop.dialog | 1 - .../en-us/{dialog => }/run_mall_parser.intent | 0 locale/en-us/vocab/{ => en}/location.voc | 0 locale/en-us/vocab/{ => en}/no.voc | 0 locale/en-us/vocab/{ => en}/time.voc | 0 locale/en-us/vocab/{ => en}/yes.voc | 0 request_handling.py | 458 ++++++++---------- 31 files changed, 215 insertions(+), 263 deletions(-) create mode 100644 __pycache__/__init__.cpython-310.pyc create mode 100644 __pycache__/request_handling.cpython-310.pyc rename locale/en-us/dialog/{ => en}/all_locations.dialog (100%) rename locale/en-us/dialog/{ => en}/another_shop.dialog (100%) rename locale/en-us/dialog/{ => en}/ask_more.dialog (100%) rename locale/en-us/dialog/{ => en}/choose_selection.dialog (100%) rename locale/en-us/dialog/{ => en}/closed_now.dialog (100%) rename locale/en-us/dialog/{ => en}/closing_minutes.dialog (100%) rename locale/en-us/dialog/{ => en}/finished.dialog (100%) create mode 100644 locale/en-us/dialog/en/found_shop.dialog rename locale/en-us/dialog/{ => en}/more_than_one.dialog (100%) rename locale/en-us/dialog/{ => en}/no_lang.dialog (100%) rename locale/en-us/dialog/{ => en}/no_shop_on_level.dialog (100%) rename locale/en-us/dialog/{ => en}/no_shop_request.dialog (100%) rename locale/en-us/dialog/{ => en}/open_now.dialog (100%) rename locale/en-us/dialog/{ => en}/opening_hours.dialog (100%) rename locale/en-us/dialog/{ => en}/opening_minutes.dialog (100%) rename locale/en-us/dialog/{ => en}/shop_by_floor.dialog (100%) rename locale/en-us/dialog/{ => en}/shop_not_found.dialog (100%) rename locale/en-us/dialog/{ => en}/start_parsing.dialog (100%) rename locale/en-us/dialog/{ => en}/stop.dialog (100%) rename locale/en-us/dialog/{ => en}/unexpected_error.dialog (100%) rename locale/en-us/dialog/{ => en}/which_floor.dialog (100%) delete mode 100644 locale/en-us/dialog/found_shop.dialog rename locale/en-us/{dialog => }/run_mall_parser.intent (100%) rename locale/en-us/vocab/{ => en}/location.voc (100%) rename locale/en-us/vocab/{ => en}/no.voc (100%) rename locale/en-us/vocab/{ => en}/time.voc (100%) rename locale/en-us/vocab/{ => en}/yes.voc (100%) diff --git a/__init__.py b/__init__.py index d9bf819..800eda7 100644 --- a/__init__.py +++ b/__init__.py @@ -31,10 +31,10 @@ from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler -# from .request_handling import existing_lang_check, get_shop_data,\ -# shop_selection_by_floors,\ -# location_format,\ -# curent_time_extraction +from .request_handling import existing_lang_check, get_shop_data,\ + shop_selection_by_floors,\ + location_format,\ + curent_time_extraction import re @@ -75,7 +75,7 @@ def user_request_handling(self, message): LOG.info(f"{self.mall_link()}") LOG.info(str(request_lang)) LOG.info(user_request) - found, link = RequestHandler.existing_lang_check(request_lang, self.mall_link()) + found, link = existing_lang_check(request_lang, self.mall_link()) if found: link = self.mall_link()+request_lang+'/directory/' LOG.info('new link: '+ link) @@ -123,7 +123,7 @@ def speak_shops(self, shop_info): """ for shop in shop_info: LOG.info(shop) - location = self.request_handler.location_format(shop['location']) + location = location_format(shop['location']) hours = re.sub('(\d+)am.+(\d+)pm', r'from \1 A M to \2 P M', shop['hours']) self.speak_dialog('found_shop', {"name": shop['name'], "hours": hours, "location": location}) LOG.info({"name": shop['name'], "hours": hours, "location": location}) @@ -143,7 +143,7 @@ def location_selection(self, shop_info): """ LOG.info(f"Shop by location selection {shop_info}") floor = self.get_response('which_floor') - shops = self.request_handler.shop_selection_by_floors(floor, shop_info) + shops = shop_selection_by_floors(floor, shop_info) if shops: self.speak_shops(shops) else: @@ -262,7 +262,7 @@ def shops_by_time_selection(self, shop_info): """ LOG.info(f"Shop by time selection {shop_info}") - day_time, hour, min = self.request_handler.curent_time_extraction() + day_time, hour, min = curent_time_extraction() # day_time, hour, min = ['11:15', 'pm'], 11, 15 open_shops = self.open_shops_search(shop_info, day_time, hour, min) if len(open_shops) >= 1: @@ -308,7 +308,7 @@ def find_shop(self, user_request, mall_link): LOG.info(f"I am parsing shops and malls for your request") file_path = self.file_system.path LOG.info(f'file_path {file_path}') - shop_info = self.request_handler.get_shop_data(mall_link, user_request, file_path) + shop_info = get_shop_data(mall_link, user_request, file_path) LOG.info(f"I found {len(shop_info)} shops") LOG.info(f"shop list: {shop_info}") if len(shop_info) == 0: diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0dd682e0dfeccdb66f192e1841181d5fd15bb358 GIT binary patch literal 12084 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 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..0a4ebd305d9a433ea2359e2f6223a69653063ef5 GIT binary patch literal 7524 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 PuDxV0+bi}fwZ?w|b(Y-? literal 0 HcmV?d00001 diff --git a/locale/en-us/dialog/all_locations.dialog b/locale/en-us/dialog/en/all_locations.dialog similarity index 100% rename from locale/en-us/dialog/all_locations.dialog rename to locale/en-us/dialog/en/all_locations.dialog diff --git a/locale/en-us/dialog/another_shop.dialog b/locale/en-us/dialog/en/another_shop.dialog similarity index 100% rename from locale/en-us/dialog/another_shop.dialog rename to locale/en-us/dialog/en/another_shop.dialog diff --git a/locale/en-us/dialog/ask_more.dialog b/locale/en-us/dialog/en/ask_more.dialog similarity index 100% rename from locale/en-us/dialog/ask_more.dialog rename to locale/en-us/dialog/en/ask_more.dialog diff --git a/locale/en-us/dialog/choose_selection.dialog b/locale/en-us/dialog/en/choose_selection.dialog similarity index 100% rename from locale/en-us/dialog/choose_selection.dialog rename to locale/en-us/dialog/en/choose_selection.dialog diff --git a/locale/en-us/dialog/closed_now.dialog b/locale/en-us/dialog/en/closed_now.dialog similarity index 100% rename from locale/en-us/dialog/closed_now.dialog rename to locale/en-us/dialog/en/closed_now.dialog diff --git a/locale/en-us/dialog/closing_minutes.dialog b/locale/en-us/dialog/en/closing_minutes.dialog similarity index 100% rename from locale/en-us/dialog/closing_minutes.dialog rename to locale/en-us/dialog/en/closing_minutes.dialog diff --git a/locale/en-us/dialog/finished.dialog b/locale/en-us/dialog/en/finished.dialog similarity index 100% rename from locale/en-us/dialog/finished.dialog rename to locale/en-us/dialog/en/finished.dialog diff --git a/locale/en-us/dialog/en/found_shop.dialog b/locale/en-us/dialog/en/found_shop.dialog new file mode 100644 index 0000000..d667791 --- /dev/null +++ b/locale/en-us/dialog/en/found_shop.dialog @@ -0,0 +1 @@ +I found {{name}} with hours {{hours}}. You can find this store on {{location}}. \ No newline at end of file diff --git a/locale/en-us/dialog/more_than_one.dialog b/locale/en-us/dialog/en/more_than_one.dialog similarity index 100% rename from locale/en-us/dialog/more_than_one.dialog rename to locale/en-us/dialog/en/more_than_one.dialog diff --git a/locale/en-us/dialog/no_lang.dialog b/locale/en-us/dialog/en/no_lang.dialog similarity index 100% rename from locale/en-us/dialog/no_lang.dialog rename to locale/en-us/dialog/en/no_lang.dialog diff --git a/locale/en-us/dialog/no_shop_on_level.dialog b/locale/en-us/dialog/en/no_shop_on_level.dialog similarity index 100% rename from locale/en-us/dialog/no_shop_on_level.dialog rename to locale/en-us/dialog/en/no_shop_on_level.dialog diff --git a/locale/en-us/dialog/no_shop_request.dialog b/locale/en-us/dialog/en/no_shop_request.dialog similarity index 100% rename from locale/en-us/dialog/no_shop_request.dialog rename to locale/en-us/dialog/en/no_shop_request.dialog diff --git a/locale/en-us/dialog/open_now.dialog b/locale/en-us/dialog/en/open_now.dialog similarity index 100% rename from locale/en-us/dialog/open_now.dialog rename to locale/en-us/dialog/en/open_now.dialog diff --git a/locale/en-us/dialog/opening_hours.dialog b/locale/en-us/dialog/en/opening_hours.dialog similarity index 100% rename from locale/en-us/dialog/opening_hours.dialog rename to locale/en-us/dialog/en/opening_hours.dialog diff --git a/locale/en-us/dialog/opening_minutes.dialog b/locale/en-us/dialog/en/opening_minutes.dialog similarity index 100% rename from locale/en-us/dialog/opening_minutes.dialog rename to locale/en-us/dialog/en/opening_minutes.dialog diff --git a/locale/en-us/dialog/shop_by_floor.dialog b/locale/en-us/dialog/en/shop_by_floor.dialog similarity index 100% rename from locale/en-us/dialog/shop_by_floor.dialog rename to locale/en-us/dialog/en/shop_by_floor.dialog diff --git a/locale/en-us/dialog/shop_not_found.dialog b/locale/en-us/dialog/en/shop_not_found.dialog similarity index 100% rename from locale/en-us/dialog/shop_not_found.dialog rename to locale/en-us/dialog/en/shop_not_found.dialog diff --git a/locale/en-us/dialog/start_parsing.dialog b/locale/en-us/dialog/en/start_parsing.dialog similarity index 100% rename from locale/en-us/dialog/start_parsing.dialog rename to locale/en-us/dialog/en/start_parsing.dialog diff --git a/locale/en-us/dialog/stop.dialog b/locale/en-us/dialog/en/stop.dialog similarity index 100% rename from locale/en-us/dialog/stop.dialog rename to locale/en-us/dialog/en/stop.dialog diff --git a/locale/en-us/dialog/unexpected_error.dialog b/locale/en-us/dialog/en/unexpected_error.dialog similarity index 100% rename from locale/en-us/dialog/unexpected_error.dialog rename to locale/en-us/dialog/en/unexpected_error.dialog diff --git a/locale/en-us/dialog/which_floor.dialog b/locale/en-us/dialog/en/which_floor.dialog similarity index 100% rename from locale/en-us/dialog/which_floor.dialog rename to locale/en-us/dialog/en/which_floor.dialog diff --git a/locale/en-us/dialog/found_shop.dialog b/locale/en-us/dialog/found_shop.dialog deleted file mode 100644 index 73f3567..0000000 --- a/locale/en-us/dialog/found_shop.dialog +++ /dev/null @@ -1 +0,0 @@ -I found {{name}} with hours of {{hours}}. You can find this store on {{location}}. \ No newline at end of file diff --git a/locale/en-us/dialog/run_mall_parser.intent b/locale/en-us/run_mall_parser.intent similarity index 100% rename from locale/en-us/dialog/run_mall_parser.intent rename to locale/en-us/run_mall_parser.intent diff --git a/locale/en-us/vocab/location.voc b/locale/en-us/vocab/en/location.voc similarity index 100% rename from locale/en-us/vocab/location.voc rename to locale/en-us/vocab/en/location.voc diff --git a/locale/en-us/vocab/no.voc b/locale/en-us/vocab/en/no.voc similarity index 100% rename from locale/en-us/vocab/no.voc rename to locale/en-us/vocab/en/no.voc diff --git a/locale/en-us/vocab/time.voc b/locale/en-us/vocab/en/time.voc similarity index 100% rename from locale/en-us/vocab/time.voc rename to locale/en-us/vocab/en/time.voc diff --git a/locale/en-us/vocab/yes.voc b/locale/en-us/vocab/en/yes.voc similarity index 100% rename from locale/en-us/vocab/yes.voc rename to locale/en-us/vocab/en/yes.voc diff --git a/request_handling.py b/request_handling.py index 96823ff..049e56a 100644 --- a/request_handling.py +++ b/request_handling.py @@ -44,260 +44,212 @@ class RequestHandler(): - - def __init__(self) -> None: - self.caching_file = '' - - def find_cached_stores(self, user_request: str, url, file_path): - """ - Check shop name existence in cache keys - Args: - user_request (str): shop from user's message - Returns: - if file is empty -> None, {} - if shop wasn't found -> None, read data - if shop found -> store_info (list), read data - Examples: - [ - {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, - {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} - ] - """ - if os.path.isfile(self.caching_file) == False: - LOG.info("Cache file doesn't exist") - self.caching_stores_in_mall(file_path, url) - return self.find_cached_stores(user_request, url, file_path) - else: - with open(self.caching_file, 'r', encoding='utf-8') as readfile: - data = json.load(readfile) - found_key = [key for key in data.keys() - if key.lower() in user_request.lower() - or user_request.lower() in key.lower()] - LOG.info(f'found key {found_key}') - if len(found_key) >=1 : - store_name = str(found_key[0]) - LOG.info(f'Shop exists {data[store_name]}') - return data[store_name], data - else: - LOG.info("Shop doesn't exist in cache") - return None, data - - def caching_stores_in_mall(self, file_path, url): - """ - Creates caching file in the current class. - Creates empty dictionary for cache. Parses - all shops info. Creates dict key from shop - name. Value list of dicts with current shop - info. - If shop name already exists in created dict - append current shop dict to existing - list. - Writes created dict to created JSON file. - Args: - file_path (str): new file path - url (str): malls url - Examples: - {"ABS stores": [ - {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, - {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} - ]} - """ - self.caching_file = file_path+'/cached_stores.json' - LOG.info(f'caching_file {self.caching_file}') - shop_cache = {} - 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') - 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} - if name in shop_cache.keys(): - shop_cache[name].append(shop_data) - else: - shop_cache[name] = [shop_data] - with open(self.caching_file, - 'w+') as outfile: - json.dump(shop_cache, outfile, ensure_ascii=False) - os.chmod(self.caching_file, 777) - LOG.info("Created mall's cache") - - - def caching_stores(self, data, store_info: list): - """ - Saves dictionary to JSON file - key - shop name, value: list with shops info - if file is empty -> creates dictionary - if file contains info -> updates data dictionary - with the new shop info - Args: - data (dict): existing shops info - store_info (list): scraped store info - Returns: - store_info (list): scraped store info - Examples: - {"ABS stores": [ - {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, - {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} - ]} - """ - LOG.info(f'data from JSON {data}') - data[store_info[0]['name']] = store_info - LOG.info(f'Updated {data}') - with open(self.caching_file, - 'w+') as outfile: - json.dump(data, outfile, ensure_ascii=False) - return store_info - - def existing_lang_check(user_lang: str, url): - """ - Check existence of user's language - on the mall web-page - Args: - user_lang (str): user's lang in ISO 639-1 - Returns: - bool: True if lang exists - """ - link = url+user_lang+'/directory/' - response = requests.get(link) - if response.status_code == 200: - LOG.info('This language is supported') - return True, link - else: - LOG.info('This language is not supported') - return False, link - - 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.now().time().strftime("%I:%M %p") - # now = datetime.today().strftime("%H:%M %p") - LOG.info(f'now {now}') - 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 location_format(self, location): - """ - Finds all digits in store's location and - formats them to numeral words. - Args: - location (str): location info - from shops info - Returns: - if digits were found: - pronounced (str): utterance with - pronounced digits + + caching_file = '' + +def find_cached_stores(user_request: str, url, file_path): + """ + Check shop name existence in cache keys + Args: + user_request (str): shop from user's message + Returns: + if file is empty -> None, {} + if shop wasn't found -> None, read data + if shop found -> store_info (list), read data + Examples: + [ + {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, + {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} + ] + """ + caching_file = file_path+'/cached_stores.json' + if os.path.isfile(caching_file) == False: + LOG.info("Cache file doesn't exist") + caching_stores_in_mall(file_path, url) + return find_cached_stores(user_request, url, file_path) + else: + with open(caching_file, 'r', encoding='utf-8') as readfile: + data = json.load(readfile) + found_key = [key for key in data.keys() + if key.lower() in user_request.lower() + or user_request.lower() in key.lower()] + LOG.info(f'found key {found_key}') + if len(found_key) >=1 : + store_name = str(found_key[0]) + LOG.info(f'Shop exists {data[store_name]}') + return data[store_name], data else: - location (str): not changed utterance - Examples: - 'level 1' -> 'level one' - """ - 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: - return location - - def shop_selection_by_floors(self, user_request, found_shops): - """ - If there are several shops in found shops list - and user agrees to select shop by floor. - Finds all digits in store's location and - formats them to ordinal and cardinal numerals. - Matches formated numerals with user's request. - If shop was found appends it to the new found + LOG.info("Shop doesn't exist in cache") + return None, data + +def caching_stores_in_mall(file_path, url): + """ + Creates caching file in the current class. + Creates empty dictionary for cache. Parses + all shops info. Creates dict key from shop + name. Value list of dicts with current shop + info. + If shop name already exists in created dict + append current shop dict to existing list. - Args: - user_request (str): floor from user - found_shops (list): found shops on user's - request - Returns: - shops_by_floor (list): shops that was found by floor - """ - shops_by_floor = [] - 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: - shops_by_floor.append(shop) - return shops_by_floor - - def parse(self, url): - 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, file_path): - """ - Check existence of user's request store in cache - if shop was found returns list with shop info, - else does parsing of mall's web-page. - Matches the name of existing stores with user's - request. If store was found, returns list with - stores' info and does caching, else returns empty - list. - on the mall web-page - Args: - url (str): mall link from hardcoded in init.py - user_request (str): utterance from stt parsing - Returns: - : found_shops (list): found shops' info - """ - # search for store existence in cache - LOG.info(file_path) - found_shops, data = self.find_cached_stores(user_request, url, file_path) - LOG.info(found_shops) - if found_shops: - LOG.info(f"found_shops: {found_shops}") - return found_shops - else: - # parsing mall web-page - found_shops = [] - soup = self.parse(url) - # loop through store names - 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') - # matching store names with user's request - 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) - LOG.info(f'I parsed: {found_shops}') - if found_shops: - # caching if shop was found - found_shops = self.caching_stores(data, found_shops) - return found_shops + Writes created dict to created JSON file. + Args: + file_path (str): new file path + url (str): malls url + Examples: + {"ABS stores": [ + {"name": "ABS stores", "time": "8am-10pm", "location": "1 level"}, + {"name": "ABS stores", "time": "8am-10pm", "location": "2 level"} + ]} + """ + caching_file = file_path+'/cached_stores.json' + LOG.info(f'caching_file {caching_file}') + shop_cache = {} + soup = 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') + 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} + if name in shop_cache.keys(): + shop_cache[name].append(shop_data) else: - # return empty list - return found_shops + shop_cache[name] = [shop_data] + with open(caching_file, + 'w+') as outfile: + json.dump(shop_cache, outfile, ensure_ascii=False) + os.chmod(caching_file, 777) + LOG.info("Created mall's cache") + +def existing_lang_check(user_lang: str, url): + """ + Check existence of user's language + on the mall web-page + Args: + user_lang (str): user's lang in ISO 639-1 + Returns: + bool: True if lang exists + """ + link = url+user_lang+'/directory/' + response = requests.get(link) + if response.status_code == 200: + LOG.info('This language is supported') + return True, link + else: + LOG.info('This language is not supported') + return False, link + +def curent_time_extraction(): + """ + 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.now().time().strftime("%I:%M %p") + # now = datetime.today().strftime("%H:%M %p") + LOG.info(f'now {now}') + 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 location_format(location): + """ + Finds all digits in store's location and + formats them to numeral words. + Args: + location (str): location info + from shops info + Returns: + if digits were found: + pronounced (str): utterance with + pronounced digits + else: + location (str): not changed utterance + Examples: + 'level 1' -> 'level one' + """ + 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: + return location + +def shop_selection_by_floors(user_request, found_shops): + """ + If there are several shops in found shops list + and user agrees to select shop by floor. + Finds all digits in store's location and + formats them to ordinal and cardinal numerals. + Matches formated numerals with user's request. + If shop was found appends it to the new found + list. + Args: + user_request (str): floor from user + found_shops (list): found shops on user's + request + Returns: + shops_by_floor (list): shops that was found by floor + """ + shops_by_floor = [] + 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: + shops_by_floor.append(shop) + return shops_by_floor + +def parse(url): + 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(url, user_request, file_path): + """ + Check existence of user's request store in cache + if shop was found returns list with shop info, + else does parsing of mall's web-page. + Matches the name of existing stores with user's + request. If store was found, returns list with + stores' info and does caching, else returns empty + list. + on the mall web-page + Args: + url (str): mall link from hardcoded in init.py + user_request (str): utterance from stt parsing + Returns: + : found_shops (list): found shops' info + """ + # search for store existence in cache + LOG.info(file_path) + found_shops, data = find_cached_stores(user_request, url, file_path) + LOG.info(found_shops) + if found_shops: + LOG.info(f"found_shops: {found_shops}") + return found_shops + else: + return [] From c12ae73bcf9ae338924fa58f0df8248b49f963ef Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 27 Sep 2022 13:56:12 -0400 Subject: [PATCH 19/23] init file cleaning --- __init__.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/__init__.py b/__init__.py index 800eda7..5364ae1 100644 --- a/__init__.py +++ b/__init__.py @@ -27,7 +27,6 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -from os import path from neon_utils.skills.neon_skill import NeonSkill, LOG from mycroft.skills.core import intent_file_handler from .request_handling import RequestHandler @@ -43,10 +42,7 @@ class DirectorySkill(NeonSkill): def __init__(self): super(DirectorySkill, self).__init__(name="DirectorySkill") - self.request_handler = RequestHandler() - self.cache = dict() self.url = "https://www.alamoanacenter.com/en/directory/" - self.path = 'cached_stores.json' def initialize(self): From 2d18fb5e2de2f03245b874b0ee7eb8a810444e2f Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 27 Sep 2022 14:08:48 -0400 Subject: [PATCH 20/23] adding github actions --- .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 ++++++++++++ 5 files changed, 198 insertions(+) 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 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/ From 07efd32779a12e87c2a8092e0219a366eca98798 Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 27 Sep 2022 14:15:18 -0400 Subject: [PATCH 21/23] deleting pycache --- __pycache__/__init__.cpython-310.pyc | Bin 12084 -> 0 bytes __pycache__/request_handling.cpython-310.pyc | Bin 7524 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __pycache__/__init__.cpython-310.pyc delete mode 100644 __pycache__/request_handling.cpython-310.pyc diff --git a/__pycache__/__init__.cpython-310.pyc b/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 0dd682e0dfeccdb66f192e1841181d5fd15bb358..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12084 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 diff --git a/__pycache__/request_handling.cpython-310.pyc b/__pycache__/request_handling.cpython-310.pyc deleted file mode 100644 index 0a4ebd305d9a433ea2359e2f6223a69653063ef5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7524 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 PuDxV0+bi}fwZ?w|b(Y-? From f672231d1cf3d2a61a911f921decf37ffd972b8c Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Tue, 27 Sep 2022 14:42:29 -0400 Subject: [PATCH 22/23] adding docstring --- __init__.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/__init__.py b/__init__.py index 5364ae1..e6acc2f 100644 --- a/__init__.py +++ b/__init__.py @@ -62,6 +62,15 @@ def mall_link(self): return self.settings.get("mall_link") or mall_link def user_request_handling(self, message): + """ + Checks user language existence on mall's web-page + using existing_lang_check() function. + Returns: + None, None: if message is empty + None, None: if language is not supported + user_request, link (str, str): if language exists + answer) + """ LOG.info(f"Message is {message.data}") if message.data == {} or message is None: return None, None From b7e18429a91dd5072bfad47d1f72ff0c566a62ba Mon Sep 17 00:00:00 2001 From: NeonMariia Date: Fri, 30 Sep 2022 08:42:56 +0000 Subject: [PATCH 23/23] Increment Version --- version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.py b/version.py index cc11eeb..caf633f 100644 --- a/version.py +++ b/version.py @@ -26,4 +26,4 @@ # 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.1a2" +__version__ = "0.0.1a3"