diff --git a/requirements.txt b/requirements.txt index be3368ad..fc4f382f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +overrides flake8 mypy mypy_extensions diff --git a/src/wechaty/accessory.py b/src/wechaty/accessory.py new file mode 100644 index 00000000..a9b1ebde --- /dev/null +++ b/src/wechaty/accessory.py @@ -0,0 +1,83 @@ +""" +docstring +""" +from abc import ABCMeta +from typing import Optional +from wechaty_puppet.puppet import Puppet +from .config import LOG +from .wechaty import Wechaty + + +class Accessory: + """ + docstring + """ + __metaclass__ = ABCMeta + _puppet: Optional[Puppet] = None + # static _wechaty property to doing ... + _wechaty: Optional[Wechaty] = None + _counter: int = 0 + + def __init__(self, name: str = "accessory"): + """ + initialize the accessory instance + """ + self.name: str = name + # increase when Accessory is initialized + self._counter += 1 + + def __str__(self) -> str: + """ + docstring + :return: the base accessory class name + """ + return "Accessory instance : %s" % self.name + + @classmethod + def puppet(cls, value: Optional[Puppet] = None) -> Optional[Puppet]: + """ + get/set global single instance of the puppet + :return: + """ + if value is None: + if cls._puppet is None: + raise AttributeError("static puppet instance not found ...") + LOG.info("get puppet instance %s ...", + cls._puppet.name) + return cls._puppet + + if cls._puppet is not None: + raise AttributeError("can't set puppet instance %s twice" % + cls._puppet.name) + LOG.info("set puppet instance %s ...", + value.name) + cls._puppet = value + return None + + @classmethod + def wechaty(cls, value: Optional[Wechaty] = None) -> Optional[Wechaty]: + """ + get/set wechaty instance + + If the param of value is None, then the function will return the + instance of wechaty.Otherwise, the function will check the type + of the value, and set as wechaty instance + :param value: + :return: + """ + if value is None: + if cls._wechaty is None: + raise AttributeError("wechaty instance not found") + LOG.info("get wechaty instance %s", + cls._wechaty.name) + return cls._wechaty + if not isinstance(value, Wechaty): + raise NameError( + "expected wechaty instance type is Wechaty, " + "but got %s" % value.__class__ + ) + if cls._wechaty is not None: + raise AttributeError("can't set wechaty instance %s twice" % + cls._wechaty.name) + cls._wechaty = value + return None diff --git a/src/wechaty/config.py b/src/wechaty/config.py index 3fcf1fb0..2e489437 100644 --- a/src/wechaty/config.py +++ b/src/wechaty/config.py @@ -1,15 +1,30 @@ -''' -config module -''' +""" + * + * Wechaty - https://github.com/wechaty/python-wechaty + * + * @copyright wechaty + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * + """ import logging import os +import re +from typing import Optional +from wechaty_puppet.file_box import FileBox -logging.basicConfig( - filename="logging.log", - level=logging.DEBUG, -) - -log = logging.getLogger('Wechaty') +LOG = logging.getLogger(__name__) # log.debug('test logging debug') # log.info('test logging info') @@ -21,3 +36,91 @@ '../data', ), ) + + +def global_exception_handler(e: Exception) -> None: + """ + handle the global exception + :param e: exception message + :return: + """ + LOG.error("occur %s %s", e.__class__.__name__, str(e.args)) + print(e) + + +class DefaultSetting(dict): + """ + store global default setting + """ + default_api_host: Optional[str] = None + default_port: Optional[int] = None + default_protocol: Optional[str] = None + + +# pylint: disable=R0903 +def valid_api_host(api_host: str) -> bool: + """ + test the validation of the api_host + :param api_host: + :return: + """ + pattern = re.compile( + r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|:?[0-9]*' + r'([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|:?[0-9]*' + r'([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.:?[0-9]*' + r'([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3}):?[0-9]*$' + ) + return bool(pattern.match(api_host)) + + +class Config: + """ + store python-wechaty configuration + """ + def __init__(self, + api_host: Optional[str] = None, + token: Optional[str] = None, + protocol: Optional[str] = None, + http_port: Optional[int] = None, + name: str = "python-wechaty", + debug: bool = True, + docker: bool = False): + """ + initialize the configuration + """ + self.default = DefaultSetting + + self.api_host = api_host if api_host is not None \ + else DefaultSetting.default_api_host + + self.http_port = http_port if http_port is not None \ + else DefaultSetting.default_port + + self.protocol = protocol if protocol is not None \ + else DefaultSetting.default_protocol + + if token is None: + raise AttributeError("token can't be None") + + self.name = name + self.debug = debug + self.docker = docker + + if self.api_host is not None and not valid_api_host(self.api_host): + raise AttributeError("api host %s is not valid" % self.api_host) + + +# export const CHATIE_OFFICIAL_ACCOUNT_ID = 'gh_051c89260e5d' +chatie_official_account_id = "gh_051c89260e5d" + + +def qr_code_for_chatie() -> FileBox: + """ + create QRcode for chatie + :return: + """ + # const CHATIE_OFFICIAL_ACCOUNT_QRCODE = + # 'http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5' + chatie_official_account_qr_code: str = \ + 'http://weixin.qq.com/r/qymXj7DEO_1ErfTs93y5' + return FileBox.from_qr_code(chatie_official_account_qr_code) diff --git a/src/wechaty/config_test.py b/src/wechaty/config_test.py index 41be37b0..11f885a1 100644 --- a/src/wechaty/config_test.py +++ b/src/wechaty/config_test.py @@ -1,6 +1,6 @@ -''' +""" config unit test -''' +""" from typing import ( Any, # Dict, @@ -9,7 +9,7 @@ import pytest # type: ignore from .config import ( - log, + LOG, ) # pylint: disable=redefined-outer-name @@ -25,14 +25,14 @@ def fixture_data() -> Iterable[str]: def test_config( data: Any, ) -> None: - ''' + """ Unit Test for config function - ''' + """ print(data) assert data == 'test', 'data should equals test' def test_log(): - '''test''' - assert log, 'log should exist' + """test""" + assert LOG, 'log should exist' diff --git a/src/wechaty/images.py b/src/wechaty/images.py new file mode 100644 index 00000000..68d81fc6 --- /dev/null +++ b/src/wechaty/images.py @@ -0,0 +1,88 @@ +""" +docstring +""" +from enum import IntEnum +from typing import Type, TypeVar +from wechaty_puppet.file_box import FileBox +from .accessory import Accessory +from .config import LOG + + +class ImageType(IntEnum): + """ + docstring ... + """ + Thumbnail = 0 + HD = 1 + Artwork = 2 + + +T = TypeVar("T", bound="Image") + + +class Image(Accessory): + """ + docstring ... + """ + + def __str__(self): + return "image instance : %d" % self.image_id + + def __init__(self, image_id: str) -> None: + """ + :param image_id: + """ + super(Image, self).__init__() + self.image_id = image_id + LOG.info("create image : %d", self.image_id) + if self.puppet is None: + raise NotImplementedError("Image class can not be instanced" + " without a puppet!") + + @classmethod + def create(cls: Type[T], image_id: str) -> T: + """ + create image instance by image_id + :param cls: + :param image_id: + :return: + """ + LOG.info("create static image : %d", image_id) + return cls(image_id) + + async def thumbnail(self) -> FileBox: + """ + docstring + :return: + """ + LOG.info("image thumbnail for %d", self.image_id) + puppet = self.puppet() + if puppet is None: + raise AttributeError + file_box = await puppet.message_image(self.image_id, + ImageType.Thumbnail) + return file_box + + async def hd(self) -> FileBox: + """ + docstring + :return: + """ + LOG.info("image hd for %d", self.image_id) + puppet = self.puppet() + if puppet is None: + raise AttributeError + file_box = await puppet.message_image(self.image_id, ImageType.HD) + return file_box + + async def artwork(self) -> FileBox: + """ + docstring + :return: + """ + LOG.info("image artwork for %d", self.image_id) + puppet = self.puppet() + if puppet is None: + raise AttributeError + file_box = await puppet.message_image(self.image_id, ImageType.Artwork) + return file_box diff --git a/src/wechaty/user/__init__.py b/src/wechaty/user/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/wechaty/user/contact.py b/src/wechaty/user/contact.py new file mode 100644 index 00000000..e69de29b diff --git a/src/wechaty/user/room.py b/src/wechaty/user/room.py new file mode 100644 index 00000000..c2dbfacb --- /dev/null +++ b/src/wechaty/user/room.py @@ -0,0 +1,5 @@ +""" +python-implementation for room +""" +from threading import Event, Thread +from src.wechaty.accessory import Accessory diff --git a/src/wechaty/wechaty.py b/src/wechaty/wechaty.py new file mode 100644 index 00000000..96fd8a4a --- /dev/null +++ b/src/wechaty/wechaty.py @@ -0,0 +1,46 @@ +""" +wechaty instance +""" +from typing import Optional +from .config import LOG + + +# pylint: disable=R0903 +class WechatyOptions: + """ + WechatyOptions instance + """ + def __init__(self): + """ + WechatyOptions constructor + """ + self.io_token: str = None + self.name: str = None + self.profile: Optional[None or str] = None + + +# pylint: disable=R0903 +class Wechaty: + """ + docstring + """ + def __init__(self, name: str = "wechaty"): + """ + docstring + """ + self.name = name + + _global_instance: Optional["Wechaty"] = None + + async def start(self) -> None: + """ + start the wechaty + :return: + """ + LOG.info("wechaty is starting ...") + + async def stop(self) -> None: + """ + stop the wechaty + """ + LOG.info("wechaty is stoping ...") diff --git a/wechaty_puppet/__init__.py b/wechaty_puppet/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wechaty_puppet/file_box.py b/wechaty_puppet/file_box.py new file mode 100644 index 00000000..ace40c46 --- /dev/null +++ b/wechaty_puppet/file_box.py @@ -0,0 +1,36 @@ +""" +docstring +""" +from typing import Type, TypeVar + +T = TypeVar("T") + + +# dummy class +class FileBox: + """ + maintain the file content, which is sended by wechat + """ + + def to_json(self) -> dict: + """ + dump the file content to json object + :return: + """ + raise NotImplementedError + + def to_file(self, file_path: str) -> None: + """ + save the content to the file + :return: + """ + raise NotImplementedError + + @classmethod + def from_qr_code(cls: Type[T], code:str) -> "FileBox": + """ + create filebox from qrcode + :param code: + :return: + """ + raise NotImplementedError diff --git a/wechaty_puppet/puppet.py b/wechaty_puppet/puppet.py new file mode 100644 index 00000000..dfefbec1 --- /dev/null +++ b/wechaty_puppet/puppet.py @@ -0,0 +1,32 @@ +""" +interface for puppet +""" +from enum import Enum +# from typing import Awaitable +from .file_box import FileBox + +class Puppet: + """ + puppet interface class + """ + + def __init__(self): + self.name: str = "puppet" + + # pylint: disable=R0201 + async def message_image(self, message_id: str, image_type: Enum) -> FileBox: + """ + docstring + :param message_id: + :param image_type: + :return: + """ + raise NotImplementedError + + def start(self) -> None: + """ + start the puppet + :return: + """ + raise NotImplementedError +