Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

have to add about Windows base running. #122

Open
po2son opened this issue Jul 23, 2024 · 0 comments
Open

have to add about Windows base running. #122

po2son opened this issue Jul 23, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@po2son
Copy link

po2son commented Jul 23, 2024

Please describe which problem should be solved

Error msg like this coz window not include 'fcntl'
[comment]:

    jellyfin_alexa_skill --config /path/to/skill.conf --data /path/to/skill/data/
    Traceback (most recent call last):
      File "<frozen runpy>", line 198, in _run_module_as_main
      File "<frozen runpy>", line 88, in _run_code
      File "C:\Python312\Scripts\jellyfin_alexa_skill.exe\__main__.py", line 4, in <module>
      File "C:\Python312\Lib\site-packages\jellyfin_alexa_skill\main.py", line 21, in <module>
        from gunicorn.app.base import BaseApplication
      File "C:\Python312\Lib\site-packages\gunicorn\app\base.py", line 11, in <module>
        from gunicorn import util
      File "C:\Python312\Lib\site-packages\gunicorn\util.py", line 8, in <module>
        import fcntl
    ModuleNotFoundError: No module named 'fcntl'

Optional: Describe your idea for a solution

So have to change to 'waitress'
[comment]:

jellyfin-alexa-skill\main.py have to change like this.

import argparse
import binascii
import logging
import os
import textwrap
import uuid
from configparser import ConfigParser
from copy import deepcopy
from pathlib import Path
from typing import Union, Tuple

import ask_sdk_model_runtime
from ask_smapi_model.services.skill_management import SkillManagementServiceClient
from ask_smapi_model.v1.skill.account_linking import AccountLinkingRequest, AccountLinkingRequestPayload, AccountLinkingType
from ask_smapi_model.v1.skill.manifest import SSLCertificateType, SkillManifestEndpoint, SkillManifestEnvelope
from ask_smapi_sdk import StandardSmapiClientBuilder
from flask import Flask
from flask_ask_sdk.skill_adapter import SkillAdapter
from flask_wtf import CSRFProtect
# from gunicorn.app.base import BaseApplication
from pyngrok import conf, ngrok

from jellyfin_alexa_skill import __version__
from jellyfin_alexa_skill.alexa.handler import get_skill_builder
from jellyfin_alexa_skill.alexa.setup.interaction.model import INTERACTION_MODELS
from jellyfin_alexa_skill.alexa.setup.manifest.manifest import get_skill_version, SKILL_MANIFEST
from jellyfin_alexa_skill.alexa.web.skill import get_skill_blueprint
from jellyfin_alexa_skill.config import get_config, DEFAULT_ALEXA_SKILL_CONFIG_PATH, DEFAULT_ALEXA_SKILL_DATA_PATH, APP_NAME, write_config
from jellyfin_alexa_skill.database.db import connect_db
from jellyfin_alexa_skill.jellyfin.api.client import JellyfinClient
from jellyfin_alexa_skill.jellyfin.web.login import get_jellyfin_login_blueprint

from waitress import serve

logging.basicConfig(format="%(levelname)s:%(message)s", level=logging.INFO)

def main():
    parser = argparse.ArgumentParser(
        description="Jellyfin Alexa Skill",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=textwrap.dedent('''\
            Example:
                jellyfin_alexa_skill --config /path/to/skill.conf --data /path/to/skill/data/
            '''))
    parser.add_argument('--config', type=str, default=DEFAULT_ALEXA_SKILL_CONFIG_PATH,
                        help='path to the config file')
    parser.add_argument('--data', type=str, default=DEFAULT_ALEXA_SKILL_DATA_PATH,
                        help='path to the data directory')
    parser.add_argument('--setup', action='store_true',
                        help='run skill setup')
    parser.add_argument('--ngrok', action='store_true',
                        help='start ngrok tunnel for development')
    parser.add_argument('--port', type=int, default=None,
                        help='port to run the web application')
    args = parser.parse_args()

    config_path = Path(args.config)
    data_path = Path(args.data)

    config = get_config(config_path)
    csrf = CSRFProtect()

    app = Flask(APP_NAME)
    app.config['SECRET_KEY'] = binascii.hexlify(os.urandom(24))
    csrf.init_app(app)

    jellyfin_endpoint = config["general"]["jellyfin_endpoint"]
    host = config["general"].get("host", "127.0.0.1")

    if args.ngrok:
        conf.get_default().region = config["ngrok"].get("region", "us")
        public_url = ngrok.connect(args.port if args.port else config["general"]["web_app_port"])
        logging.info(" * ngrok tunnel \"{}\" -> \"http://127.0.0.1:{}/\"".format(public_url, args.port))

    if args.setup:
        setup_skill(config, config_path, data_path, app, csrf, jellyfin_endpoint, host)
    else:
        data_path.mkdir(parents=True, exist_ok=True)
        connect_db(data_path / "data.sqlite")
        jellyfin_client = JellyfinClient(server_endpoint=jellyfin_endpoint, client_name=APP_NAME)
        skill_adapter = SkillAdapter(skill=get_skill_builder(jellyfin_client).create(), skill_id=config["general"]["skill_id"], app=app)
        skill_blueprint = get_skill_blueprint(skill_adapter)
        csrf.exempt(skill_blueprint)
        app.register_blueprint(skill_blueprint)
        login_blueprint = get_jellyfin_login_blueprint(jellyfin_endpoint, config["smapi"]["client_id"])
        app.register_blueprint(login_blueprint)
        
        port = args.port if args.port else config["general"]["web_app_port"]
        serve(app, host=host, port=port)


