Przeglądaj źródła

Merge branch 'master' into dev

JoostSijm 3 lat temu
rodzic
commit
d5a15e40cd

+ 4 - 4
Pipfile

@@ -9,17 +9,17 @@ pytest = "*"
 pytest-vcr = "*"
 tox = "*"
 tox-venv = "*"
+pylint = "*"
 
 [packages]
 appdirs = "*"
 beautifulsoup4 = "*"
 cfscrape = "*"
-requests = "*"
-webbot = "*"
 python-dateutil = "*"
 pathlib2 = "*"
-selenium-stealth= "*"
-selenium = "*"
+requests = "*"
+selenium-stealth = "*"
+webbot = "*"
 
 [requires]
 python_version = "3"

+ 80 - 22
Pipfile.lock

@@ -1,7 +1,7 @@
 {
     "_meta": {
         "hash": {
-            "sha256": "fc0e2fd70a9660274318e874f607a1a93ed7e549023b60e1e9c08a48810b833e"
+            "sha256": "a369f80373274e99e967fe2f267be5b19893c3bc9e2d56028b84a0af0c814ea6"
         },
         "pipfile-spec": 6,
         "requires": {
@@ -53,6 +53,7 @@
                 "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
                 "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
             "version": "==4.0.0"
         },
         "idna": {
@@ -60,6 +61,7 @@
                 "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
                 "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.10"
         },
         "pathlib2": {
@@ -91,7 +93,6 @@
                 "sha256:2d7131d7bc5a5b99a2d9b04aaf2612c411b03b8ca1b1ee8d3de5845a9be2cb3c",
                 "sha256:deaf32b60ad91a4611b98d8002757f29e6f2c2d5fcaf202e1c9ad06d6772300d"
             ],
-            "index": "pypi",
             "version": "==3.141.0"
         },
         "selenium-stealth": {
@@ -106,6 +107,7 @@
                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.15.0"
         },
         "soupsieve": {
@@ -113,7 +115,7 @@
                 "sha256:052774848f448cf19c7e959adf5566904d525f33a3f8b6ba6f6f8f26ec7de0cc",
                 "sha256:c2c1c2d44f158cdbddab7824a9af8c4f83c76b1e23e049479aa432feb6c4c23b"
             ],
-            "markers": "python_version >= '3.0'",
+            "markers": "python_version >= '3'",
             "version": "==2.2.1"
         },
         "urllib3": {
@@ -121,6 +123,7 @@
                 "sha256:2f4da4594db7e1e110a944bb1b551fdf4e6c136ad42e4234131391e21eb5b0df",
                 "sha256:e7b021f7241115872f92f43c6508082facffbd1c048e3c6e2bb9c2a157e28937"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'",
             "version": "==1.26.4"
         },
         "webbot": {
@@ -141,29 +144,22 @@
             "index": "pypi",
             "version": "==1.4.4"
         },
