From 185be391725aec3f254d7e9a774972d17830f92e Mon Sep 17 00:00:00 2001 From: louistiti Date: Thu, 5 Sep 2024 22:14:18 +0800 Subject: [PATCH] feat(skill/akinator): complete rewrite --- core/skills-endpoints.json | 138 +++++----- skills/games/akinator/skill.json | 2 +- skills/games/akinator/src/actions/guess.py | 68 +++-- skills/games/akinator/src/actions/setup.py | 36 ++- skills/games/akinator/src/lib/VERSION.txt | 1 - skills/games/akinator/src/lib/__init__.py | 36 +-- skills/games/akinator/src/lib/akinator.py | 242 ------------------ skills/games/akinator/src/lib/exceptions.py | 63 ----- skills/games/akinator/src/lib/main.py | 131 ++++++++++ skills/games/akinator/src/lib/memory.py | 19 +- .../games/akinator/src/lib/question_widget.py | 7 + skills/games/akinator/src/lib/utils.py | 125 --------- skills/leon/random_number/src/actions/run.py | 1 - .../src/widgets/todos_list_widget.py | 1 - 14 files changed, 270 insertions(+), 600 deletions(-) delete mode 100644 skills/games/akinator/src/lib/VERSION.txt delete mode 100644 skills/games/akinator/src/lib/akinator.py delete mode 100644 skills/games/akinator/src/lib/exceptions.py create mode 100644 skills/games/akinator/src/lib/main.py create mode 100644 skills/games/akinator/src/lib/question_widget.py delete mode 100644 skills/games/akinator/src/lib/utils.py diff --git a/core/skills-endpoints.json b/core/skills-endpoints.json index f91d3694..764e3166 100644 --- a/core/skills-endpoints.json +++ b/core/skills-endpoints.json @@ -1,63 +1,5 @@ { "endpoints": [ - { - "method": "POST", - "route": "/api/action/productivity/todo_list/create_list", - "params": ["list"], - "entitiesType": "trim" - }, - { - "method": "GET", - "route": "/api/action/productivity/todo_list/view_lists", - "params": [] - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/view_list", - "params": ["list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/rename_list", - "params": ["old_list", "new_list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/delete_list", - "params": ["list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/add_todos", - "params": ["todos", "list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/complete_todos", - "params": ["todos", "list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/productivity/todo_list/uncheck_todos", - "params": ["todos", "list"], - "entitiesType": "trim" - }, - { - "method": "POST", - "route": "/api/action/news/github_trends/run", - "params": ["number", "daterange"], - "entitiesType": "builtIn" - }, - { - "method": "GET", - "route": "/api/action/news/product_hunt_trends/run", - "params": [] - }, { "method": "POST", "route": "/api/action/games/akinator/choose_thematic", @@ -116,29 +58,62 @@ "params": [] }, { - "method": "GET", - "route": "/api/action/social_communication/conversation/setup", - "params": [] + "method": "POST", + "route": "/api/action/news/github_trends/run", + "params": ["number", "daterange"], + "entitiesType": "builtIn" }, { "method": "GET", - "route": "/api/action/social_communication/conversation/chit_chat", + "route": "/api/action/news/product_hunt_trends/run", "params": [] }, { - "method": "GET", - "route": "/api/action/social_communication/conversation/converse", - "params": [] + "method": "POST", + "route": "/api/action/productivity/todo_list/create_list", + "params": ["list"], + "entitiesType": "trim" }, { "method": "GET", - "route": "/api/action/social_communication/mbti/setup", + "route": "/api/action/productivity/todo_list/view_lists", "params": [] }, { - "method": "GET", - "route": "/api/action/social_communication/mbti/quiz", - "params": [] + "method": "POST", + "route": "/api/action/productivity/todo_list/view_list", + "params": ["list"], + "entitiesType": "trim" + }, + { + "method": "POST", + "route": "/api/action/productivity/todo_list/rename_list", + "params": ["old_list", "new_list"], + "entitiesType": "trim" + }, + { + "method": "POST", + "route": "/api/action/productivity/todo_list/delete_list", + "params": ["list"], + "entitiesType": "trim" + }, + { + "method": "POST", + "route": "/api/action/productivity/todo_list/add_todos", + "params": ["todos", "list"], + "entitiesType": "trim" + }, + { + "method": "POST", + "route": "/api/action/productivity/todo_list/complete_todos", + "params": ["todos", "list"], + "entitiesType": "trim" + }, + { + "method": "POST", + "route": "/api/action/productivity/todo_list/uncheck_todos", + "params": ["todos", "list"], + "entitiesType": "trim" }, { "method": "GET", @@ -290,6 +265,31 @@ "method": "GET", "route": "/api/action/utilities/translator-poc/translate", "params": [] + }, + { + "method": "GET", + "route": "/api/action/social_communication/conversation/setup", + "params": [] + }, + { + "method": "GET", + "route": "/api/action/social_communication/conversation/chit_chat", + "params": [] + }, + { + "method": "GET", + "route": "/api/action/social_communication/conversation/converse", + "params": [] + }, + { + "method": "GET", + "route": "/api/action/social_communication/mbti/setup", + "params": [] + }, + { + "method": "GET", + "route": "/api/action/social_communication/mbti/quiz", + "params": [] } ] } diff --git a/skills/games/akinator/skill.json b/skills/games/akinator/skill.json index 54ee4ad1..d2fdd7d2 100644 --- a/skills/games/akinator/skill.json +++ b/skills/games/akinator/skill.json @@ -2,7 +2,7 @@ "$schema": "../../../schemas/skill-schemas/skill.json", "name": "Akinator", "bridge": "python", - "version": "1.0.0", + "version": "2.0.0", "description": "Leon's friend, the Akinator genie will read your mind.", "author": { "name": "Louis Grenard", diff --git a/skills/games/akinator/src/actions/guess.py b/skills/games/akinator/src/actions/guess.py index 0ccae336..c930a18f 100644 --- a/skills/games/akinator/src/actions/guess.py +++ b/skills/games/akinator/src/actions/guess.py @@ -1,7 +1,6 @@ from bridges.python.src.sdk.leon import leon from bridges.python.src.sdk.types import ActionParams -from ..lib import akinator, memory - +from ..lib import Akinator, memory def run(params: ActionParams) -> None: """Guess according to the given thematic""" @@ -17,39 +16,39 @@ def run(params: ActionParams) -> None: if answer is None: return leon.answer({'core': {'isInActionLoop': False}}) - aki = akinator.Akinator() - session = memory.get_session() - response = session['response'] - aki.session = session['session'] - aki.signature = session['signature'] - aki.progression = session['progression'] - aki.uri = session['uri'] - aki.timestamp = session['timestamp'] - aki.server = session['server'] - aki.child_mode = session['child_mode'] - aki.frontaddr = session['frontaddr'] - aki.question_filter = session['question_filter'] - resp = aki._parse_response(response) - aki._update(resp, '"step": "0"' in response) + akinator = Akinator( + lang=session['lang'], + theme=session['theme'] + ) - if session['progression'] > 80: - aki.win() + # Retrieve the current session progress + akinator.json = { + 'step': session['step'], + 'progression': session['progression'], + 'sid': session['sid'], + 'cm': session['cm'], + 'session': session['session'], + 'signature': session['signature'] + } + new_progress_response = akinator.post_answer(answer) + + if 'name_proposition' in new_progress_response: leon.answer({ 'key': 'guessed', 'data': { - 'name': aki.first_guess['name'], - 'description': aki.first_guess['description'] + 'name': new_progress_response['name_proposition'], + 'description': new_progress_response['description_proposition'] } }) leon.answer({ 'key': 'guessed_img', 'data': { - 'name': aki.first_guess['name'], - 'url': aki.first_guess['absolute_picture_path'] + 'name': new_progress_response['name_proposition'], + 'url': new_progress_response['photo'] } }) @@ -61,23 +60,22 @@ def run(params: ActionParams) -> None: } }) - aki.answer(answer) - memory.upsert_session({ - 'response': aki.response, - 'session': aki.session, - 'signature': aki.signature, - 'progression': aki.progression, - 'uri': aki.uri, - 'timestamp': aki.timestamp, - 'server': aki.server, - 'child_mode': aki.child_mode, - 'frontaddr': aki.frontaddr, - 'question_filter': aki.question_filter + 'lang': session['lang'], + 'theme': session['theme'], + 'sid': session['sid'], + 'cm': session['cm'], + 'signature': session['signature'], + 'session': session['session'], + 'question': new_progress_response['question'], + 'step': int(new_progress_response['step']), + 'progression': float(new_progress_response['progression']) }) + # TODO: widget with image + leon.answer({ - 'key': aki.question, + 'key': akinator.question, 'core': { 'showSuggestions': True } diff --git a/skills/games/akinator/src/actions/setup.py b/skills/games/akinator/src/actions/setup.py index aedf2ef9..c1ba56e0 100644 --- a/skills/games/akinator/src/actions/setup.py +++ b/skills/games/akinator/src/actions/setup.py @@ -1,7 +1,6 @@ from bridges.python.src.sdk.leon import leon from bridges.python.src.sdk.types import ActionParams -from ..lib import akinator, memory - +from ..lib import Akinator, memory def run(params: ActionParams) -> None: """Initialize new session""" @@ -10,35 +9,34 @@ def run(params: ActionParams) -> None: slots, lang = params['slots'], params['lang'] thematic = slots['thematic']['resolution']['value'] - theme_lang = lang - if thematic != 'characters': - theme_lang = lang + '_' + thematic try: - aki = akinator.Akinator() + akinator = Akinator( + lang=lang, + theme=thematic + ) - q = aki.start_game(theme_lang) + question = akinator.start_game() memory.upsert_session({ - 'response': aki.response, - 'session': aki.session, - 'progression': aki.progression, - 'signature': aki.signature, - 'uri': aki.uri, - 'timestamp': aki.timestamp, - 'server': aki.server, - 'child_mode': aki.child_mode, - 'frontaddr': aki.frontaddr, - 'question_filter': aki.question_filter + 'lang': lang, + 'theme': thematic, + 'cm': False, + 'sid': akinator.json['sid'], + 'question': akinator.question, + 'step': akinator.step, + 'progression': akinator.progression, + 'signature': akinator.json['signature'], + 'session': akinator.json['session'], }) leon.answer({ - 'key': q, + 'key': question, 'core': { 'showNextActionSuggestions': True } }) - except BaseException: + except: leon.answer({ 'key': 'network_error', 'core': { diff --git a/skills/games/akinator/src/lib/VERSION.txt b/skills/games/akinator/src/lib/VERSION.txt deleted file mode 100644 index 0062ac97..00000000 --- a/skills/games/akinator/src/lib/VERSION.txt +++ /dev/null @@ -1 +0,0 @@ -5.0.0 diff --git a/skills/games/akinator/src/lib/__init__.py b/skills/games/akinator/src/lib/__init__.py index b8bc4f38..2ce76105 100644 --- a/skills/games/akinator/src/lib/__init__.py +++ b/skills/games/akinator/src/lib/__init__.py @@ -1,33 +1,3 @@ -""" -An API wrapper for the online game, Akinator, written in Python -""" -""" -MIT License - -Copyright (c) 2019 NinjaSnail1080 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - - -from .akinator import Akinator -from .exceptions import * -import os -with open(os.path.join(os.path.dirname(__file__), "VERSION.txt")) as f: - __version__ = f.read() +from .main import Akinator,AkinatorError +__version__ = '1.6.0' +__url__ = 'https://github.com/taka-4602/Akinator-python' diff --git a/skills/games/akinator/src/lib/akinator.py b/skills/games/akinator/src/lib/akinator.py deleted file mode 100644 index 24ab9080..00000000 --- a/skills/games/akinator/src/lib/akinator.py +++ /dev/null @@ -1,242 +0,0 @@ -""" -MIT License - -Copyright (c) 2019 NinjaSnail1080 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -from .utils import get_lang_and_theme, ans_to_id, raise_connection_error -from .exceptions import CantGoBackAnyFurther -import re -import time -import json -try: - import requests - from requests.packages.urllib3.exceptions import InsecureRequestWarning - requests.packages.urllib3.disable_warnings(InsecureRequestWarning) -except ImportError: - pass - -request_timeout = 5 # secs - -# * URLs for the API requests -NEW_SESSION_URL = "https://{}/new_session?callback=jQuery331023608747682107778_{}&urlApiWs={}&partner=1&childMod={}&player=website-desktop&uid_ext_session={}&frontaddr={}&constraint=ETAT<>'AV'&soft_constraint={}&question_filter={}" -ANSWER_URL = "https://{}/answer_api?callback=jQuery331023608747682107778_{}&urlApiWs={}&childMod={}&session={}&signature={}&step={}&answer={}&frontaddr={}&question_filter={}" -BACK_URL = "{}/cancel_answer?callback=jQuery331023608747682107778_{}&childMod={}&session={}&signature={}&step={}&answer=-1&question_filter={}" -WIN_URL = "{}/list?callback=jQuery331023608747682107778_{}&childMod={}&session={}&signature={}&step={}" - -# * HTTP headers to use for the requests -HEADERS = { - "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "en-US,en;q=0.9", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) snap Chromium/81.0.4044.92 Chrome/81.0.4044.92 Safari/537.36", - "x-requested-with": "XMLHttpRequest", -} - - -class Akinator(): - """A class that represents an Akinator game. - - The first thing you want to do after calling an instance of this class is to call "start_game()". - """ - - def __init__(self): - self.uri = None - self.server = None - self.session = None - self.signature = None - self.uid = None - self.frontaddr = None - self.child_mode = None - self.question_filter = None - self.timestamp = None - - self.question = None - self.progression = None - self.step = None - - self.first_guess = None - self.guesses = None - - self.response = None - - def _update(self, resp, start=False): - """Update class variables""" - - if start: - self.session = int(resp["parameters"]["identification"]["session"]) - self.signature = int(resp["parameters"]["identification"]["signature"]) - self.question = str(resp["parameters"]["step_information"]["question"]) - self.progression = float(resp["parameters"]["step_information"]["progression"]) - self.step = int(resp["parameters"]["step_information"]["step"]) - else: - self.question = str(resp["parameters"]["question"]) - self.progression = float(resp["parameters"]["progression"]) - self.step = int(resp["parameters"]["step"]) - - def _parse_response(self, res): - """Parse the JSON response and turn it into a Python object""" - - return json.loads(",".join(res.split("(")[1::])[:-1]) - - def _get_session_info(self): - """Get uid and frontaddr from akinator.com/game""" - - info_regex = re.compile("var uid_ext_session = '(.*)'\\;\\n.*var frontaddr = '(.*)'\\;") - r = requests.get("https://en.akinator.com/game", timeout=request_timeout, verify=False) - - match = info_regex.search(r.text) - self.uid, self.frontaddr = match.groups()[0], match.groups()[1] - - def _auto_get_region(self, lang, theme): - """Automatically get the uri and server from akinator.com for the specified language and theme""" - - server_regex = re.compile("[{\"translated_theme_name\":\"[\\s\\S]*\",\"urlWs\":\"https:\\\\/\\\\/srv[0-9]+\\.akinator\\.com:[0-9]+\\\\/ws\",\"subject_id\":\"[0-9]+\"}]") - uri = lang + ".akinator.com" - - bad_list = ["https://srv12.akinator.com:9398/ws"] - while True: - r = requests.get("https://" + uri, timeout=request_timeout, verify=False) - - match = server_regex.search(r.text) - parsed = json.loads(match.group().split("'arrUrlThemesToPlay', ")[-1]) - - if theme == "c": - server = next((i for i in parsed if i["subject_id"] == "1"), None)["urlWs"] - elif theme == "a": - server = next((i for i in parsed if i["subject_id"] == "14"), None)["urlWs"] - elif theme == "o": - server = next((i for i in parsed if i["subject_id"] == "2"), None)["urlWs"] - - if server not in bad_list: - return {"uri": uri, "server": server} - - def start_game(self, language=None, child_mode=False): - """Start an Akinator game. Run this function first before the others. Returns a string containing the first question - - The "language" parameter can be left as None for English, the default language, or it can be set to one of the following (case-insensitive): - - "en": English (default) - - "en_animals": English server for guessing animals - - "en_objects": English server for guessing objects - - "ar": Arabic - - "cn": Chinese - - "de": German - - "de_animals": German server for guessing animals - - "es": Spanish - - "es_animals": Spanish server for guessing animals - - "fr": French - - "fr_animals": French server for guessing animals - - "fr_objects": French server for guessing objects - - "il": Hebrew - - "it": Italian - - "it_animals": Italian server for guessing animals - - "jp": Japanese - - "jp_animals": Japanese server for guessing animals - - "kr": Korean - - "nl": Dutch - - "pl": Polish - - "pt": Portuguese - - "ru": Russian - - "tr": Turkish - - "id": Indonesian - You can also put the name of the language spelled out, like "spanish", "korean", "french_animals", etc. - - The "child_mode" parameter is False by default. If it's set to True, then Akinator won't ask questions about things that are NSFW - """ - self.timestamp = time.time() - region_info = self._auto_get_region(get_lang_and_theme(language)["lang"], get_lang_and_theme(language)["theme"]) - self.uri, self.server = region_info["uri"], region_info["server"] - - self.child_mode = child_mode - soft_constraint = "ETAT%3D%27EN%27" if self.child_mode else "" - self.question_filter = "cat%3D1" if self.child_mode else "" - - self._get_session_info() - - r = requests.get(NEW_SESSION_URL.format(self.uri, self.timestamp, self.server, str(self.child_mode).lower(), self.uid, self.frontaddr, soft_constraint, self.question_filter), headers=HEADERS, timeout=request_timeout, verify=False) - self.response = r.text - resp = self._parse_response(r.text) - - if resp["completion"] == "OK": - self._update(resp, True) - return self.question - else: - return raise_connection_error(resp["completion"]) - - def answer(self, ans): - """Answer the current question, which you can find with "Akinator.question". Returns a string containing the next question - - The "ans" parameter must be one of these (case-insensitive): - - "yes" OR "y" OR "0" for YES - - "no" OR "n" OR "1" for NO - - "i" OR "idk" OR "i dont know" OR "i don't know" OR "2" for I DON'T KNOW - - "probably" OR "p" OR "3" for PROBABLY - - "probably not" OR "pn" OR "4" for PROBABLY NOT - """ - ans = ans_to_id(ans) - - r = requests.get(ANSWER_URL.format(self.uri, self.timestamp, self.server, str(self.child_mode).lower(), self.session, self.signature, self.step, ans, self.frontaddr, self.question_filter), headers=HEADERS, timeout=request_timeout, verify=False) - self.response = r.text - resp = self._parse_response(r.text) - - if resp["completion"] == "OK": - self._update(resp) - return self.question - else: - return raise_connection_error(resp["completion"]) - - def back(self): - """Goes back to the previous question. Returns a string containing that question - - If you're on the first question and you try to go back again, the CantGoBackAnyFurther exception will be raised - """ - if self.step == 0: - raise CantGoBackAnyFurther("You were on the first question and couldn't go back any further") - - r = requests.get(BACK_URL.format(self.server, self.timestamp, str(self.child_mode).lower(), self.session, self.signature, self.step, self.question_filter), headers=HEADERS, timeout=request_timeout, verify=False) - self.response = r.text - resp = self._parse_response(r.text) - - if resp["completion"] == "OK": - self._update(resp) - return self.question - else: - return utils.raise_connection_error(resp["completion"]) - - def win(self): - """Get Aki's guesses for who the person you're thinking of is based on your answers to the questions so far - - Defines and returns the variable "Akinator.first_guess", a dictionary describing his first choice for who you're thinking about. The three most important values in the dict are "name" (character's name), "description" (description of character), and "absolute_picture_path" (direct link to image of character) - - This function also defines "Akinator.guesses", which is a list of dictionaries containing his choices in order from most likely to least likely - - It's recommended that you call this function when Aki's progression is above 85%, which is when he will have most likely narrowed it down to just one choice. You can get his current progression via "Akinator.progression" - """ - r = requests.get(WIN_URL.format(self.server, self.timestamp, str(self.child_mode).lower(), self.session, self.signature, self.step), headers=HEADERS, timeout=request_timeout, verify=False) - self.response = r.text - resp = self._parse_response(r.text) - - if resp["completion"] == "OK": - self.first_guess = resp["parameters"]["elements"][0]["element"] - self.guesses = [g["element"] for g in resp["parameters"]["elements"]] - return self.first_guess - else: - return utils.raise_connection_error(resp["completion"]) diff --git a/skills/games/akinator/src/lib/exceptions.py b/skills/games/akinator/src/lib/exceptions.py deleted file mode 100644 index 06a68e49..00000000 --- a/skills/games/akinator/src/lib/exceptions.py +++ /dev/null @@ -1,63 +0,0 @@ -""" -MIT License - -Copyright (c) 2019 NinjaSnail1080 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - - -class InvalidAnswerError(ValueError): - """Raised when the user inputs an invalid answer""" - pass - - -class InvalidLanguageError(ValueError): - """Raised when the user inputs an invalid language""" - pass - - -class AkiConnectionFailure(Exception): - """Raised if the Akinator API fails to connect for some reason. Base class for AkiTimedOut, AkiNoQuestions, AkiServerDown, and AkiTechnicalError""" - pass - - -class AkiTimedOut(AkiConnectionFailure): - """Raised if the Akinator session times out. Derived from AkiConnectionFailure""" - pass - - -class AkiNoQuestions(AkiConnectionFailure): - """Raised if the Akinator API runs out of questions to ask. This will happen if "Akinator.step" is at 79 and the "answer" function is called again. Derived from AkiConnectionFailure""" - pass - - -class AkiServerDown(AkiConnectionFailure): - """Raised if Akinator's servers are down for the region you're running on. If this happens, try again later or use a different language. Derived from AkiConnectionFailure""" - pass - - -class AkiTechnicalError(AkiConnectionFailure): - """Raised if Aki's servers had a technical error. If this happens, try again later or use a different language. Derived from AkiConnectionFailure""" - pass - - -class CantGoBackAnyFurther(Exception): - """Raised when the user is on the first question and tries to go back further""" - pass diff --git a/skills/games/akinator/src/lib/main.py b/skills/games/akinator/src/lib/main.py new file mode 100644 index 00000000..eb193b49 --- /dev/null +++ b/skills/games/akinator/src/lib/main.py @@ -0,0 +1,131 @@ +import requests +from bs4 import BeautifulSoup + +class AkinatorError(Exception): + pass +class Akinator(): + def __init__(self,theme:str="characters",lang:str="jp",child_mode:bool=False) -> None: + self.ENDPOINT=f"https://{lang}.akinator.com/" + self.name=None + self.description=None + self.photo=None + self.answer_id=None + self.akitude=None + if theme=="characters": + sid=1 + elif theme=="objects": + sid=2 + elif theme=="animals": + sid=14 + else: + raise AkinatorError("the theme must be 'characters' / 'objects' / 'animals'") + self.json={ + "step":0, + "progression":0.0, + "sid":sid, + "cm":child_mode, + "answer":0, + } + + def start_game(self): + self.name=None + self.description=None + self.photo=None + self.answer_id=None + self.akitude="https://en.akinator.com/assets/img/akitudes_670x1096/defi.png" + game=requests.post(f"{self.ENDPOINT}game",json={"sid":self.json["sid"],"cm":self.json["cm"]}).text + soup = BeautifulSoup(game,"html.parser") + askSoundlike=soup.find(id="askSoundlike") + question_label=soup.find(id="question-label").get_text() + session_id=askSoundlike.find(id="session").get("value") + signature_id=askSoundlike.find(id="signature").get("value") + self.json["session"]=session_id + self.json["signature"]=signature_id + self.step=0 + self.progression=0.0 + self.question=question_label + return question_label + + def post_answer(self,answer:str): + if answer=="y": + self.json["answer"]=0 + elif answer=="n": + self.json["answer"]=1 + elif answer=="idk": + self.json["answer"]=2 + elif answer=="p": + self.json["answer"]=3 + elif answer=="pn": + self.json["answer"]=4 + else: + raise AkinatorError("the answer must be 'y' / 'n' / 'idk' / 'p' / 'pn'") + try: + progression=requests.post(f"{self.ENDPOINT}answer",json=self.json) + progression=progression.json() + if progression["completion"]=="KO": + raise AkinatorError("completion : KO") + elif progression["completion"]=="SOUNDLIKE": + raise AkinatorError("completion : SOUNDLIKE") + try: + self.json["step"]=int(progression["step"]) + self.json["progression"]=float(progression["progression"]) + self.step=int(progression["step"]) + self.progression=float(progression["progression"]) + self.question=progression["question"] + self.question_id=progression["question_id"] + self.akitude=f"https://en.akinator.com/assets/img/akitudes_670x1096/{progression['akitude']}" + except: + self.name=progression["name_proposition"] + self.description=progression["description_proposition"] + self.photo=progression["photo"] + self.answer_id=progression["id_proposition"] + self.json["step_last_proposition"]=int(self.json["step"]) + + return progression + + except Exception as e: + raise AkinatorError(progression) + + def go_back(self): + self.name=None + self.description=None + self.photo=None + self.answer_id=None + if self.json["step"]==0: + raise AkinatorError("it's first question") + if "answer" in self.json: + del self.json["answer"] + try: + goback=requests.post(f"{self.ENDPOINT}cancel_answer",json=self.json) + goback=goback.json() + self.json["step"]=int(goback["step"]) + self.json["progression"]=float(goback["progression"]) + self.step=int(goback["step"]) + self.progression=float(goback["progression"]) + self.question=goback["question"] + self.question_id=goback["question_id"] + self.akitude=f"https://en.akinator.com/assets/img/akitudes_670x1096/{goback['akitude']}" + return goback + except: + raise AkinatorError(goback) + + def exclude(self): + self.name=None + self.description=None + self.photo=None + self.answer_id=None + if "answer" in self.json: + del self.json["answer"] + try: + exclude=requests.post(f"{self.ENDPOINT}exclude",json=self.json) + exclude=exclude.json() + self.json["step"]=int(exclude["step"]) + self.json["progression"]=float(exclude["progression"]) + self.step=int(exclude["step"]) + self.progression=float(exclude["progression"]) + self.question=exclude["question"] + self.question_id=exclude["question_id"] + self.akitude=f"https://en.akinator.com/assets/img/akitudes_670x1096/{exclude['akitude']}" + return exclude + except: + raise AkinatorError(exclude) diff --git a/skills/games/akinator/src/lib/memory.py b/skills/games/akinator/src/lib/memory.py index 167e6280..4ddbe486 100644 --- a/skills/games/akinator/src/lib/memory.py +++ b/skills/games/akinator/src/lib/memory.py @@ -1,18 +1,17 @@ from bridges.python.src.sdk.memory import Memory -from typing import TypedDict, Any +from typing import TypedDict, NotRequired class Session(TypedDict): - response: str - session: int + question: str progression: float - signature: int - uri: str - timestamp: float - server: Any - child_mode: bool - frontaddr: str - question_filter: str + step: int + session: str + signature: str + lang: str + theme: str + sid: int + cm: bool session_memory = Memory({ diff --git a/skills/games/akinator/src/lib/question_widget.py b/skills/games/akinator/src/lib/question_widget.py new file mode 100644 index 00000000..1a26d024 --- /dev/null +++ b/skills/games/akinator/src/lib/question_widget.py @@ -0,0 +1,7 @@ +from bridges.python.src.sdk.widget import WidgetOptions +from ..widgets.number_widget import NumberWidget, NumberWidgetParams + +number_widget_options: WidgetOptions[NumberWidgetParams] = WidgetOptions( + params={'random_number': random_number} +) +number_widget = NumberWidget(number_widget_options) diff --git a/skills/games/akinator/src/lib/utils.py b/skills/games/akinator/src/lib/utils.py deleted file mode 100644 index a72eb7af..00000000 --- a/skills/games/akinator/src/lib/utils.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -MIT License - -Copyright (c) 2019 NinjaSnail1080 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -""" - -from .exceptions import InvalidAnswerError, InvalidLanguageError, AkiServerDown, AkiTechnicalError, AkiTimedOut, AkiNoQuestions, AkiConnectionFailure - - -def ans_to_id(ans): - """Convert an input answer string into an Answer ID for Akinator""" - - ans = str(ans).lower() - if ans == "yes" or ans == "y" or ans == "0": - return "0" - elif ans == "no" or ans == "n" or ans == "1": - return "1" - elif ans == "i" or ans == "idk" or ans == "i dont know" or ans == "i don't know" or ans == "2": - return "2" - elif ans == "probably" or ans == "p" or ans == "3": - return "3" - elif ans == "probably not" or ans == "pn" or ans == "4": - return "4" - else: - raise InvalidAnswerError(""" - You put "{}", which is an invalid answer. - The answer must be one of these: - - "yes" OR "y" OR "0" for YES - - "no" OR "n" OR "1" for NO - - "i" OR "idk" OR "i dont know" OR "i don't know" OR "2" for I DON'T KNOW - - "probably" OR "p" OR "3" for PROBABLY - - "probably not" OR "pn" OR "4" for PROBABLY NOT - """.format(ans)) - - -def get_lang_and_theme(lang=None): - """Returns the language code and theme based on what is input""" - - if lang is None: - return {"lang": "en", "theme": "c"} - - lang = str(lang).lower() - if lang == "en" or lang == "english": - return {"lang": "en", "theme": "c"} - elif lang == "en_animals" or lang == "english_animals": - return {"lang": "en", "theme": "a"} - elif lang == "en_objects" or lang == "english_objects": - return {"lang": "en", "theme": "o"} - elif lang == "ar" or lang == "arabic": - return {"lang": "ar", "theme": "c"} - elif lang == "cn" or lang == "chinese": - return {"lang": "cn", "theme": "c"} - elif lang == "de" or lang == "german": - return {"lang": "de", "theme": "c"} - elif lang == "de_animals" or lang == "german_animals": - return {"lang": "de", "theme": "a"} - elif lang == "es" or lang == "spanish": - return {"lang": "es", "theme": "c"} - elif lang == "es_animals" or lang == "spanish_animals": - return {"lang": "es", "theme": "a"} - elif lang == "fr" or lang == "french": - return {"lang": "fr", "theme": "c"} - elif lang == "fr_animals" or lang == "french_animals": - return {"lang": "fr", "theme": "a"} - elif lang == "fr_objects" or lang == "french_objects": - return {"lang": "fr", "theme": "o"} - elif lang == "il" or lang == "hebrew": - return {"lang": "il", "theme": "c"} - elif lang == "it" or lang == "italian": - return {"lang": "it", "theme": "c"} - elif lang == "it_animals" or lang == "italian_animals": - return {"lang": "it", "theme": "a"} - elif lang == "jp" or lang == "japanese": - return {"lang": "jp", "theme": "c"} - elif lang == "jp_animals" or lang == "japanese_animals": - return {"lang": "jp", "theme": "a"} - elif lang == "kr" or lang == "korean": - return {"lang": "kr", "theme": "c"} - elif lang == "nl" or lang == "dutch": - return {"lang": "nl", "theme": "c"} - elif lang == "pl" or lang == "polish": - return {"lang": "pl", "theme": "c"} - elif lang == "pt" or lang == "portuguese": - return {"lang": "pt", "theme": "c"} - elif lang == "ru" or lang == "russian": - return {"lang": "ru", "theme": "c"} - elif lang == "tr" or lang == "turkish": - return {"lang": "tr", "theme": "c"} - elif lang == "id" or lang == "indonesian": - return {"lang": "id", "theme": "c"} - else: - raise InvalidLanguageError("You put \"{}\", which is an invalid language.".format(lang)) - - -def raise_connection_error(response): - """Raise the proper error if the API failed to connect""" - - if response == "KO - SERVER DOWN": - raise AkiServerDown("Akinator's servers are down in this region. Try again later or use a different language") - elif response == "KO - TECHNICAL ERROR": - raise AkiTechnicalError("Akinator's servers have had a technical error. Try again later or use a different language") - elif response == "KO - TIMEOUT": - raise AkiTimedOut("Your Akinator session has timed out") - elif response == "KO - ELEM LIST IS EMPTY" or response == "WARN - NO QUESTION": - raise AkiNoQuestions("\"Akinator.step\" reached 79. No more questions") - else: - raise AkiConnectionFailure("An unknown error has occured. HttpServer response: {}".format(response)) diff --git a/skills/leon/random_number/src/actions/run.py b/skills/leon/random_number/src/actions/run.py index d2b48708..65792ce7 100644 --- a/skills/leon/random_number/src/actions/run.py +++ b/skills/leon/random_number/src/actions/run.py @@ -4,7 +4,6 @@ from bridges.python.src.sdk.types import ActionParams from random import randint from bridges.python.src.sdk.widget import WidgetOptions - from ..widgets.number_widget import NumberWidget, NumberWidgetParams diff --git a/skills/productivity/todo_list/src/widgets/todos_list_widget.py b/skills/productivity/todo_list/src/widgets/todos_list_widget.py index e86a45cb..ca78689e 100644 --- a/skills/productivity/todo_list/src/widgets/todos_list_widget.py +++ b/skills/productivity/todo_list/src/widgets/todos_list_widget.py @@ -31,7 +31,6 @@ class TodosListWidget(Widget[TodosListWidgetParams]): 'children': [Checkbox({ 'label': todo['name'], 'checked': todo['is_completed'], - # TODO 'onChange': self.run_skill_action(f'productivity:todo_list:{action_name}', { 'entities': [ {