def setup_skill(config, config_path, data_path, app, csrf, jellyfin_endpoint, host):
    smapi_client = get_smapi_client(config["smapi"]["client_id"], config["smapi"]["client_secret"])
    stage = config["general"].get("stage", "development")
    skill_id = config["general"]["skill_id"]

    if skill_id is None:
        skill_id = create_skill(smapi_client, config["smapi"]["vendor_id"], stage)
        config["general"]["skill_id"] = skill_id
        write_config(config, config_path)
        logging.info(f"Created new skill with id: {skill_id}")

    skill_endpoint = SkillManifestEndpoint(
        uri=f"{host}/api/alexa",
        ssl_certificate_type=SSLCertificateType.SELF_SIGNED
    )

    manifest = deepcopy(SKILL_MANIFEST)
    manifest["apis"]["custom"]["endpoint"] = skill_endpoint

    account_linking_request = get_account_linking_request(config, host)
    smapi_client.call_create_account_linking(skill_id, account_linking_request, stage)
    smapi_client.call_update_skill_manifest(skill_id, SkillManifestEnvelope(manifest), stage)
    logging.info(f"Updated skill manifest for skill id: {skill_id}")

    logging.info(" * Skill setup completed")

    data_path.mkdir(parents=True, exist_ok=True)
    connect_db(data_path / "data.sqlite")

    jellyfin_client = JellyfinClient(server_endpoint=jellyfin_endpoint, client_name=APP_NAME)
    skill_adapter = SkillAdapter(skill=get_skill_builder(jellyfin_client).create(), skill_id=skill_id, app=app)
    skill_blueprint = get_skill_blueprint(skill_adapter)
    csrf.exempt(skill_blueprint)
    app.register_blueprint(skill_blueprint)

    login_blueprint = get_jellyfin_login_blueprint(jellyfin_endpoint, config["smapi"]["client_id"])
    app.register_blueprint(login_blueprint)

    port = config["general"]["web_app_port"]
    serve(app, host=host, port=port)


def get_smapi_client(client_id: str, client_secret: str) -> SkillManagementServiceClient:
    api_endpoint = os.getenv("ASK_SMAPI_SERVER", "https://api.amazonalexa.com")
    return StandardSmapiClientBuilder().with_client_id(client_id).with_client_secret(client_secret).with_refresh_token(uuid.uuid4()).with_api_endpoint(api_endpoint).build()


def create_skill(smapi_client: SkillManagementServiceClient, vendor_id: str, stage: str) -> str:
    manifest = deepcopy(SKILL_MANIFEST)
    response = smapi_client.call_create_skill_v1(SkillManifestEnvelope(manifest), vendor_id, stage)
    return response["skillId"]


def get_account_linking_request(config: ConfigParser, host: str) -> AccountLinkingRequest:
    return AccountLinkingRequest(
        account_linking_request_payload=AccountLinkingRequestPayload(
            access_token_uri=config["smapi"]["access_token_uri"],
            client_id=config["smapi"]["client_id"],
            scopes=config["smapi"]["scopes"],
            client_secret=config["smapi"]["client_secret"],
            authorization_uri=config["smapi"]["authorization_uri"],
            domains=config["smapi"]["domains"],
            redirect_urls=[f"{host}/authresponse"],
            access_token_scheme="HTTP_BASIC",
            default_access_token_expiration_seconds=config["smapi"].getint("default_access_token_expiration_seconds", 3600),
            grant_type=AccountLinkingType.AUTH_CODE,
        )
    )


if __name__ == "__main__":
    main()

and run

    python ./jellyfin_alexa_skill/main.py --config D:/work/jellyfin_alexa_skill/skill.conf --data D:/work/jellyfin_alexa_skill/data/

I solved like this.

@po2son po2son added the enhancement New feature or request label Jul 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant