1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-12-23 16:53:04 +03:00

feat(skill/akinator): complete rewrite

This commit is contained in:
louistiti 2024-09-05 22:14:18 +08:00
parent 67bc7ddb32
commit 185be39172
14 changed files with 270 additions and 600 deletions

View File

@ -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": []
}
]
}

View File

@ -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",

View File

@ -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
}

View File

@ -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': {

View File

@ -1 +0,0 @@
5.0.0

View File

@ -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'

View File

@ -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"])

View File

@ -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

View File

@ -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)

View File

@ -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({

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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': [
{