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:
parent
67bc7ddb32
commit
185be39172
@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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': {
|
||||
|
@ -1 +0,0 @@
|
||||
5.0.0
|
@ -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'
|
||||
|
@ -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"])
|
@ -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
|
131
skills/games/akinator/src/lib/main.py
Normal file
131
skills/games/akinator/src/lib/main.py
Normal 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)
|
@ -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({
|
||||
|
7
skills/games/akinator/src/lib/question_widget.py
Normal file
7
skills/games/akinator/src/lib/question_widget.py
Normal 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)
|
@ -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))
|
@ -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
|
||||
|
||||
|
||||
|
@ -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': [
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user