Explorar el Código

Initial commit

JoostSijm hace 5 años
commit
18d1c974f9
Se han modificado 8 ficheros con 475 adiciones y 0 borrados
  1. 3 0
      .gitignore
  2. 17 0
      Pipfile
  3. 151 0
      Pipfile.lock
  4. 1 0
      example.env
  5. 29 0
      hvs/__init__.py
  6. 61 0
      hvs/__main__.py
  7. 130 0
      hvs/app.py
  8. 83 0
      hvs/models.py

+ 3 - 0
.gitignore

@@ -0,0 +1,3 @@
+.venv/
+.env
+__pycache__

+ 17 - 0
Pipfile

@@ -0,0 +1,17 @@
+[[source]]
+name = "pypi"
+url = "https://pypi.org/simple"
+verify_ssl = true
+
+[dev-packages]
+
+[packages]
+requests = "*"
+beautifulsoup4 = "*"
+sqlalchemy = "*"
+python-dotenv = "*"
+psycopg2-binary = "*"
+apscheduler = "*"
+
+[requires]
+python_version = "3.7"

+ 151 - 0
Pipfile.lock

@@ -0,0 +1,151 @@
+{
+    "_meta": {
+        "hash": {
+            "sha256": "40681423ee7bb7705184a16526334130adc76c1b726852ea6797a4552c90cdac"
+        },
+        "pipfile-spec": 6,
+        "requires": {
+            "python_version": "3.7"
+        },
+        "sources": [
+            {
+                "name": "pypi",
+                "url": "https://pypi.org/simple",
+                "verify_ssl": true
+            }
+        ]
+    },
+    "default": {
+        "apscheduler": {
+            "hashes": [
+                "sha256:529afb7909e08416132891188cbfea5351eb35e4a684b67e983d819e8d01a6b0",
+                "sha256:cde18f6dbffa1b75aff67fd7fe423a3020cb0363f6c67bd45f24306d90898231"
+            ],
+            "index": "pypi",
+            "version": "==3.6.1"
+        },
+        "beautifulsoup4": {
+            "hashes": [
+                "sha256:05668158c7b85b791c5abde53e50265e16f98ad601c402ba44d70f96c4159612",
+                "sha256:25288c9e176f354bf277c0a10aa96c782a6a18a17122dba2e8cec4a97e03343b",
+                "sha256:f040590be10520f2ea4c2ae8c3dae441c7cfff5308ec9d58a0ec0c1b8f81d469"
+            ],
+            "index": "pypi",
+            "version": "==4.8.0"
+        },
+        "certifi": {
+            "hashes": [
+                "sha256:046832c04d4e752f37383b628bc601a7ea7211496b4638f6514d0e5b9acc4939",
+                "sha256:945e3ba63a0b9f577b1395204e13c3a231f9bc0223888be653286534e5873695"
+            ],
+            "version": "==2019.6.16"
+        },
+        "chardet": {
+            "hashes": [
+                "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
+                "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+            ],
+            "version": "==3.0.4"
+        },
+        "idna": {
+            "hashes": [
+                "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
+                "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
+            ],
+            "version": "==2.8"
+        },
+        "psycopg2-binary": {
+            "hashes": [
+                "sha256:080c72714784989474f97be9ab0ddf7b2ad2984527e77f2909fcd04d4df53809",
+                "sha256:110457be80b63ff4915febb06faa7be002b93a76e5ba19bf3f27636a2ef58598",
+                "sha256:171352a03b22fc099f15103959b52ee77d9a27e028895d7e5fde127aa8e3bac5",
+                "sha256:19d013e7b0817087517a4b3cab39c084d78898369e5c46258aab7be4f233d6a1",
+                "sha256:249b6b21ae4eb0f7b8423b330aa80fab5f821b9ffc3f7561a5e2fd6bb142cf5d",
+                "sha256:2ac0731d2d84b05c7bb39e85b7e123c3a0acd4cda631d8d542802c88deb9e87e",
+                "sha256:2b6d561193f0dc3f50acfb22dd52ea8c8dfbc64bcafe3938b5f209cc17cb6f00",
+                "sha256:2bd23e242e954214944481124755cbefe7c2cf563b1a54cd8d196d502f2578bf",
+                "sha256:3e1239242ca60b3725e65ab2f13765fc199b03af9eaf1b5572f0e97bdcee5b43",
+                "sha256:3eb70bb697abbe86b1d2b1316370c02ba320bfd1e9e35cf3b9566a855ea8e4e5",
+                "sha256:51a2fc7e94b98bd1bb5d4570936f24fc2b0541b63eccadf8fdea266db8ad2f70",
+                "sha256:52f1bdafdc764b7447e393ed39bb263eccb12bfda25a4ac06d82e3a9056251f6",
+                "sha256:5b3581319a3951f1e866f4f6c5e42023db0fae0284273b82e97dfd32c51985cd",
+                "sha256:63c1b66e3b2a3a336288e4bcec499e0dc310cd1dceaed1c46fa7419764c68877",
+                "sha256:8123a99f24ecee469e5c1339427bcdb2a33920a18bb5c0d58b7c13f3b0298ba3",
+                "sha256:85e699fcabe7f817c0f0a412d4e7c6627e00c412b418da7666ff353f38e30f67",
+                "sha256:8dbff4557bbef963697583366400822387cccf794ccb001f1f2307ed21854c68",
+                "sha256:908d21d08d6b81f1b7e056bbf40b2f77f8c499ab29e64ec5113052819ef1c89b",
+                "sha256:af39d0237b17d0a5a5f638e9dffb34013ce2b1d41441fd30283e42b22d16858a",
+                "sha256:af51bb9f055a3f4af0187149a8f60c9d516cf7d5565b3dac53358796a8fb2a5b",
+                "sha256:b2ecac57eb49e461e86c092761e6b8e1fd9654dbaaddf71a076dcc869f7014e2",
+                "sha256:cd37cc170678a4609becb26b53a2bc1edea65177be70c48dd7b39a1149cabd6e",
+                "sha256:d17e3054b17e1a6cb8c1140f76310f6ede811e75b7a9d461922d2c72973f583e",
+                "sha256:d305313c5a9695f40c46294d4315ed3a07c7d2b55e48a9010dad7db7a66c8b7f",
+                "sha256:dd0ef0eb1f7dd18a3f4187226e226a7284bda6af5671937a221766e6ef1ee88f",
+                "sha256:e1adff53b56db9905db48a972fb89370ad5736e0450b96f91bcf99cadd96cfd7",
+                "sha256:f0d43828003c82dbc9269de87aa449e9896077a71954fbbb10a614c017e65737",
+                "sha256:f78e8b487de4d92640105c1389e5b90be3496b1d75c90a666edd8737cc2dbab7"
+            ],
+            "index": "pypi",
+            "version": "==2.8.3"
+        },
+        "python-dotenv": {
+            "hashes": [
+                "sha256:debd928b49dbc2bf68040566f55cdb3252458036464806f4094487244e2a4093",
+                "sha256:f157d71d5fec9d4bd5f51c82746b6344dffa680ee85217c123f4a0c8117c4544"
+            ],
+            "index": "pypi",
+            "version": "==0.10.3"
+        },
+        "pytz": {
+            "hashes": [
+                "sha256:26c0b32e437e54a18161324a2fca3c4b9846b74a8dccddd843113109e1116b32",
+                "sha256:c894d57500a4cd2d5c71114aaab77dbab5eabd9022308ce5ac9bb93a60a6f0c7"
+            ],
+            "version": "==2019.2"
+        },
+        "requests": {
+            "hashes": [
+                "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4",
+                "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31"
+            ],
+            "index": "pypi",
+            "version": "==2.22.0"
+        },
+        "six": {
+            "hashes": [
+                "sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c",
+                "sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73"
+            ],
+            "version": "==1.12.0"
+        },
+        "soupsieve": {
+            "hashes": [
+                "sha256:8662843366b8d8779dec4e2f921bebec9afd856a5ff2e82cd419acc5054a1a92",
+                "sha256:a5a6166b4767725fd52ae55fee8c8b6137d9a51e9f1edea461a062a759160118"
+            ],
+            "version": "==1.9.3"
+        },
+        "sqlalchemy": {
+            "hashes": [
+                "sha256:0459bf0ea6478f3e904de074d65769a11d74cdc34438ab3159250c96d089aef0"
+            ],
+            "index": "pypi",
+            "version": "==1.3.7"
+        },
+        "tzlocal": {
+            "hashes": [
+                "sha256:11c9f16e0a633b4b60e1eede97d8a46340d042e67b670b290ca526576e039048",
+                "sha256:949b9dd5ba4be17190a80c0268167d7e6c92c62b30026cf9764caf3e308e5590"
+            ],
+            "version": "==2.0.0"
+        },
+        "urllib3": {
+            "hashes": [
+                "sha256:b246607a25ac80bedac05c6f282e3cdaf3afb65420fd024ac94435cabe6e18d1",
+                "sha256:dbe59173209418ae49d485b87d1681aefa36252ee85884c31346debd19463232"
+            ],
+            "version": "==1.25.3"
+        }
+    },
+    "develop": {}
+}

+ 1 - 0
example.env

@@ -0,0 +1 @@
+AUTHORIZATION=PLACEHOLDER

+ 29 - 0
hvs/__init__.py

@@ -0,0 +1,29 @@
+"""Init"""
+
+import os
+
+from sqlalchemy import create_engine
+from sqlalchemy.orm import sessionmaker
+from dotenv import load_dotenv
+
+from hvs.models import Base, Region, DeepExploration, ResourceTrack, ResourceStat
+
+from apscheduler.schedulers.background import BackgroundScheduler
+
+
+load_dotenv()
+
+# database
+engine = create_engine(os.environ["DATABASE_URI"])
+Session = sessionmaker(bind=engine)
+
+# scheduler
+scheduler = BackgroundScheduler()
+scheduler.add_jobstore('sqlalchemy', url=os.environ["DATABASE_URI"])
+scheduler.start()
+
+# api
+BASE_URL = os.environ["API_URL"]
+HEADERS = {
+    'Authorization': os.environ["AUTHORIZATION"]
+}

+ 61 - 0
hvs/__main__.py

@@ -0,0 +1,61 @@
+"""Main app"""
+
+from datetime import datetime, timedelta
+import random
+import time
+
+from hvs import scheduler
+from hvs.app import download_resources, need_refill, refill, save_resources, print_resources
+
+
+def job_check_resources(state_id, resource_id, capital_id):
+    """Check resources and refill if necessary"""
+    regions = download_resources(state_id, resource_id)
+    save_resources(state_id, regions)
+    print_resources(regions)
+    if need_refill(regions):
+        random_time = timedelta(seconds=random.randint(0, 300))
+        scheduled_date = datetime.now() + random_time
+        job_id = 'refill_{}_{}'.format(capital_id, resource_id)
+        print('refill resource: {} at {} ({} minutes)'.format(
+            resource_id,
+            scheduled_date,
+            round(random_time.seconds / 60)
+        ))
+        job = scheduler.get_job(job_id)
+        if not job:
+            scheduler.add_job(
+                job_refill_resource,
+                'date',
+                args=[4002, resource_id],
+                id=job_id,
+                run_date=scheduled_date
+            )
+
+def job_refill_resource(capital_id, resource_id):
+    """Execute refill job"""
+    refill(capital_id, resource_id)
+
+if __name__ == '__main__':
+    # jobs
+    VN_CHECK_GOLD_JOB = scheduler.get_job('vn_check_gold')
+    if not VN_CHECK_GOLD_JOB:
+        scheduler.add_job(
+            job_check_resources,
+            'cron',
+            args=[2788, 0, 4002],
+            id='vn_check_gold',
+            minute='0,15,30,45'
+        )
+    VN_CHECK_URANIUM_JOB = scheduler.get_job('vn_check_uranium')
+    if not VN_CHECK_URANIUM_JOB:
+        scheduler.add_job(
+            job_check_resources,
+            'cron',
+            args=[2788, 11, 4002],
+            id='vn_check_uranium',
+            minute='0'
+        )
+
+    while True:
+        time.sleep(100)

+ 130 - 0
hvs/app.py

@@ -0,0 +1,130 @@
+"""Main application"""
+
+import re
+
+import requests
+from bs4 import BeautifulSoup
+
+from hvs import BASE_URL, HEADERS, Session
+from hvs.models import ResourceTrack, ResourceStat
+
+
+RESOURCES = {
+    0: 'gold',
+    2: 'oil',
+    4: 'ore',
+    11: 'uranium',
+    15: 'diamond',
+}
+
+def download_resources(state_id, resource_id):
+    """Download the resource list"""
+    response = requests.get(
+        '{}listed/stateresources/{}/{}'.format(BASE_URL, state_id, RESOURCES[resource_id]),
+        headers=HEADERS
+    )
+    return parse_resources(response.text)
+
+
+def read_resources():
+    """Read resource file"""
+    with open('resources.html') as file:
+        return parse_resources(file)
+
+
+def parse_resources(html):
+    """Read the resources left"""
+    soup = BeautifulSoup(html, 'html.parser')
+
+    regions_tree = soup.find_all(class_='list_link')
+
+    regions = {}
+    for region_tree in regions_tree:
+        region_id = int(region_tree['user'])
+        columns = region_tree.find_all('td')
+        regions[region_id] = {
+            'name': re.sub('Factories: .*$', '', columns[1].text),
+            'explored': float(columns[2].string),
+            'maximum': int(float(columns[3].string)),
+            'deep_exploration': int(columns[4].string),
+            'limit_left': int(columns[5].string),
+        }
+    return regions
+
+def print_resources(regions):
+    """print resources"""
+    print('region                        expl max   D left    c %    t %')
+    for region in regions.values():
+        region['explored_percentage'] = 100 / region['maximum'] * region['explored']
+        region['total_left'] = region['explored'] + region['limit_left']
+        region['total_percentage'] = 100 / 2500 * region['total_left']
+
+        print('{:25}: {:7.2f}{:4}{:4}{:5}{:7.2f}{:7.2f}'.format(
+            region['name'],
+            region['explored'],
+            region['maximum'],
+            region['deep_exploration'],
+            region['limit_left'],
+            region['explored_percentage'],
+            region['total_percentage'],
+        ))
+
+
+def need_refill(regions):
+    """Check if refill is needed"""
+    for region in regions.values():
+        percentage = 100 / region['maximum'] * region['explored']
+        if percentage < 25 and region['limit_left']:
+            return True
+    return False
+
+
+def refill(capital_id, resource_id):
+    """Main function"""
+    data = {
+        'tmp_gov': resource_id
+    }
+    requests.post(
+        '{}parliament/donew/42/{}/0'.format(BASE_URL, resource_id),
+        headers=HEADERS,
+        data=data
+    )
+
+    response = requests.get(
+        '{}parliament/index/{}'.format(BASE_URL, capital_id),
+        headers=HEADERS
+    )
+    soup = BeautifulSoup(response.text, 'html.parser')
+    active_laws = soup.find('div', {'id': 'parliament_active_laws'})
+    resource_name = RESOURCES[resource_id]
+    exploration_laws = active_laws.findAll(
+        text='Resources exploration: state, {} resources'.format(resource_name)
+    )
+    print('Resources exploration: state, {} resources'.format(resource_name))
+    print(exploration_laws)
+    for exploration_law in exploration_laws:
+        action = exploration_law.parent.parent['action']
+        action = action.replace('law', 'votelaw')
+        result = requests.post(
+            '{}{}/pro'.format(BASE_URL, action),
+            headers=HEADERS
+        )
+        print(result.text)
+
+
+def save_resources(state_id, regions):
+    """Save resources to database"""
+    session = Session()
+    resource_track = ResourceTrack()
+    resource_track.state_id = state_id
+    session.add(resource_track)
+    session.commit()
+
+    for region_id, region in regions.items():
+        resource_stat = ResourceStat()
+        resource_stat.region_id = region_id
+        resource_stat.explored = region['explored']
+        resource_stat.deep_exploration = region['deep_exploration']
+        resource_stat.limit_left = region['limit_left']
+        session.add(resource_stat)
+    session.commit()

+ 83 - 0
hvs/models.py

@@ -0,0 +1,83 @@
+"""Database models"""
+
+import datetime
+
+from sqlalchemy import MetaData, Column, ForeignKey, Integer, String, SmallInteger, DateTime
+from sqlalchemy.orm import relationship, backref
+from sqlalchemy.ext.declarative import declarative_base
+
+
+meta = MetaData(naming_convention={
+    "ix": "ix_%(column_0_label)s",
+    "uq": "uq_%(table_name)s_%(column_0_name)s",
+    "ck": "ck_%(table_name)s_%(constraint_name)s",
+    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
+    "pk": "pk_%(table_name)s"
+})
+Base = declarative_base(metadata=meta)
+
+class Region(Base):
+    """Model for region"""
+    __tablename__ = 'region'
+    id = Column(Integer, primary_key=True)
+    name = Column(String)
+    gold_limit = Column(SmallInteger)
+    oil_limit = Column(SmallInteger)
+    ore_limit = Column(SmallInteger)
+    uranium_limit = Column(SmallInteger)
+    diamond_limit = Column(SmallInteger)
+
+
+class DeepExploration(Base):
+    """Model for deep exploration"""
+    __tablename__ = 'deep_exploration'
+    id = Column(Integer, primary_key=True)
+    date_time_end = Column(DateTime)
+    region_id = Column(Integer)
+    resource_type = Column(SmallInteger)
+    region_id = Column(Integer, ForeignKey('region.id'))
+    region_track = relationship(
+        "Region",
+        backref=backref("deep_explorations", lazy="dynamic")
+    )
+
+
+class ResourceTrack(Base):
+    """Model for resource track"""
+    __tablename__ = 'resource_track'
+    id = Column(Integer, primary_key=True)
+    resource_type = Column(SmallInteger)
+    date_time = Column(DateTime, default=datetime.datetime.utcnow)
+    state_id = Column(Integer, ForeignKey('state.id'))
+    resource_track = relationship(
+        "State",
+        backref=backref("resource_tracks", lazy="dynamic")
+    )
+
+
+class ResourceStat(Base):
+    """Model for resource stat"""
+    __tablename__ = 'resource_stat'
+    id = Column(Integer, primary_key=True)
+    explored = Column(SmallInteger)
+    deep_exploration = Column(SmallInteger)
+    limit_left = Column(SmallInteger)
+
+    resource_track_id = Column(Integer, ForeignKey('resource_track.id'))
+    resource_track = relationship(
+        "ResourceTrack",
+        backref=backref("resource_stats", lazy="dynamic")
+    )
+
+    region_id = Column(Integer, ForeignKey('region.id'))
+    region = relationship(
+        "Region",
+        backref=backref("resource_stats", lazy="dynamic")
+    )
+
+
+class State(Base):
+    """Model for state"""
+    __tablename__ = 'state'
+    id = Column(Integer, primary_key=True)
+    name = Column(String)