-        "atomicwrites": {
+        "astroid": {
             "hashes": [
-                "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197",
-                "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"
+                "sha256:6b0ed1af831570e500e2437625979eaa3b36011f66ddfc4ce930128610258ca9",
+                "sha256:cd80bf957c49765dce6d92c43163ff9d2abc43132ce64d4b1b47717c6d2522df"
             ],
-            "markers": "sys_platform == 'win32'",
-            "version": "==1.4.0"
+            "markers": "python_version >= '3.6'",
+            "version": "==2.5.2"
         },
         "attrs": {
             "hashes": [
                 "sha256:31b2eced602aa8423c2aea9c76a724617ed67cf9513173fd3a4f03e3a929c7e6",
                 "sha256:832aa3cde19744e49938b91fea06d69ecb9e649c93ba974535d08ad92164f700"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==20.3.0"
         },
-        "colorama": {
-            "hashes": [
-                "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
-                "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"
-            ],
-            "markers": "sys_platform == 'win32'",
-            "version": "==0.4.4"
-        },
         "distlib": {
             "hashes": [
                 "sha256:8c09de2c67b3e7deef7184574fc060ab8a793e7adbb183d942c389c8b13c52fb",
@@ -183,6 +179,7 @@
                 "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6",
                 "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.10"
         },
         "iniconfig": {
@@ -192,6 +189,49 @@
             ],
             "version": "==1.1.1"
         },
+        "isort": {
+            "hashes": [
+                "sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
+                "sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
+            ],
+            "markers": "python_version >= '3.6' and python_version < '4.0'",
+            "version": "==5.8.0"
+        },
+        "lazy-object-proxy": {
+            "hashes": [
+                "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653",
+                "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61",
+                "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2",
+                "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837",
+                "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3",
+                "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43",
+                "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726",
+                "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3",
+                "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587",
+                "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8",
+                "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a",
+                "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd",
+                "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f",
+                "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad",
+                "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4",
+                "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b",
+                "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf",
+                "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981",
+                "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741",
+                "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e",
+                "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
+                "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
+            ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+            "version": "==1.6.0"
+        },
+        "mccabe": {
+            "hashes": [
+                "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+                "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+            ],
+            "version": "==0.6.1"
+        },
         "multidict": {
             "hashes": [
                 "sha256:018132dbd8688c7a69ad89c4a3f39ea2f9f33302ebe567a879da8f4ca73f0d0a",
@@ -232,6 +272,7 @@
                 "sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
                 "sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
             ],
+            "markers": "python_version >= '3.6'",
             "version": "==5.1.0"
         },
         "packaging": {
@@ -239,6 +280,7 @@
                 "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5",
                 "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==20.9"
         },
         "pluggy": {
@@ -246,6 +288,7 @@
                 "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
                 "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.13.1"
         },
         "py": {
@@ -253,22 +296,32 @@
                 "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
                 "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.10.0"
         },
+        "pylint": {
+            "hashes": [
+                "sha256:209d712ec870a0182df034ae19f347e725c1e615b2269519ab58a35b3fcbbe7a",
+                "sha256:bd38914c7731cdc518634a8d3c5585951302b6e2b6de60fbb3f7a0220e21eeee"
+            ],
+            "index": "pypi",
+            "version": "==2.7.4"
+        },
         "pyparsing": {
             "hashes": [
                 "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
                 "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
             ],
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==2.4.7"
         },
         "pytest": {
             "hashes": [
-                "sha256:9d1edf9e7d0b84d72ea3dbcdfd22b35fb543a5e8f2a60092dd578936bf63d7f9",
-                "sha256:b574b57423e818210672e07ca1fa90aaf194a4f63f3ab909a2c67ebb22913839"
+                "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634",
+                "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc"
             ],
             "index": "pypi",
-            "version": "==6.2.2"
+            "version": "==6.2.3"
         },
         "pytest-vcr": {
             "hashes": [
@@ -280,11 +333,11 @@
         },
         "python-dotenv": {
             "hashes": [
-                "sha256:31d752f5b748f4e292448c9a0cac6a08ed5e6f4cefab85044462dcad56905cec",
-                "sha256:9fa413c37d4652d3fa02fea0ff465c384f5db75eab259c4fc5d0c5b8bf20edd4"
+                "sha256:471b782da0af10da1a80341e8438fca5fadeba2881c54360d5fd8d03d03a4f4a",
+                "sha256:49782a97c9d641e8a09ae1d9af0856cc587c8d2474919342d5104d85be9890b2"
             ],
             "index": "pypi",
-            "version": "==0.16.0"
+            "version": "==0.17.0"
         },
         "pyyaml": {
             "hashes": [
@@ -318,6 +371,7 @@
                 "sha256:fdc842473cd33f45ff6bce46aea678a54e3d21f1b61a7750ce3c498eedfe25d6",
                 "sha256:fe69978f3f768926cfa37b867e3843918e012cf83f680806599ddce33c2c68b0"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
             "version": "==5.4.1"
         },
         "six": {
@@ -325,6 +379,7 @@
                 "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259",
                 "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==1.15.0"
         },
         "toml": {
@@ -332,6 +387,7 @@
                 "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
                 "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
             ],
+            "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==0.10.2"
         },
         "tox": {
@@ -355,6 +411,7 @@
                 "sha256:12c3fcdae7b88ecf11fc0d3e6d77586549d4575a2ceee18e82eee75c1f626162",
                 "sha256:57095bf22fc0a2d99ee9674cdafebed0f3ba763018582450706f7d3a74fff599"
             ],
+            "markers": "python_version >= '3.5'",
             "version": "==4.1.1"
         },
         "virtualenv": {
@@ -362,6 +419,7 @@
                 "sha256:49ec4eb4c224c6f7dd81bb6d0a28a09ecae5894f4e593c89b0db0885f565a107",
                 "sha256:83f95875d382c7abafe06bd2a4cdd1b363e1bb77e02f155ebe8ac082a916b37c"
             ],
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
             "version": "==20.4.3"
         },
         "wrapt": {

+ 2 - 5
README.md

@@ -60,7 +60,7 @@ Connect through a remote API using URL and authentication key.
 Create local authentication middleware and log in with environ variables
 ```python
 import os
-from rival_regions_wrapper import LocalAuthentication
+from rival_regions_wrapper.middleware import LocalAuthentication
 
 authentication = LocalAuthentication(
   os.environ["USERNAME"],
@@ -76,14 +76,11 @@ region = authentication.get('listed/upgrades/{}'.format(region_id))
 
 Example of API wrapper to get oil current available resources from a state
 ```python
-from rival_regions_wrapper import apiWrapper
 from rival_regions_wrapper.api_wrapper import ResourceState
 
-api_wrapper = ApiWrapper(authentication)
-
 state = 3382
 resource = 'oil'
-response = ResourceState(api_wrapper, state).info(resource)
+response = ResourceState(authentication, state).info(resource)
 ```
 
 For more examples look at the unit tests.

+ 4 - 3
setup.py

@@ -7,7 +7,7 @@ with open("README.md", "r") as fh:
 
 setuptools.setup(
     name="rival_regions_wrapper",
-    version="1.1.3",
+    version="1.2.6",
     author="Joost Sijm",
     author_email="joostsijm@gmail.com",
     description="Rival Regions API wrapper",
@@ -20,10 +20,11 @@ setuptools.setup(
         "appdirs",
         "beautifulsoup4",
         "cfscrape",
-        "requests",
-        "webbot",
         "python-dateutil",
         "pathlib2",
+        "requests",
+        "selenium-stealth",
+        "webbot",
     ],
     classifiers=[
         "Programming Language :: Python :: 3",

+ 34 - 3
src/rival_regions_wrapper/__init__.py

@@ -6,6 +6,37 @@ This unofficial API wrapper is an implementation
 of some Rival Regions functionalities.
 """
 
-from .authentication_handler import AuthenticationHandler
-from .middleware import LocalAuthentication, RemoteAuthentication
-from .api_wrapper import ApiWrapper
+import logging
+import pathlib2
+
+from appdirs import user_data_dir
+
+
+DATA_DIR = user_data_dir('rival_regions_wrapper', 'bergc')
+pathlib2.Path(DATA_DIR).mkdir(parents=True, exist_ok=True)
+
+# get logger
+LOGGER = logging.getLogger(__name__)
+LOGGER.setLevel(logging.DEBUG)
+
+# create file handler
+FILE_HANDLER = logging.FileHandler('{}/output.log'.format(DATA_DIR))
+FILE_HANDLER.setLevel(logging.DEBUG)
+
+# create console handler
+STREAM_HANDLER = logging.StreamHandler()
+STREAM_HANDLER.setLevel(logging.INFO)
+
+# create formatter and add it to the handlers
+STREAM_FORMATTER = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
+STREAM_HANDLER.setFormatter(STREAM_FORMATTER)
+FILE_FORMATTER = logging \
+        .Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
+FILE_HANDLER.setFormatter(FILE_FORMATTER)
+
+# add the handlers to logger
+LOGGER.addHandler(STREAM_HANDLER)
+LOGGER.addHandler(FILE_HANDLER)
+
+# from .authentication_handler import AuthenticationHandler
+# from .middleware import LocalAuthentication, RemoteAuthentication

+ 13 - 28
src/rival_regions_wrapper/api_wrapper/__init__.py

@@ -1,30 +1,15 @@
 """API wrapper for Rival Regions"""
 
-
-from .profile import Profile
-from .storage import Storage
-from .market import Market
-from .resource_state import ResourceState
-from .perks import Perks
-from .craft import Craft
-from .overview import Overview
-from .war import War
-from .work import Work
-from .article import Article
-
-
-class ApiWrapper:
-    """API wrapper"""
-    authentication = None
-
-    def __init__(self, authentication):
-        """Initialize API wrapper with authentication"""
-        self.authentication = authentication
-
-    def get(self, path):
-        """Send get requests"""
-        return self.authentication.get(path)
-
-    def post(self, path, data=None):
-        """Send post request"""
-        return self.authentication.post(path, data=data)
+from rival_regions_wrapper.middleware import LocalAuthentication
+
+from rival_regions_wrapper.api_wrapper.profile import Profile
+from rival_regions_wrapper.api_wrapper.storage import Storage
+from rival_regions_wrapper.api_wrapper.market import Market
+from rival_regions_wrapper.api_wrapper.resource_state import ResourceState
+from rival_regions_wrapper.api_wrapper.perks import Perks
+from rival_regions_wrapper.api_wrapper.craft import Craft
+from rival_regions_wrapper.api_wrapper.overview import Overview
+from rival_regions_wrapper.api_wrapper.war import War
+from rival_regions_wrapper.api_wrapper.work import Work
+from rival_regions_wrapper.api_wrapper.article import Article
+from rival_regions_wrapper.api_wrapper.conference import Conference

+ 11 - 0
src/rival_regions_wrapper/api_wrapper/abstract_wrapper.py

@@ -0,0 +1,11 @@
+"""
+Abstract wrapper module
+"""
+
+from abc import ABC
+
+
+class AbstractWrapper(ABC):
+    """abstract base class for wrappers"""
+    def __init__(self, api_wrapper):
+        self.api_wrapper = api_wrapper

+ 10 - 8
src/rival_regions_wrapper/api_wrapper/article.py

@@ -5,14 +5,12 @@ import re
 
 from bs4 import BeautifulSoup
 
-from rival_regions_wrapper import functions
+from rival_regions_wrapper import util
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
 
-class Article():
+class Article(AbstractWrapper):
     """Wrapper class for article"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
-
     def info(self, article_id):
         """Get artcile"""
         path = 'news/show/{}'.format(article_id)
@@ -33,7 +31,9 @@ class Article():
 
         article_info = {
             'article_id': article_id,
-            'article_title': unicodedata.normalize("NFKD", soup.select_one('.title_totr').text),
+            'article_title': unicodedata.normalize(
+                "NFKD", soup.select_one('.title_totr').text
+            ),
             'author_name': re.sub(r',\s\skarma.*$', '', author.text),
             'author_id': int(author['action'].replace('slide/profile/', '')),
             'region_name': region.text,
@@ -45,7 +45,9 @@ class Article():
         }
 
         if newspaper:
-            article_info['newspaper_id'] = int(newspaper['action'].replace('newspaper/show/', ''))
+            article_info['newspaper_id'] = int(
+                    newspaper['action'].replace('newspaper/show/', '')
+                )
             article_info['newspaper_name'] = newspaper.text
         else:
             article_info['newspaper_id'] = None
@@ -59,5 +61,5 @@ class Article():
 
         date_element = soup.select_one('.news_conent_title')
         date_string = date_element.text.replace('✘', '').strip()
-        article_info['post_date'] = functions.parse_date(date_string)
+        article_info['post_date'] = util.parse_date(date_string)
         return article_info

+ 143 - 0
src/rival_regions_wrapper/api_wrapper/conference.py

@@ -0,0 +1,143 @@
+"""Conference class"""
+
+import time
+
+from rival_regions_wrapper import authentication_handler, LOGGER
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
+
+
+class Conference(AbstractWrapper):
+    """Wrapper class for confernce"""
+    def __init__(self, api_wrapper, conference_id):
+        AbstractWrapper.__init__(self, api_wrapper)
+        self.conference_id = conference_id
+
+    @authentication_handler.session_handler
+    def message(self, message):
+        """Send message to conference"""
+        LOGGER.info(
+                '"%s": CONF "%s": start send message',
+                self.api_wrapper.client.username, self.conference_id
+            )
+        browser = self.api_wrapper.client.get_browser()
+        try:
+            browser.go_to(
+                    'https://rivalregions.com/#slide/conference/{}'
+                    .format(self.conference_id)
+                )
+            browser.refresh()
+            time.sleep(2)
+
+            character_count = 0
+            tmp_messages = []
+            for sentence in message.split('\n'):
+                sentence_character_count = 0
+                tmp_sentence = []
+                for word in sentence.split(' '):
+                    sentence_character_count += len(word) + 1
+                    if sentence_character_count >= 899:
+                        message = '{}\n{}'.format('\n'.join(
+                                tmp_messages),
+                                ' '.join(tmp_sentence)
+                            )
+                        LOGGER.info(
+                                '"%s": CONF "%s": next message length: %s',
+                                self.api_wrapper.client.username,
+                                self.conference_id, len(message)
+                            )
+                        browser.type(message, id='message')
+                        browser.click(id='chat_send')
+                        sentence_character_count = 0
+                        tmp_sentence = []
+                        character_count = 0
+                        tmp_messages = []
+                    tmp_sentence.append(word)
+
+                sentence = ' '.join(tmp_sentence)
+                character_count += len(sentence) + 1
+                if character_count >= 900:
+                    message = '\n'.join(tmp_messages)
+                    LOGGER.info(
+                            '"%s": CONF "%s": next message length: %s',
+                            self.api_wrapper.client.username,
+                            self.conference_id, len(message)
+                        )
+                    browser.type(message, id='message')
+                    browser.click(id='chat_send')
+                    character_count = 0
+                    tmp_messages = []
+                tmp_messages.append(sentence)
+
+            if tmp_messages:
+                message = '\n'.join(tmp_messages)
+                LOGGER.info(
+                        '"%s": CONF "%s": next message length: %s',
+                        self.api_wrapper.client.username,
+                        self.conference_id, len(message)
+                    )
+                browser.type(message, id='message')
+                browser.click(id='chat_send')
+
+            LOGGER.info(
+                    '"%s": CONF "%s": finished sending message',
+                    self.api_wrapper.client.username, self.conference_id
+                )
+        finally:
+            browser.close_current_tab()
+
+    @authentication_handler.session_handler
+    def notification(self, message, sound):
+        """Send notification to conference"""
+        LOGGER.info(
+                '"%s": CONF: %s notification',
+                self.api_wrapper.client.username, self.conference_id
+            )
+        data = {
+            'sound': 1 if sound else 0,
+            'text': message,
+            'c': self.api_wrapper.client.var_c,
+        }
+
+        if self.api_wrapper.client.session:
+            response = self.api_wrapper.client.session.post(
+                "https://rivalregions.com/rival/konffcm/{}/".format(
+                    self.conference_id
+                ),
+                data=data
+            )
+            self.api_wrapper.client.check_response(response)
+        else:
+            raise authentication_handler.NoLogginException()
+        LOGGER.info(
+                '"%s": CONF: id %s send notification ',
+                self.api_wrapper.client.username, self.conference_id
+            )
+        return response.text
+
+    @authentication_handler.session_handler
+    def change_title(self, title):
+        """Change title of conference"""
+        LOGGER.info(
+                '"%s": CONF: %s change title: %s',
+                self.api_wrapper.client.username, self.conference_id, title
+            )
+        data = {
+            't': title,
+            'c': self.api_wrapper.client.var_c,
+        }
+
+        if self.api_wrapper.client.session:
+            response = self.api_wrapper.client.session.post(
+                "https://rivalregions.com/rival/changename/{}/".format(
+                    self.conference_id
+                ),
+                data=data
+            )
+            self.api_wrapper.client.check_response(response)
+        else:
+            raise authentication_handler.NoLogginException()
+        LOGGER.info(
+                '"%s": CONF: id %s changed title',
+                self.api_wrapper.client.username, self.conference_id
+            )
+        return response.text

+ 15 - 53
src/rival_regions_wrapper/api_wrapper/craft.py

@@ -1,40 +1,19 @@
-"""CRAFT class"""
+"""Craft class"""
 
 import re
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper import util
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
-class Craft():
-    """Wrapper class for crafting"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
 
+class Craft(AbstractWrapper):
+    """Wrapper class for crafting"""
     def info(self, item):
         """Get craft"""
-        keys = {
-            'oil': 3,
-            'ore': 4,
-            'uranium': 11,
-            'diamonds': 15,
-            'liquid_oxygen': 21,
-            'helium-3': 24,
-            'rivalium': 26,
-            'antirad': 13,
-            'energy_drink': 17,
-            'spacerockets': 20,
-            'lss': 25,
-            'tanks': 2,
-            'aircrafts': 1,
-            'missiles': 14,
-            'bombers': 16,
-            'battleships': 18,
-            'laser_drones': 27,
-            'moon_tanks': 22,
-            'space_stations': 23
-        }
-        if isinstance(item, str) and item in keys:
-            item = keys[item]
+        if isinstance(item, str) and item in util.ITEM_KEYS:
+            item = util.ITEM_KEYS[item]
         path = 'storage/produce/{}'.format(item)
         response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
@@ -49,41 +28,24 @@ class Craft():
         }
         resource_cost = {}
         for name, selector in resource_dict.items():
-            element = resources.select_one('.{} .produce_discount'.format(selector))
+            element = resources.select_one(
+                    '.{} .produce_discount'.format(selector)
+                )
             if element:
                 resource_cost[name] = int(
                     re.sub(r'-|\.', '', element.text)
                 )
         craft = {
-            'market_price': int(re.sub(r'\.|\s\$', '', soup.select('.small .imp')[1].text)),
+            'market_price': int(
+                re.sub(r'\.|\s\$', '', soup.select('.small .imp')[1].text)
+            ),
             'resources': resource_cost
         }
         return craft
 
     def produce(self, item, amount):
         """Craft item"""
-        keys = {
-            'oil': 3,
-            'ore': 4,
-            'uranium': 11,
-            'diamonds': 15,
-            'liquid_oxygen': 21,
-            'helium-3': 24,
-            'rivalium': 26,
-            'antirad': 13,
-            'energy_drink': 17,
-            'spacerockets': 20,
-            'lss': 25,
-            'tanks': 2,
-            'aircrafts': 1,
-            'missiles': 14,
-            'bombers': 16,
-            'battleships': 18,
-            'laser_drones': 27,
-            'moon_tanks': 22,
-            'space_stations': 23
-        }
-        if isinstance(item, str) and item in keys:
-            item = keys[item]
+        if isinstance(item, str) and item in util.ITEM_KEYS:
+            item = util.ITEM_KEYS[item]
         self.api_wrapper.post('storage/newproduce/{}/{}'.format(item, amount))
         return True

+ 33 - 0
src/rival_regions_wrapper/api_wrapper/language_chat.py

@@ -0,0 +1,33 @@
+"""Language chat class"""
+
+from rival_regions_wrapper import authentication_handler, LOGGER
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
+
+
+class LanguageChat(AbstractWrapper):
+    """Wrapper class for language chat"""
+    def __init__(self, api_wrapper, language):
+        AbstractWrapper.__init__(self, api_wrapper)
+        self.language = language
+
+    @authentication_handler.session_handler
+    def message(self, message):
+        """send message to language chat"""
+        LOGGER.info(
+                '"%s": CHAT: language %s',
+                self.api_wrapper.client.username, self.language
+            )
+        browser = self.api_wrapper.client.get_browser()
+        try:
+            browser.go_to(
+                    'https://rivalregions.com/#slide/chat/lang_{}'.format(
+                            self.language
+                        )
+                )
+            self.api_wrapper.client.send_chat(browser, message)
+            LOGGER.info(
+                    '"%s": CHAT: language %s, finished sending message',
+                    self.api_wrapper.client.username, self.language
+                )
+        finally:
+            browser.close_current_tab()

+ 16 - 31
src/rival_regions_wrapper/api_wrapper/market.py

@@ -4,37 +4,16 @@ import re
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper import util
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
-class Market():
-    """Wrapper class for profile"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
 
+class Market(AbstractWrapper):
+    """Wrapper class for profile"""
     def info(self, resource):
         """Get profile"""
-        keys = {
-            'oil': 3,
-            'ore': 4,
-            'uranium': 11,
-            'diamonds': 15,
-            'liquid_oxygen': 21,
-            'helium-3': 24,
-            'rivalium': 26,
-            'antirad': 13,
-            'energy_drink': 17,
-            'spacerockets': 20,
-            'lss': 25,
-            'tanks': 2,
-            'aircrafts': 1,
-            'missiles': 14,
-            'bombers': 16,
-            'battleships': 18,
-            'laser_drones': 27,
-            'moon_tanks': 22,
-            'space_stations': 23
-        }
-        if isinstance(resource, str) and resource in keys:
-            resource = keys[resource]
+        if isinstance(resource, str) and resource in util.ITEM_KEYS:
+            resource = util.ITEM_KEYS[resource]
         path = 'storage/listed/{}'.format(resource)
         response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
@@ -43,11 +22,17 @@ class Market():
         offers = []
         for offer_tree in offers_tree:
             offers.append({
-                'player_id': int(
-                    re.sub(r'^.*\/', '', offer_tree.select_one('.results_date')['action'])
+                'player_id': int(re.sub(
+                        r'^.*\/', '',
+                        offer_tree.select_one('.results_date')['action']
+                    )
                 ),
                 'player_name': offer_tree.select_one('.results_date').string,
-                'price': int(float(offer_tree.select('.list_level')[1]['rat'])*100),
-                'amount': int(offer_tree.select_one('.list_level.imp.small')['rat']),
+                'price': int(
+                    float(offer_tree.select('.list_level')[1]['rat'])*100
+                ),
+                'amount': int(
+                    offer_tree.select_one('.list_level.imp.small')['rat']
+                ),
             })
         return offers

+ 6 - 25
src/rival_regions_wrapper/api_wrapper/overview.py

@@ -1,45 +1,26 @@
 """Profile class"""
 
-import re
-
 from bs4 import BeautifulSoup
 
-from rival_regions_wrapper import functions
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
+from rival_regions_wrapper.api_wrapper.perks import Perks
 
 
-class Overview():
+class Overview(AbstractWrapper):
     """Wrapper class for perks"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
-
     def info(self):
-        """Get perks"""
+        """Get overview """
         path = 'main/content'
         response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
-        perks = soup.select('.perk_source_4')
-        upgrade_perk = None
-        upgrade_date = None
-        for perk in perks:
-            date_string = perk.select_one('.small')
-            if date_string:
-                upgrade_perk = int(perk['perk'])
-                date_string = re.sub(r'^.*:\s', '', soup.select_one('.perk_source_4 .small').text)
-                upgrade_date = functions.parse_date(date_string)
-                break
+        perks = Perks.info_parse(soup)
         auto_war = soup.select_one('.war_index_war span.pointer:nth-child(4)')
         if auto_war and auto_war.has_attr('action'):
             auto_war = auto_war['action'].replace('war/details/', '')
         else:
             auto_war = None
         overview = {
-            'perks': {
-                'strenght': int(soup.find('div', {'perk': 1, 'class': 'perk_source_2'}).text),
-                'education': int(soup.find('div', {'perk': 2, 'class': 'perk_source_2'}).text),
-                'endurance': int(soup.find('div', {'perk': 3, 'class': 'perk_source_2'}).text),
-                'upgrade_date': upgrade_date,
-                'upgrade_perk': upgrade_perk
-            },
+            'perks': perks,
             'war': {
                 'auto_war': auto_war,
             }

+ 22 - 10
src/rival_regions_wrapper/api_wrapper/perks.py

@@ -4,19 +4,22 @@ import re
 
 from bs4 import BeautifulSoup
 
-from rival_regions_wrapper import functions
+from rival_regions_wrapper import util
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
 
-class Perks():
+class Perks(AbstractWrapper):
     """Wrapper class for perks"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
-
     def info(self):
         """Get perks"""
         path = 'main/content'
         response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
+        return self.info_parse(soup)
+
+    @staticmethod
+    def info_parse(soup):
+        """Parse perk info"""
         perks = soup.select('.perk_source_4')
         upgrade_perk = None
         upgrade_date = None
@@ -24,13 +27,22 @@ class Perks():
             date_string = perk.select_one('.small')
             if date_string:
                 upgrade_perk = int(perk['perk'])
-                date_string = re.sub(r'^.*:\s', '', soup.select_one('.perk_source_4 .small').text)
-                upgrade_date = functions.parse_date(date_string)
+                date_string = re.sub(
+                        r'^.*:\s', '',
+                        soup.select_one('.perk_source_4 .small').text
+                    )
+                upgrade_date = util.parse_date(date_string)
                 break
         perks = {
-            'strenght': int(soup.find('div', {'perk': 1, 'class': 'perk_source_2'}).text),
-            'education': int(soup.find('div', {'perk': 2, 'class': 'perk_source_2'}).text),
-            'endurance': int(soup.find('div', {'perk': 3, 'class': 'perk_source_2'}).text),
+            'strenght': int(
+                soup.find('div', {'perk': 1, 'class': 'perk_source_2'}).text
+            ),
+            'education': int(
+                soup.find('div', {'perk': 2, 'class': 'perk_source_2'}).text
+            ),
+            'endurance': int(
+                soup.find('div', {'perk': 3, 'class': 'perk_source_2'}).text
+            ),
             'upgrade_date': upgrade_date,
             'upgrade_perk': upgrade_perk
         }

+ 31 - 4
src/rival_regions_wrapper/api_wrapper/profile.py

@@ -4,11 +4,14 @@ import re
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper import authentication_handler, LOGGER
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
-class Profile():
+
+class Profile(AbstractWrapper):
     """Wrapper class for profile"""
     def __init__(self, api_wrapper, profile_id):
-        self.api_wrapper = api_wrapper
+        AbstractWrapper.__init__(self, api_wrapper)
         self.profile_id = profile_id
 
     def info(self):
@@ -22,9 +25,33 @@ class Profile():
             'profile_id': self.profile_id,
             'name': re.sub(r'.*:\s', '', soup.find('h1').text),
             'level': int(re.sub(r'^Level\:\s|\s\(.*\)$', '', level)),
-            'level_percentage': int(re.sub(r'^Level\:\s(\d+)\s\(|\s\%\)$', '', level)),
+            'level_percentage': int(
+                re.sub(r'^Level\:\s(\d+)\s\(|\s\%\)$', '', level)
+            ),
             'strenght': int(perks[0].text),
             'education': int(perks[1].text),
-            'endurance': int(perks[2].text)
+            'endurance': int(perks[2].text),
         }
         return profile
+
+    @authentication_handler.session_handler
+    def message(self, message):
+        """send personal message"""
+        LOGGER.info(
+                '"%s": PM: user id %s',
+                self.api_wrapper.client.username, self.profile_id
+            )
+        browser = self.api_wrapper.client.get_browser()
+        try:
+            browser.go_to(
+                    'https://rivalregions.com/#messages/{}'.format(
+                            self.profile_id
+                        )
+                )
+            self.api_wrapper.client.send_chat(browser, message)
+            LOGGER.info(
+                    '"%s:" PM: user id %s, finished sending message',
+                    self.api_wrapper.client.username, self.profile_id
+                )
+        finally:
+            browser.close_current_tab()

+ 4 - 2
src/rival_regions_wrapper/api_wrapper/resource_state.py

@@ -4,11 +4,13 @@ import re
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
-class ResourceState():
+
+class ResourceState(AbstractWrapper):
     """Wrapper class for resource state"""
     def __init__(self, api_wrapper, state_id):
-        self.api_wrapper = api_wrapper
+        AbstractWrapper.__init__(self, api_wrapper)
         self.state_id = state_id
 
     def info(self, resource):

+ 8 - 28
src/rival_regions_wrapper/api_wrapper/storage.py

@@ -2,45 +2,25 @@
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper import util
+
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
-class Storage():
-    """Wrapper class for storage"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
 
+class Storage(AbstractWrapper):
+    """Wrapper class for storage"""
     def info(self):
         """storage info"""
         path = 'storage'
         response = self.api_wrapper.get(path)
         soup = BeautifulSoup(response, 'html.parser')
-        keys = {
-            'oil': 3,
-            'ore': 4,
-            'uranium': 11,
-            'diamonds': 15,
-            'liquid_oxygen': 21,
-            'helium-3': 24,
-            'rivalium': 26,
-            'antirad': 13,
-            'energy_drink': 17,
-            'spacerockets': 20,
-            'lss': 25,
-            'tanks': 2,
-            'aircrafts': 1,
-            'missiles': 14,
-            'bombers': 16,
-            'battleships': 18,
-            'laser_drones': 27,
-            'moon_tanks': 22,
-            'space_stations': 23
-        }
         storage = {}
-        for key, item_id in keys.items():
+        for key, item_id in util.ITEM_KEYS.items():
             storage[key] = int(
-                soup.find('span', {'urlbar' : item_id}).text.replace('.', '')
+                soup.find('span', {'urlbar': item_id}).text.replace('.', '')
             )
             storage['{}_max'.format(key)] = int(
-                soup.find('span', {'urlbar' : item_id})['maxstore']
+                soup.find('span', {'urlbar': item_id})['maxstore']
             )
 
         return storage

+ 4 - 6
src/rival_regions_wrapper/api_wrapper/war.py

@@ -6,14 +6,12 @@ import unicodedata
 
 from bs4 import BeautifulSoup
 
-from rival_regions_wrapper import functions
+from rival_regions_wrapper import util
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
 
 
-class War():
+class War(AbstractWrapper):
     """Wrapper class for war"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
-
     def page(self):
         """Get training war"""
         path = 'war'
@@ -81,7 +79,7 @@ class War():
                 )
             if results:
                 war_info['finish_date'] = \
-                    functions.parse_date(results.group(0))
+                    util.parse_date(results.group(0))
 
         war_info['war_units'] = {}
         for war_unit in soup.select('.war_w_unit_div'):

+ 3 - 4
src/rival_regions_wrapper/api_wrapper/work.py

@@ -4,6 +4,8 @@ import re
 
 from bs4 import BeautifulSoup
 
+from rival_regions_wrapper.api_wrapper.abstract_wrapper import AbstractWrapper
+
 
 RESOURCE_DICT = {
     'oil': 'oil',
@@ -14,11 +16,8 @@ RESOURCE_DICT = {
 }
 
 
-class Work():
+class Work(AbstractWrapper):
     """Wrapper class for work"""
-    def __init__(self, api_wrapper):
-        self.api_wrapper = api_wrapper
-
     def page(self):
         """Get work page"""
         path = 'work'

+ 76 - 381
src/rival_regions_wrapper/authentication_handler.py

@@ -1,47 +1,18 @@
-
 """
-Client module
+Authentication handeler module
 """
 
+import time
+
 import sys
-import logging
 import re
-import time
-from datetime import datetime
-import json
-import pathlib2
 
 import requests
 import cfscrape
-from .browser import StealthBrowser as Browser
-from appdirs import user_data_dir
-
-
-DATA_DIR = user_data_dir('rival_regions_wrapper', 'bergc')
-pathlib2.Path(DATA_DIR).mkdir(parents=True, exist_ok=True)
 
-# get logger
-LOGGER = logging.getLogger(__name__)
-LOGGER.setLevel(logging.DEBUG)
-
-# create file handler
-FILE_HANDLER = logging.FileHandler('{}/output.log'.format(DATA_DIR))
-FILE_HANDLER.setLevel(logging.DEBUG)
-
-# create console handler
-STREAM_HANDLER = logging.StreamHandler()
-STREAM_HANDLER.setLevel(logging.INFO)
-
-# create formatter and add it to the handlers
-STREAM_FORMATTER = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
-STREAM_HANDLER.setFormatter(STREAM_FORMATTER)
-FILE_FORMATTER = logging \
-        .Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
-FILE_HANDLER.setFormatter(FILE_FORMATTER)
-
-# add the handlers to logger
-LOGGER.addHandler(STREAM_HANDLER)
-LOGGER.addHandler(FILE_HANDLER)
+from rival_regions_wrapper import LOGGER, login_methods
+from rival_regions_wrapper.cookie_handler import CookieHandler
+from rival_regions_wrapper.browser import Browser
 
 
 class RRClientException(Exception):
@@ -82,7 +53,7 @@ def session_handler(func):
         try:
             return func(*args, **kwargs)
         except (SessionExpireException, ConnectionError, ConnectionResetError):
-            instance.remove_cookie(instance.username)
+            CookieHandler.remove_cookie(instance.username)
             instance.login()
             return try_run(instance, func, *args, **kwargs)
         except NoLogginException:
@@ -115,33 +86,35 @@ class AuthenticationHandler:
         self.login()
 
     def login(self):
-        self.remove_cookie(self.username)
         """Login user if needed"""
-        LOGGER.info('"%s": start login, method: "%s"',
-                    self.username, self.login_method)
-        cookies = self.get_cookies(self.username)
+        LOGGER.info(
+                '"%s": start login, method: "%s"',
+                self.username, self.login_method
+            )
+        cookies = CookieHandler.get_cookies(self.username)
         if not cookies:
-            LOGGER.info('"%s": no cookie, new login, method "%s"',
-                        self.username, self.login_method)
-            if self.login_method not in [
-                        "g", "google", "v", "vk", "f", "facebook"
-                    ]:
-                raise RRClientException("Not a valid login method.")
+            cookies = []
+            LOGGER.info(
+                    '"%s": no cookie, new login, method "%s"',
+                    self.username, self.login_method
+                )
+
+            login_method_dict = {
+                'g': login_methods.login_google,
+                'google': login_methods.login_google,
+                'v': login_methods.login_vk,
+                'vk': login_methods.login_vk,
+                'f': login_methods.login_facebook,
+                'facebook': login_methods.login_facebook,
+            }
 
             auth_text = requests.get("https://rivalregions.com").text
             browser = Browser(showWindow=self.show_window)
 
-            method_dict = {
-                'g': self.login_google,
-                'google': self.login_google,
-                'v': self.login_vk,
-                'vk': self.login_vk,
-                'f': self.login_facebook,
-                'facebook': self.login_facebook,
-            }
-
-            if self.login_method in method_dict:
-                browser = method_dict[self.login_method](browser, auth_text)
+            if self.login_method in login_method_dict:
+                browser = login_method_dict[self.login_method](
+                        browser, auth_text, self.username, self.password
+                    )
             else:
                 LOGGER.info(
                         '"%s": Invalid login method "%s"',
@@ -154,8 +127,11 @@ class AuthenticationHandler:
             if browser_cookie:
                 expiry = browser_cookie.get('expiry', None)
                 value = browser_cookie.get('value', None)
-                LOGGER.info(f'"value": {value}, "expiry": {expiry}')
-                cookie = self.create_cookie(
+                LOGGER.info(
+                        '"%s": "value": %s, "expiry": %s',
+                        self.username, value, expiry
+                    )
+                cookie = CookieHandler.create_cookie(
                         'PHPSESSID',
                         expiry,
                         value
@@ -164,31 +140,35 @@ class AuthenticationHandler:
             else:
                 raise NoCookieException()
 
-            # TODO: what's up with 'rival/googles'
-
             cookie_names = ['rr_f']
             for cookie_name in cookie_names:
                 browser_cookie = browser.get_cookie(cookie_name)
                 if browser_cookie:
-                    LOGGER.info(f'"{self.username}": Get {cookie_name}')
+                    LOGGER.info(
+                        '"%s": Get %s',
+                        self.username, cookie_name
+                    )
                     expiry = browser_cookie.get('expiry', None)
                     value = browser_cookie.get('value', None)
                     cookies.append(
-                        self.create_cookie(
+                        CookieHandler.create_cookie(
                             cookie_name,
                             expiry,
                             value
                         )
                     )
-                    LOGGER.info(f'"value": {value}, "expiry": {expiry}')
+                    LOGGER.info(
+                            '"%s": "value": %s, "expiry": %s',
+                            self.username, value, expiry
+                        )
                 else:
                     raise NoCookieException()
 
-            self.write_cookies(self.username, cookies)
+            CookieHandler.write_cookies(self.username, cookies)
             LOGGER.debug('"%s": closing login tab', self.username)
             browser.close_current_tab()
         else:
-            LOGGER.info('Cookies found')
+            LOGGER.info('"%s": Cookies found', self.username)
 
         self.session = cfscrape.CloudflareScraper()
         for cookie in cookies:
@@ -203,152 +183,6 @@ class AuthenticationHandler:
                 LOGGER.debug('"%s": got var_c: %s', self.username, var_c)
                 self.var_c = line.split("'")[-2]
 
-    # This is working
-    def login_google(self, browser, auth_text):
-        """login using Google"""
-        LOGGER.info('"%s": Login method Google', self.username)
-        auth_text1 = auth_text.split('\t<a href="')
-        auth_text2 = auth_text1[1].split('" class="sa')
-        time.sleep(1)
-        browser.go_to(auth_text2[0])
-
-        LOGGER.info('"%s": Typing in username', self.username)
-        browser.type(self.username, into='Email')
-
-        LOGGER.info('"%s": pressing next button', self.username)
-        browser.click(css_selector="#next")
-        time.sleep(2)
-
-        LOGGER.info('"%s": Typing in password', self.username)
-        browser.type(self.password, css_selector="input")
-
-        LOGGER.info('"%s": pressing sign in button', self.username)
-        browser.click(css_selector="#submit")
-        time.sleep(3)
-
-        # Some why it wont click and login immediately. This seems to work
-        time.sleep(1)
-        browser.go_to(auth_text2[0])
-        time.sleep(1)
-        browser.go_to(auth_text2[0])
-        time.sleep(1)
-        browser.click(css_selector="#sa_add2 > div:nth-child(4) > a.sa_link.gogo > div")
-        time.sleep(3)
-        return browser
-
-    # IDK if this is working
-    def login_vk(self, browser, auth_text):
-        """login using VK"""
-        LOGGER.info('Login method VK')
-        auth_text1 = auth_text.split("(\'.vkvk\').attr(\'url\', \'")
-        auth_text2 = auth_text1[1].split('&response')
-
-        browser.go_to(auth_text2[0])
-        browser.type(self.username, into='email')
-        browser.type(
-                self.password,
-                xpath="/html/body/div/div/div/div[2]/form/div/div/input[7]"
-        )
-        browser.click('Log in')
-        return browser
-
-    # IDK if this is working
-    def login_facebook(self, browser, auth_text):
-        """login using Facebook"""
-        LOGGER.info('Login method Facebook')
-        auth_text1 = \
-            auth_text.split('">\r\n\t\t\t\t<div class="sa_sn imp float_left" ')
-        auth_text2 = auth_text1[0].split('200px;"><a class="sa_link" href="')
-        url = auth_text2[1]
-
-        browser.go_to(url)
-        browser.type(self.username, into='Email')
-        browser.type(self.password, into='Password')
-        browser.click('Log In')
-        time.sleep(5)
-        browser.click(css_selector='.sa_sn.imp.float_left')
-        return browser
-
-    @classmethod
-    def write_cookies(cls, username, passed_cookies):
-        """Write cookie to file"""
-        LOGGER.info('Saving cookie for "%s"', username)
-        cookies = None
-        try:
-            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
-                cookies = json.load(cookies_file)
-            if not cookies:
-                raise FileNotFoundError # raise error as if file hadn't been found
-        except FileNotFoundError:
-            cookies = {username : {}}
-        if username not in cookies:
-            cookies[username] = {}
-        LOGGER.info(cookies)
-        for cookie in passed_cookies:
-            cookies[username][cookie['name']] = {
-                'expiry': cookie['expires'],
-                'value': cookie['value'],
-            }
-
-        with open('{}/cookies.json'.format(DATA_DIR), 'w+') as cookies_file:
-            json.dump(cookies, cookies_file)
-        LOGGER.info('Saved cookie for "%s"', username)
-
-    @classmethod
-    def get_cookies(cls, username):
-        """Read cookies for username"""
-        LOGGER.info('"%s": Reading cookie', username)
-        cookies = []
-        try:
-            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
-                cookies_data = json.load(cookies_file)
-                for cookie_username, user_cookies in cookies_data.items():
-                    if cookie_username == username:
-                        LOGGER.info('"%s": Found cookies', username)
-                        for cookie_name, cookie in user_cookies.items():
-                            expires = datetime.fromtimestamp(
-                                    int(cookie['expiry'])
-                                )
-                            if datetime.now() >= expires:
-                                LOGGER.info('"%s": Cookie is expired', username)
-                                return None
-                            cookies.append(cls.create_cookie(
-                                cookie_name,
-                                cookie['expiry'],
-                                cookie['value'],
-                            ))
-                        return cookies
-        except FileNotFoundError:
-            pass
-        return cookies
-
-    @classmethod
-    def remove_cookie(cls, username):
-        """Remove cookie from storage"""
-        LOGGER.info('Removing cookie for "%s"', username)
-        cookies = None
-        try:
-            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
-                cookies = json.load(cookies_file)
-        except FileNotFoundError:
-            cookies = {}
-        cookies.pop(username, None)
-        with open('{}/cookies.json'.format(DATA_DIR), 'w+') as cookies_file:
-            json.dump(cookies, cookies_file)
-        LOGGER.info('Removed cookie for "%s"', username)
-
-    @staticmethod
-    def create_cookie(name, expiry, value):
-        """Create cookie"""
-        return {
-            'domain': 'rivalregions.com',
-            'name': name,
-            'path': '/',
-            'secure': False,
-            'expires': expiry,
-            'value': value,
-        }
-
     @session_handler
     def get(self, path, add_var_c=False):
         """Send get request to Rival Regions"""
@@ -360,18 +194,14 @@ class AuthenticationHandler:
             params['c'] = self.var_c
 
         LOGGER.info(
-                '"%s" GET: "%s" var_c: %s', self.username, path, add_var_c
+                '"%s": GET: "%s" var_c: %s', self.username, path, add_var_c
             )
         if self.session:
             response = self.session.get(
                 url='https://rivalregions.com/{}'.format(path),
                 params=params
             )
-            if "Session expired, please, reload the page" \
-                    in response.text or \
-                    'window.location="https://rivalregions.com";' \
-                    in response.text:
-                raise SessionExpireException()
+            self.check_response(response)
         else:
             raise NoLogginException()
         return response.text
@@ -385,181 +215,46 @@ class AuthenticationHandler:
             data = {}
         data['c'] = self.var_c
 
-        LOGGER.info('"%s" POST: "%s"', self.username, path)
+        LOGGER.info('"%s": POST: "%s"', self.username, path)
         if self.session:
             response = self.session.post(
                 "https://rivalregions.com/{}".format(path),
                 data=data
             )
-            if "Session expired, please, reload the page" \
-                    in response.text or \
-                    'window.location="https://rivalregions.com";' \
-                    in response.text:
-                raise SessionExpireException()
+            self.check_response(response)
         else:
             raise NoLogginException()
         return response.text
 
-    @session_handler
-    def send_chat(self, language, message):
-        """send chat message"""
-        LOGGER.info('"%s" CHAT: language %s', self.username, language)
-        if self.session:
-            response = self.session.get("https://rivalregions.com/#overview")
-            if "Session expired, please, reload the page" in response.text:
-                raise SessionExpireException()
-            browser = Browser(showWindow=self.show_window)
-            browser.go_to('https://rivalregions.com/')
-            for cookie in self.get_cookies(self.username):
-                browser.add_cookie(cookie)
-            browser.go_to(
-                    'https://rivalregions.com/#slide/chat/lang_{}'
-                    .format(language)
-                )
-            browser.refresh()
-            time.sleep(2)
-            browser.type(message, id='message')
-            browser.click(id='chat_send')
-            LOGGER.info(
-                '"%s" CHAT: language %s, finished sending message',
-                self.username, language
-            )
-            browser.close_current_tab()
-        else:
-            raise NoLogginException()
-
-    @session_handler
-    def send_personal_message(self, user_id, message):
-        """send personal message"""
-        LOGGER.info('"%s" PM: user id %s', self.username, user_id)
-        if self.session:
-            response = self.session.get("https://rivalregions.com/#overview")
-            if "Session expired, please, reload the page" in response.text:
-                raise SessionExpireException()
-            browser = Browser(showWindow=self.show_window)
-            browser.go_to('https://rivalregions.com/')
-            for cookie in self.get_cookies(self.username):
-                browser.add_cookie(cookie)
-            browser.go_to(
-                    'https://rivalregions.com/#messages/{}'.format(user_id)
-                )
-            browser.refresh()
-            time.sleep(2)
-            browser.type(message, id='message')
-            browser.click(id='chat_send')
-            LOGGER.info(
-                    '"%s" PM: user id %s, finished sending message',
-                    self.username, user_id)
-            browser.close_current_tab()
-        else:
+    def get_browser(self):
+        """Get browser"""
+        if not self.session:
             raise NoLogginException()
 
-    @session_handler
-    def send_conference_message(self, conference_id, message):
-        """send conference message"""
-        LOGGER.info(
-                '"%s" CONF: id %s',
-                self.username, conference_id
+        response = self.session.get(
+                "https://rivalregions.com/#overview"
             )
-        if self.session:
-            response = self.session.get("https://rivalregions.com/#overview")
-            if "Session expired, please, reload the page" in response.text:
-                raise SessionExpireException()
-            browser = Browser(showWindow=self.show_window)
-            browser.go_to('https://rivalregions.com/')
-            for cookie in self.get_cookies(self.username):
-                browser.add_cookie(cookie)
-            browser.go_to(
-                    'https://rivalregions.com/#slide/conference/{}'
-                    .format(conference_id)
+        self.check_response(response)
+        browser = Browser(showWindow=self.show_window)
+        browser.go_to('https://rivalregions.com/')
+        for cookie_name, value in \
+                self.session.cookies.get_dict().items():
+            browser.add_cookie(
+                    CookieHandler.create_cookie(cookie_name, None, value)
                 )
-            browser.refresh()
-            time.sleep(2)
-
-            character_count = 0
-            tmp_messages = []
-            for sentence in message.split('\n'):
-                sentence_character_count = 0
-                tmp_sentence = []
-                for word in sentence.split(' '):
-                    sentence_character_count += len(word) + 1
-                    if sentence_character_count >= 899:
-                        message = '{}\n{}'.format('\n'.join(
-                                tmp_messages),
-                                ' '.join(tmp_sentence)
-                            )
-                        LOGGER.info(
-                                '"%s" CONF: id %s, next message length: %s',
-                                self.username, conference_id, len(message)
-                            )
-                        browser.type(message, id='message')
-                        browser.click(id='chat_send')
-                        sentence_character_count = 0
-                        tmp_sentence = []
-                        character_count = 0
-                        tmp_messages = []
-                    tmp_sentence.append(word)
-
-                sentence = ' '.join(tmp_sentence)
-                character_count += len(sentence) + 1
-                if character_count >= 900:
-                    message = '\n'.join(tmp_messages)
-                    LOGGER.info(
-                        'conference %s: next message length: %s',
-                        conference_id, len(message)
-                    )
-                    browser.type(message, id='message')
-                    browser.click(id='chat_send')
-                    character_count = 0
-                    tmp_messages = []
-                tmp_messages.append(sentence)
-
-            if tmp_messages:
-                message = '\n'.join(tmp_messages)
-                LOGGER.info(
-                        'conference %s: next message length: %s',
-                        conference_id, len(message)
-                    )
-                browser.type(message, id='message')
-                browser.click(id='chat_send')
-
-            LOGGER.info(
-                    'conference %s: finished sending message',
-                    conference_id
-                )
-            browser.close_current_tab()
-        else:
-            raise NoLogginException()
+        return browser
 
-    @session_handler
-    def send_conference_notification(self, conference_id, message, sound):
-        """send conference notification"""
-        LOGGER.info(
-                '"%s" CONF: id %s notification ',
-                self.username, conference_id
-            )
-        data = {
-            'sound': 1 if sound else 0,
-            'text': message,
-            'c': self.var_c
-        }
+    @classmethod
+    def send_chat(cls, browser, message):
+        """Send message"""
+        browser.refresh()
+        time.sleep(2)
+        browser.type(message, id='message')
+        browser.click(id='chat_send')
 
-        if self.session:
-            response = self.session.post(
-                "https://rivalregions.com/rival/konffcm/{}/".format(
-                    conference_id
-                ),
-                data=data
-            )
-            if "Session expired, please, reload the page" \
-                    in response.text or \
-                    'window.location="https://rivalregions.com";' \
-                    in response.text:
-                raise SessionExpireException()
-        else:
-            raise NoLogginException()
-        LOGGER.info(
-                '"%s" CONF: id %s send notification ',
-                self.username, conference_id
-            )
-        return response.text
+    @classmethod
+    def check_response(cls, response):
+        """Check resonse for authentication"""
+        if "Session expired, please, reload the page" in response.text or \
+                'window.location="https://rivalregions.com";' in response.text:
+            raise SessionExpireException()

+ 58 - 32
src/rival_regions_wrapper/browser.py

@@ -1,21 +1,29 @@
+"""
+Browser module
+"""
+
 import os
 import errno
+
 from selenium_stealth import stealth
 from selenium import webdriver
 from selenium.webdriver.common.keys import Keys
-from webbot import Browser
+import webbot
 
-class StealthBrowser(Browser):
-    """
 
+class Browser(webbot.Browser):
+    """
     **Constructor**
 
 
     :__init__(showWindow = True , proxy = None):
-        The constructor takes showWindow flag as argument which Defaults to False. If it is set to true , all browser happen without showing up any GUI window .
+        The constructor takes showWindow flag as argument which Defaults
+                to False. If it is set to true , all browser happen without
+                showing up any GUI window .
 
         :Args:
-            - showWindow : If true , will run a headless browser without showing GUI window.
+            - showWindow : If true , will run a headless browser without
+                    showing GUI window.
             - proxy : Url of any optional proxy server.
 
 
@@ -23,26 +31,33 @@ class StealthBrowser(Browser):
     Object attributes:  Key , errors
 
     :Key:
-        - It contains the constants for all the special keys in the keyboard which can be used in the *press* method
+        - It contains the constants for all the special keys in the keyboard
+                which can be used in the *press* method
     errors:
-        - List containing all the errors which might have occurred during performing an action like click ,type etc.
+        - List containing all the errors which might have occurred during
+                performing an action like click ,type etc.
     """
-
-    def __init__(self, showWindow=True, proxy=None , downloadPath:str=None):
+    def __init__(self, showWindow=True, proxy=None, downloadPath=None):
         options = webdriver.ChromeOptions()
         options.add_argument("--disable-dev-shm-usage")
         options.add_argument("--no-sandbox")
         options.add_argument('--disable-web-security')
         options.add_argument('--allow-running-insecure-content')
         options.add_argument("user-agent=DN")
-        options.add_experimental_option("excludeSwitches", ["enable-automation"])
+        options.add_experimental_option(
+                "excludeSwitches", ["enable-automation"]
+            )
         options.add_experimental_option('useAutomationExtension', False)
-        if downloadPath is not None and isinstance(downloadPath,str):
-            absolutePath = os.path.abspath(downloadPath)
-            if(not os.path.isdir(absolutePath)):
-                raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), absolutePath)
-
-            options.add_experimental_option('prefs', {'download.default_directory' : absolutePath})
+        if downloadPath is not None and isinstance(downloadPath, str):
+            absolute_path = os.path.abspath(downloadPath)
+            if not os.path.isdir(absolute_path):
+                raise FileNotFoundError(
+                        errno.ENOENT, os.strerror(errno.ENOENT), absolute_path
+                    )
+
+            options.add_experimental_option(
+                    'prefs', {'download.default_directory': absolute_path}
+                )
 
         if proxy is not None and isinstance(proxy, str):
             options.add_argument("--proxy-server={}".format(proxy))
@@ -53,25 +68,36 @@ class StealthBrowser(Browser):
         self.driver = webdriver.Chrome(options=options)
         self.Key = Keys
         self.errors = []
-        stealth(self.driver,
+        stealth(
+                self.driver,
                 languages=["en-US", "en"],
                 vendor="Google Inc.",
                 platform="Win32",
                 webgl_vendor="Intel Inc.",
                 renderer="Intel Iris OpenGL Engine",
                 fix_hairline=True,
-                )
-
-        [setattr(self, function, getattr(self.driver, function)) for function in
-         ['add_cookie', 'delete_all_cookies', 'delete_cookie', 'execute_script', 'execute_async_script',
-          'fullscreen_window', 'get_cookie', 'get_cookies', 'get_log', 'get_network_conditions',
-          'get_screenshot_as_base64', 'get_screenshot_as_file', 'get_screenshot_as_png', 'get_window_position',
-          'get_window_rect', 'get_window_size', 'maximize_window', 'minimize_window', 'implicitly_wait', 'quit',
-          'refresh', 'save_screenshot', 'set_network_conditions', 'set_page_load_timeout', 'set_script_timeout',
-          'set_window_position', 'set_window_rect', 'start_client', 'start_session', 'stop_client', 'switch_to_alert']]
-
-
-
-
-
-
+            )
+
+        for function in [
+                    'add_cookie', 'delete_all_cookies', 'delete_cookie',
+                    'execute_script', 'execute_async_script',
+                    'fullscreen_window', 'get_cookie', 'get_cookies',
+                    'get_log', 'get_network_conditions',
+                    'get_screenshot_as_base64', 'get_screenshot_as_file',
+                    'get_screenshot_as_png', 'get_window_position',
+                    'get_window_rect', 'get_window_size', 'maximize_window',
+                    'minimize_window', 'implicitly_wait', 'quit', 'refresh',
+                    'save_screenshot', 'set_network_conditions',
+                    'set_page_load_timeout', 'set_script_timeout',
+                    'set_window_position', 'set_window_rect', 'start_client',
+                    'start_session', 'stop_client', 'switch_to_alert'
+                ]:
+            setattr(self, function, getattr(self.driver, function))
+
+    def add_cookie(self, cookie):
+        """To pretent lint error"""
+        self.add_cookie(cookie)
+
+    def refresh(self):
+        """To pretent lint error"""
+        self.refresh()

+ 92 - 0
src/rival_regions_wrapper/cookie_handler.py

@@ -0,0 +1,92 @@
+"""
+Store and retrieve cookies
+"""
+
+from datetime import datetime
+import json
+
+from rival_regions_wrapper import LOGGER, DATA_DIR
+
+
+class CookieHandler():
+    """Cookie handler class"""
+    @classmethod
+    def write_cookies(cls, username, passed_cookies):
+        """Write cookie to file"""
+        LOGGER.info('"%s": Saving cookie', username)
+        cookies = None
+        try:
+            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
+                cookies = json.load(cookies_file)
+            if not cookies:
+                raise FileNotFoundError
+        except FileNotFoundError:
+            cookies = {username: {}}
+        if username not in cookies:
+            cookies[username] = {}
+        for cookie in passed_cookies:
+            cookies[username][cookie['name']] = {
+                'expiry': cookie['expires'],
+                'value': cookie['value'],
+            }
+
+        with open('{}/cookies.json'.format(DATA_DIR), 'w+') as cookies_file:
+            json.dump(cookies, cookies_file)
+        LOGGER.info('"%s": Saved cookie', username)
+
+    @classmethod
+    def get_cookies(cls, username):
+        """Read cookies for username"""
+        LOGGER.info('"%s": Reading cookie', username)
+        cookies = []
+        try:
+            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
+                cookies_data = json.load(cookies_file)
+                for cookie_username, user_cookies in cookies_data.items():
+                    if cookie_username == username:
+                        LOGGER.info('"%s": Found cookies', username)
+                        for cookie_name, cookie in user_cookies.items():
+                            expires = datetime.fromtimestamp(
+                                    int(cookie['expiry'])
+                                )
+                            if datetime.now() >= expires:
+                                LOGGER.info(
+                                        '"%s": Cookie is expired', username
+                                    )
+                                return None
+                            cookies.append(cls.create_cookie(
+                                cookie_name,
+                                cookie['expiry'],
+                                cookie['value'],
+                            ))
+                        return cookies
+        except FileNotFoundError:
+            pass
+        return cookies
+
+    @classmethod
+    def remove_cookie(cls, username):
+        """Remove cookie from storage"""
+        LOGGER.info('"%s": Removing cookie', username)
+        cookies = None
+        try:
+            with open('{}/cookies.json'.format(DATA_DIR), 'r') as cookies_file:
+                cookies = json.load(cookies_file)
+        except FileNotFoundError:
+            cookies = {}
+        cookies.pop(username, None)
+        with open('{}/cookies.json'.format(DATA_DIR), 'w+') as cookies_file:
+            json.dump(cookies, cookies_file)
+        LOGGER.info('"%s": Removed cookie', username)
+
+    @staticmethod
+    def create_cookie(name, expiry, value):
+        """Create cookie"""
+        return {
+            'domain': 'rivalregions.com',
+            'name': name,
+            'path': '/',
+            'secure': False,
+            'expires': expiry,
+            'value': value,
+        }

+ 76 - 0
src/rival_regions_wrapper/login_methods.py

@@ -0,0 +1,76 @@
+"""Login methods"""
+
+import time
+
+from rival_regions_wrapper import LOGGER
+
+
+# This should be working
+def login_google(browser, auth_text, username, password):
+    """login using Google"""
+    LOGGER.info('"%s": Login method Google', username)
+    auth_text1 = auth_text.split('\t<a href="')
+    auth_text2 = auth_text1[1].split('" class="sa')
+    time.sleep(1)
+    browser.go_to(auth_text2[0])
+
+    LOGGER.info('"%s": Typing in username', username)
+    browser.type(username, into='Email')
+
+    LOGGER.info('"%s": pressing next button', username)
+    browser.click(css_selector="#next")
+    time.sleep(2)
+
+    LOGGER.info('"%s": Typing in password', username)
+    browser.type(password, css_selector="input")
+
+    LOGGER.info('"%s": pressing sign in button', username)
+    browser.click(css_selector="#submit")
+    time.sleep(3)
+
+    # Some why it wont click and login immediately. This seems to work
+    time.sleep(1)
+    browser.go_to(auth_text2[0])
+    time.sleep(1)
+    browser.go_to(auth_text2[0])
+    time.sleep(1)
+    browser.click(
+        css_selector="#sa_add2 > div:nth-child(4) > a.sa_link.gogo > div"
+    )
+    time.sleep(3)
+    return browser
+
+
+# IDK if this is working
+def login_vk(browser, auth_text, username, password):
+    """login using VK"""
+    LOGGER.info('Login method VK')
+    auth_text1 = auth_text.split("(\'.vkvk\').attr(\'url\', \'")
+    auth_text2 = auth_text1[1].split('&response')
+
+    browser.go_to(auth_text2[0])
+    browser.type(username, into='email')
+    browser.type(
+            password,
+            xpath="/html/body/div/div/div/div[2]/form/div/div/input[7]"
+    )
+    browser.click('Log in')
+    return browser
+
+
+# IDK if this is working
+def login_facebook(browser, auth_text, username, password):
+    """login using Facebook"""
+    LOGGER.info('Login method Facebook')
+    auth_text1 = \
+        auth_text.split('">\r\n\t\t\t\t<div class="sa_sn imp float_left" ')
+    auth_text2 = auth_text1[0].split('200px;"><a class="sa_link" href="')
+    url = auth_text2[1]
+
+    browser.go_to(url)
+    browser.type(username, into='Email')
+    browser.type(password, into='Password')
+    browser.click('Log In')
+    time.sleep(5)
+    browser.click(css_selector='.sa_sn.imp.float_left')
+    return browser

+ 1 - 1
src/rival_regions_wrapper/middleware.py

@@ -4,7 +4,7 @@ from abc import ABC, abstractmethod
 
 import requests
 
-from rival_regions_wrapper import AuthenticationHandler
+from rival_regions_wrapper.authentication_handler import AuthenticationHandler
 
 
 class MiddlewareBase(ABC):

+ 26 - 1
src/rival_regions_wrapper/functions.py → src/rival_regions_wrapper/util.py

@@ -1,4 +1,6 @@
-"""Common functions"""
+"""
+Common functions and datastructures that modules might need
+"""
 
 import re
 from datetime import timedelta, timezone
@@ -6,6 +8,29 @@ from datetime import timedelta, timezone
 from dateutil import parser
 
 
+ITEM_KEYS = {
+    'oil': 3,
+    'ore': 4,
+    'uranium': 11,
+    'diamonds': 15,
+    'liquid_oxygen': 21,
+    'helium-3': 24,
+    'rivalium': 26,
+    'antirad': 13,
+    'energy_drink': 17,
+    'spacerockets': 20,
+    'lss': 25,
+    'tanks': 2,
+    'aircrafts': 1,
+    'missiles': 14,
+    'bombers': 16,
+    'battleships': 18,
+    'laser_drones': 27,
+    'moon_tanks': 22,
+    'space_stations': 23
+}
+
+
 def parse_date(date_string):
     """Try to parse any string to date"""
     date_string = date_string.lower()

+ 23 - 3
tests/conftest.py

@@ -3,9 +3,10 @@
 import os
 
 import pytest
+import _pytest.skipping
 from dotenv import load_dotenv
 
-from rival_regions_wrapper import LocalAuthentication, ApiWrapper
+from rival_regions_wrapper.middleware import LocalAuthentication
 
 
 load_dotenv()
@@ -15,6 +16,26 @@ class MissingAuthenticationError(Exception):
     """Error for missing authentication"""
 
 
+def pytest_addoption(parser):
+    """Add option to parser to prevent skips"""
+    parser.addoption(
+        "--no-skips",
+        action="store_true",
+        default=False, help="disable skip marks")
+
+
+@pytest.hookimpl(tryfirst=True)
+def pytest_cmdline_preparse(config, args):
+    """Add check for skips"""
+    if "--no-skips" not in args:
+        return
+
+    def no_skip(*args, **kwargs):
+        return
+
+    _pytest.skipping.skip = no_skip
+
+
 @pytest.fixture(scope='module')
 def vcr(vcr):
     """Set parameters vor VCR"""
@@ -33,5 +54,4 @@ def api_wrapper():
             'Load the following variables in your user environment: '
             'username, password, login_method'
         )
-    authentication = LocalAuthentication(username, password, login_method)
-    return ApiWrapper(authentication)
+    return LocalAuthentication(username, password, login_method)

+ 244 - 89
tests/test_rival_regions_wrapper.py

@@ -1,17 +1,23 @@
 """Wrapper test"""
 
+# pylint: disable=redefined-outer-name
+
 from datetime import datetime, timedelta
 
 import pytest
 
-from rival_regions_wrapper.api_wrapper import Profile, Storage, Market, ResourceState, Perks, \
-    Craft, Overview, War, Work, Article
+from rival_regions_wrapper.api_wrapper import Profile, Storage, Market, \
+        ResourceState, Perks, Craft, Overview, War, Work, Article, Conference
 
 
 @pytest.fixture
 def profile_keys():
     """Standard key from profile"""
-    return ['profile_id', 'name', 'level', 'level_percentage', 'strenght', 'education', 'endurance']
+    return [
+            'profile_id', 'name', 'level', 'level_percentage', 'strenght',
+            'education', 'endurance'
+        ]
+
 
 @pytest.mark.vcr()
 def test_profile_info(api_wrapper, profile_keys):
@@ -20,28 +26,41 @@ def test_profile_info(api_wrapper, profile_keys):
     response = profile_instance.info()
 
     assert isinstance(response, dict), "The response should be a dict"
-    assert response['profile_id'] == 192852686, "The ID should be in the response"
-    assert set(profile_keys).issubset(response.keys()), "All keys should be in the response"
+    assert response['profile_id'] == 192852686, \
+        "The ID should be in the response"
+    assert set(profile_keys).issubset(response.keys()), \
+        "All keys should be in the response"
     assert isinstance(response['name'], str), "Name should be a string"
     assert isinstance(response['level'], int), "level should be a int"
-    assert isinstance(response['level_percentage'], int), "level_percentage should be a int"
+    assert isinstance(response['level_percentage'], int), \
+        "level_percentage should be a int"
     assert isinstance(response['strenght'], int), "strenght should be a int"
     assert isinstance(response['education'], int), "education should be a int"
     assert isinstance(response['endurance'], int), "endurance should be a int"
 
+
+@pytest.mark.skip(reason="message request")
+def test_profile_message(api_wrapper):
+    """Test an API to send message to profile"""
+    Profile(api_wrapper, 2000340574).message('hi')
+
+
 @pytest.fixture
 def storage_keys():
     """Standard keys for storage"""
     return [
-        'oil', 'ore', 'uranium', 'diamonds', 'liquid_oxygen',
-        'helium-3', 'rivalium', 'antirad', 'energy_drink',
-        'spacerockets', 'lss', 'tanks', 'aircrafts', 'missiles',
-        'bombers', 'battleships', 'laser_drones', 'moon_tanks', 'space_stations',
-        'oil_max', 'ore_max', 'uranium_max', 'diamonds_max', 'liquid_oxygen_max',
-        'helium-3_max', 'rivalium_max', 'antirad_max', 'energy_drink_max',
-        'spacerockets_max', 'lss_max', 'tanks_max', 'aircrafts_max', 'missiles_max',
-        'bombers_max', 'battleships_max', 'laser_drones_max', 'moon_tanks_max', 'space_stations'
-    ]
+            'oil', 'ore', 'uranium', 'diamonds', 'liquid_oxygen',
+            'helium-3', 'rivalium', 'antirad', 'energy_drink',
+            'spacerockets', 'lss', 'tanks', 'aircrafts', 'missiles',
+            'bombers', 'battleships', 'laser_drones', 'moon_tanks',
+            'space_stations', 'oil_max', 'ore_max', 'uranium_max',
+            'diamonds_max', 'liquid_oxygen_max', 'helium-3_max',
+            'rivalium_max', 'antirad_max', 'energy_drink_max',
+            'spacerockets_max', 'lss_max', 'tanks_max', 'aircrafts_max',
+            'missiles_max', 'bombers_max', 'battleships_max',
+            'laser_drones_max', 'moon_tanks_max', 'space_stations'
+        ]
+
 
 @pytest.mark.vcr()
 def test_storage_info(api_wrapper, storage_keys):
@@ -49,13 +68,16 @@ def test_storage_info(api_wrapper, storage_keys):
     response = Storage(api_wrapper).info()
 
     assert isinstance(response, dict), "The response should be a dict"
-    assert set(storage_keys).issubset(response.keys()), "All keys should be in the response"
+    assert set(storage_keys).issubset(response.keys()), \
+        "All keys should be in the response"
+
 
 @pytest.fixture
 def market_keys():
     """Standard keys for storage"""
     return ['player_id', 'player_name', 'price', 'amount']
 
+
 @pytest.mark.vcr()
 def test_market_info(api_wrapper, market_keys):
     """Test an API call to get market info"""
@@ -64,17 +86,28 @@ def test_market_info(api_wrapper, market_keys):
 
     assert isinstance(response, list), "The response should be a list"
     if response:
-        assert isinstance(response[0], dict), "The first element should be a dict"
-        assert set(market_keys).issubset(response[0].keys()), "All keys should be in the response"
-        assert isinstance(response[0]['player_id'], int), "The player_id should be a int"
-        assert isinstance(response[0]['player_name'], str), "The player_name should be a int"
-        assert isinstance(response[0]['price'], int), "The price should be a int"
-        assert isinstance(response[0]['amount'], int), "The price should be a int"
+        assert isinstance(response[0], dict), \
+            "The first element should be a dict"
+        assert set(market_keys).issubset(response[0].keys()), \
+            "All keys should be in the response"
+        assert isinstance(response[0]['player_id'], int), \
+            "The player_id should be a int"
+        assert isinstance(response[0]['player_name'], str), \
+            "The player_name should be a int"
+        assert isinstance(response[0]['price'], int), \
+            "The price should be a int"
+        assert isinstance(response[0]['amount'], int), \
+            "The price should be a int"
+
 
 @pytest.fixture
 def resource_keys():
     """Standard keys for resource"""
-    return ['region_id', 'region_name', 'explored', 'maximum', 'deep_exploration', 'limit_left']
+    return [
+            'region_id', 'region_name', 'explored', 'maximum',
+            'deep_exploration', 'limit_left'
+        ]
+
 
 @pytest.mark.vcr()
 def test_resource_state_info(api_wrapper, resource_keys):
@@ -85,45 +118,72 @@ def test_resource_state_info(api_wrapper, resource_keys):
 
     assert isinstance(response, list), "The response should be a list"
     if response:
-        assert isinstance(response[0], dict), "The first element should be a dict"
-        assert set(resource_keys).issubset(response[0].keys()), "All keys should be in the response"
-        assert isinstance(response[0]['region_id'], int), "The region_id should be a int"
-        assert isinstance(response[0]['region_name'], str), "The region_name should be a str"
-        assert isinstance(response[0]['explored'], float), "The explored should be a float"
-        assert isinstance(response[0]['maximum'], int), "The maximum should be a int"
-        assert isinstance(response[0]['deep_exploration'], int), "deep_exploration should be int"
-        assert isinstance(response[0]['limit_left'], int), "The limit_left should be a int"
+        assert isinstance(response[0], dict), \
+            "The first element should be a dict"
+        assert set(resource_keys).issubset(response[0].keys()), \
+            "All keys should be in the response"
+        assert isinstance(response[0]['region_id'], int), \
+            "The region_id should be a int"
+        assert isinstance(response[0]['region_name'], str), \
+            "The region_name should be a str"
+        assert isinstance(response[0]['explored'], float), \
+            "The explored should be a float"
+        assert isinstance(response[0]['maximum'], int), \
+            "The maximum should be a int"
+        assert isinstance(response[0]['deep_exploration'], int), \
+            "deep_exploration should be int"
+        assert isinstance(response[0]['limit_left'], int), \
+            "The limit_left should be a int"
+
 
 @pytest.fixture
 def perks_keys():
     """Standard keys for perks"""
-    return ['strenght', 'education', 'endurance', 'upgrade_date', 'upgrade_perk']
+    return [
+            'strenght', 'education', 'endurance', 'upgrade_date',
+            'upgrade_perk'
+        ]
+
 
 @pytest.mark.vcr()
 def test_perks_info(api_wrapper, perks_keys):
     """Test an API call to get perks info"""
     response = Perks(api_wrapper).info()
 
-    assert isinstance(response, dict), "The response should be a dict"
-    assert set(perks_keys).issubset(response.keys()), "All keys should be in the response"
+    assert isinstance(response, dict), \
+        "The response should be a dict"
+    assert set(perks_keys).issubset(response.keys()), \
+        "All keys should be in the response"
     assert isinstance(response['strenght'], int), "strengt should be an int"
     assert isinstance(response['education'], int), "educatino should be an int"
     assert isinstance(response['endurance'], int), "endurance should be an int"
-    assert isinstance(response['upgrade_date'], datetime), "upgrade_date should be a date"
-    assert isinstance(response['upgrade_perk'], int), "upgrade_perk should be an int"
+
+    try:
+        assert isinstance(response['upgrade_date'], datetime), \
+            "upgrade_date should be a date"
+        assert isinstance(response['upgrade_perk'], int), \
+            "upgrade_perk should be an int"
+    except AssertionError:
+        assert isinstance(response['upgrade_date'], type(None)), \
+            "upgrade_date should be None if not upgrading"
+        assert isinstance(response['upgrade_perk'], type(None)), \
+            "upgrade_perk should be an int"
+
 
 @pytest.mark.skip(reason="Update request")
 def test_perks_upgrade(api_wrapper):
     """Test an API call to upgrade perk"""
     perk = 'endurance'
     upgrade_type = 'money'
-    Perks.upgrade(perk, upgrade_type )
+    Perks(api_wrapper).upgrade(perk, upgrade_type)
+
 
 @pytest.fixture
 def craft_keys():
     """Standard keys for craft"""
     return ['market_price', 'resources']
 
+
 @pytest.mark.skip(reason="Update request")
 def test_craft_produce(api_wrapper):
     """Test an API call to produce new item"""
@@ -132,24 +192,31 @@ def test_craft_produce(api_wrapper):
 
     assert True
 
+
 @pytest.fixture
 def overview_info_keys():
     """Standard keys for overview info"""
     return ['perks', 'war']
 
+
 @pytest.mark.vcr()
 def test_overview_info(api_wrapper, overview_info_keys):
     """Test an API call for overview"""
     response = Overview(api_wrapper).info()
 
     assert isinstance(response, dict), "The response hould be a dict"
-    assert set(overview_info_keys).issubset(response.keys()), "All keys should be in the response"
+    assert set(overview_info_keys).issubset(response.keys()), \
+        "All keys should be in the response"
     assert isinstance(response['war'], dict), "The war key should be a dict"
 
+
 @pytest.fixture
 def overview_status_keys():
     """Standard kenys for overview status"""
-    return ['profile_id', 'party_id', 'gold', 'money', 'level', 'exp']
+    return [
+            'profile_id', 'party_id', 'gold', 'money', 'level', 'exp'
+        ]
+
 
 @pytest.mark.vcr()
 def test_overview_status(api_wrapper, overview_status_keys):
@@ -157,7 +224,9 @@ def test_overview_status(api_wrapper, overview_status_keys):
     response = Overview(api_wrapper).status()
 
     assert isinstance(response, dict), "The response hould be a dict"
-    assert set(overview_status_keys).issubset(response.keys()), "All keys should be in the response"
+    assert set(overview_status_keys).issubset(response.keys()), \
+        "All keys should be in the response"
+
 
 @pytest.mark.vcr()
 def test_war_page(api_wrapper):
@@ -166,7 +235,9 @@ def test_war_page(api_wrapper):
 
     assert isinstance(response, dict), "The response should be a dict"
     if response['training_war']:
-        assert isinstance(response['training_war'], int), "The training_war should be an int"
+        assert isinstance(response['training_war'], int), \
+            "The training_war should be an int"
+
 
 @pytest.mark.vcr()
 def test_war_info(api_wrapper):
@@ -177,17 +248,26 @@ def test_war_info(api_wrapper):
     response = war.info(war_id)
 
     assert isinstance(response, dict), "The response should be a dict"
-    assert isinstance(response['damage'], int), "Damage should be an int"
-    assert isinstance(response['attack_hourly_available'], bool), "Attack hourly should be a bool"
-    assert isinstance(response['energ_drinks'], int), "Energy drinks should be an int"
+    assert isinstance(response['damage'], int), \
+        "Damage should be an int"
+    assert isinstance(response['attack_hourly_available'], bool), \
+        "Attack hourly should be a bool"
+    assert isinstance(response['energ_drinks'], int), \
+        "Energy drinks should be an int"
     if 'max_hero_name' in response:
-        assert isinstance(response['max_hero_name'], str), "max hero name should be a str"
+        assert isinstance(response['max_hero_name'], str), \
+            "max hero name should be a str"
     if 'max_hero_damage' in response:
-        assert isinstance(response['max_hero_damage'], int), "max hero damage should be an int"
+        assert isinstance(response['max_hero_damage'], int), \
+            "max hero damage should be an int"
     if 'time_left' in response:
-        assert isinstance(response['time_left'], timedelta), "time left should be a time delta"
-    assert isinstance(response['finish_date'], datetime), "Finish date should be a date"
-    assert isinstance(response['war_units'], dict), "war units should be a dict"
+        assert isinstance(response['time_left'], timedelta), \
+            "time left should be a time delta"
+    assert isinstance(response['finish_date'], datetime), \
+        "Finish date should be a date"
+    assert isinstance(response['war_units'], dict), \
+        "war units should be a dict"
+
 
 @pytest.mark.vcr()
 def test_war_info_ground_war(api_wrapper):
@@ -198,16 +278,27 @@ def test_war_info_ground_war(api_wrapper):
     assert isinstance(response, dict), "The response should be a dict"
     assert response['type'] == 'war', "Type should be a ground war"
     assert isinstance(response['attack'], dict), "Attack should be a dict"
-    assert isinstance(response['attack']['state_id'], int), "State id should be an integer"
-    assert isinstance(response['attack']['state_name'], str), "State nameshould be a string"
-    assert isinstance(response['attack']['region_id'], int), "Region id should be an integer"
-    assert isinstance(response['attack']['region_name'], str), "Region name should be a string"
-    assert isinstance(response['attack']['damage'], int), "Damage should be an intger"
-    assert isinstance(response['defend']['state_id'], int), "State id should be an integer"
-    assert isinstance(response['defend']['state_name'], str), "State name should be a string"
-    assert isinstance(response['defend']['region_id'], int), "Region id should be an integer"
-    assert isinstance(response['defend']['region_name'], str), "Region name should be a string"
-    assert isinstance(response['defend']['damage'], int), "Damage should be an integer"
+    assert isinstance(response['attack']['state_id'], int), \
+        "State id should be an integer"
+    assert isinstance(response['attack']['state_name'], str), \
+        "State nameshould be a string"
+    assert isinstance(response['attack']['region_id'], int), \
+        "Region id should be an integer"
+    assert isinstance(response['attack']['region_name'], str), \
+        "Region name should be a string"
+    assert isinstance(response['attack']['damage'], int), \
+        "Damage should be an intger"
+    assert isinstance(response['defend']['state_id'], int), \
+        "State id should be an integer"
+    assert isinstance(response['defend']['state_name'], str), \
+        "State name should be a string"
+    assert isinstance(response['defend']['region_id'], int), \
+        "Region id should be an integer"
+    assert isinstance(response['defend']['region_name'], str), \
+        "Region name should be a string"
+    assert isinstance(response['defend']['damage'], int), \
+        "Damage should be an integer"
+
 
 @pytest.mark.vcr()
 def test_war_info_coup(api_wrapper):
@@ -218,6 +309,7 @@ def test_war_info_coup(api_wrapper):
     assert isinstance(response, dict), "The response should be a dict"
     assert response['type'] == 'coup', "Type should be a coup"
 
+
 @pytest.mark.vcr()
 def test_war_info_revolution(api_wrapper):
     """Test war info"""
@@ -227,6 +319,7 @@ def test_war_info_revolution(api_wrapper):
     assert isinstance(response, dict), "The response should be a dict"
     assert response['type'] == 'revolution', "Type should be a revolution"
 
+
 @pytest.mark.vcr()
 def test_war_info_trooper_war(api_wrapper):
     """Test war info"""
@@ -236,6 +329,7 @@ def test_war_info_trooper_war(api_wrapper):
     assert isinstance(response, dict), "The response should be a dict"
     assert response['type'] == 'troopers war', "Type should be a trooper war"
 
+
 @pytest.mark.vcr()
 def test_war_info_sea_war(api_wrapper):
     """Test war info"""
@@ -245,6 +339,7 @@ def test_war_info_sea_war(api_wrapper):
     assert isinstance(response, dict), "The response should be a dict"
     assert response['type'] == 'sea war', "Type should be a sea war"
 
+
 @pytest.mark.vcr()
 def test_war_info_space_war(api_wrapper):
     """Test war info"""
@@ -262,15 +357,21 @@ def test_work_info(api_wrapper):
 
     assert isinstance(response, dict), "The response should be a dict"
     assert isinstance(response['factory'], dict), "Factory should be a dict"
-    assert isinstance(response['resources_left'], dict), "Resources left should be a dict"
+    assert isinstance(response['resources_left'], dict), \
+        "Resources left should be a dict"
     assert isinstance(response['work_exp'], dict), "Work exp should be a dict"
 
+
 @pytest.fixture
 def article_keys():
     """Standard key fro article"""
-    return ['article_id', 'article_title', 'newspaper_id', 'newspaper_name', \
-        'author_name', 'author_id', 'region_name', 'region_id', 'content_text', 'content_html', \
-        'language', 'rating', 'comments', 'post_date']
+    return [
+            'article_id', 'article_title', 'newspaper_id', 'newspaper_name',
+            'author_name', 'author_id', 'region_name', 'region_id',
+            'content_text', 'content_html', 'language', 'rating', 'comments',
+            'post_date'
+        ]
+
 
 @pytest.mark.vcr()
 def test_article_info_one(api_wrapper, article_keys):
@@ -279,21 +380,37 @@ def test_article_info_one(api_wrapper, article_keys):
     response = Article(api_wrapper).info(article_id)
 
     assert isinstance(response, dict), "The resonse should be a dict"
-    assert set(article_keys).issubset(response.keys()), "All keys should be in the response"
-    assert isinstance(response['article_id'], int), "Article id should be an integer"
-    assert isinstance(response['article_title'], str), "Article title should be a str"
-    assert isinstance(response['newspaper_id'], int), "Newspaper id should be an integer"
-    assert isinstance(response['newspaper_name'], str), "Newspaper name should be a string"
-    assert isinstance(response['author_name'], str), "Author name should be a string"
-    assert isinstance(response['author_id'], int), "Author id should be an integer"
-    assert isinstance(response['region_name'], str), "Region name should be a string"
-    assert isinstance(response['region_id'], int), "Region id should be an integer"
-    assert isinstance(response['content_text'], str), "Content text should be a string"
-    assert isinstance(response['content_html'], str), "Content html should be a string"
-    assert isinstance(response['language'], str), "Language should be a string"
-    assert isinstance(response['rating'], int), "Rating should be an integer"
-    assert isinstance(response['comments'], int), "Comments should be an integer"
-    assert isinstance(response['post_date'], datetime), "Post date should be a datetime"
+    assert set(article_keys).issubset(response.keys()), \
+        "All keys should be in the response"
+    assert isinstance(response['article_id'], int), \
+        "Article id should be an integer"
+    assert isinstance(response['article_title'], str), \
+        "Article title should be a str"
+    assert isinstance(response['newspaper_id'], int), \
+        "Newspaper id should be an integer"
+    assert isinstance(response['newspaper_name'], str), \
+        "Newspaper name should be a string"
+    assert isinstance(response['author_name'], str), \
+        "Author name should be a string"
+    assert isinstance(response['author_id'], int), \
+        "Author id should be an integer"
+    assert isinstance(response['region_name'], str), \
+        "Region name should be a string"
+    assert isinstance(response['region_id'], int), \
+        "Region id should be an integer"
+    assert isinstance(response['content_text'], str), \
+        "Content text should be a string"
+    assert isinstance(response['content_html'], str), \
+        "Content html should be a string"
+    assert isinstance(response['language'], str), \
+        "Language should be a string"
+    assert isinstance(response['rating'], int), \
+        "Rating should be an integer"
+    assert isinstance(response['comments'], int), \
+        "Comments should be an integer"
+    assert isinstance(response['post_date'], datetime), \
+        "Post date should be a datetime"
+
 
 @pytest.mark.vcr()
 def test_article_info_two(api_wrapper, article_keys):
@@ -302,18 +419,56 @@ def test_article_info_two(api_wrapper, article_keys):
     response = Article(api_wrapper).info(article_id)
 
     assert isinstance(response, dict), "The resonse should be a dict"
-    assert set(article_keys).issubset(response.keys()), "All keys should be in the response"
-    assert isinstance(response['article_id'], int), "Article id should be an integer"
-    assert isinstance(response['article_title'], str), "Article title should be a str"
+    assert set(article_keys).issubset(response.keys()), \
+        "All keys should be in the response"
+    assert isinstance(response['article_id'], int), \
+        "Article id should be an integer"
+    assert isinstance(response['article_title'], str), \
+        "Article title should be a str"
     assert response['newspaper_id'] is None, "Newspaper id should be none"
     assert response['newspaper_name'] is None, "Newspaper name should be none"
-    assert isinstance(response['author_name'], str), "Author name should be a string"
-    assert isinstance(response['author_id'], int), "Author id should be an integer"
-    assert isinstance(response['region_name'], str), "Region name should be a string"
-    assert isinstance(response['region_id'], int), "Region id should be an integer"
-    assert isinstance(response['content_text'], str), "Content text should be a string"
-    assert isinstance(response['content_html'], str), "Content html should be a string"
+    assert isinstance(response['author_name'], str), \
+        "Author name should be a string"
+    assert isinstance(response['author_id'], int), \
+        "Author id should be an integer"
+    assert isinstance(response['region_name'], str), \
+        "Region name should be a string"
+    assert isinstance(response['region_id'], int), \
+        "Region id should be an integer"
+    assert isinstance(response['content_text'], str), \
+        "Content text should be a string"
+    assert isinstance(response['content_html'], str), \
+        "Content html should be a string"
     assert isinstance(response['language'], str), "Language should be a string"
     assert isinstance(response['rating'], int), "Rating should be an integer"
-    assert isinstance(response['comments'], int), "Comments should be an integer"
-    assert isinstance(response['post_date'], datetime), "Post date should be a datetime"
+    assert isinstance(response['comments'], int), \
+        "Comments should be an integer"
+    assert isinstance(response['post_date'], datetime), \
+        "Post date should be a datetime"
+
+
+@pytest.mark.skip(reason="message request")
+def test_conference_message(api_wrapper):
+    """Test conference message"""
+    conference_id = 439289
+    Conference(api_wrapper, conference_id).message('hi')
+
+
+@pytest.mark.skip(reason="notification request")
+def test_conference_notification(api_wrapper):
+    """Test conference notification"""
+    conference_id = 439289
+    Conference(api_wrapper, conference_id).notification('hi', True)
+
+
+@pytest.mark.skip(reason="notification request")
+def test_conference_change_title(api_wrapper):
+    """Test conference change title"""
+    conference_id = 439289
+    Conference(api_wrapper, conference_id).change_title('new title')
+
+
+@pytest.mark.skip(reason="message request")
+def test_language_chat_(api_wrapper):
+    """Test an API to send message to profile"""
+    Profile(api_wrapper, 2000340574).message('hi')

+ 1 - 1
tox.ini

@@ -4,7 +4,7 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = py38
+envlist = py39
 
 [testenv]
 deps =