Skip to content

Commit

Permalink
add readme to setup
Browse files Browse the repository at this point in the history
  • Loading branch information
NotAName320 committed Mar 25, 2024
1 parent 398216d commit abe7c99
Showing 5 changed files with 180 additions and 32 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -7,3 +7,6 @@

# Python egg metadata, regenerated from source files by setuptools.
/*.egg-info

tester.py
*.log
24 changes: 10 additions & 14 deletions example.py
Original file line number Diff line number Diff line change
@@ -18,31 +18,27 @@

import asyncio

from yatgl import Client, Template


async def add_some_tgs():
while True:
print('inserted 3 more telegrams into queue')
Client().queue_tg(Template('secret key', 'tgid'), '1')
Client().queue_tg(Template('secret key', 'tgid'), '2')
Client().queue_tg(Template('secret key', 'tgid'), '3')
await asyncio.sleep(500)
from yatgl import Client, NationGroup, Template


async def main():
# Lazy initialization with singleton
Client(client_key='client key here')
Client(client_key='client key here', user_agent='nation here')

# If you want, you can change the delay, like so:
# Client(delay=200)

# Queue some telegrams
Client().queue_tg(Template('secret key', 'tgid'), 'nation here')
# You can queue telegrams manually...
Client().queue_tg(Template('secret key', 'tgid'), 'nation here')

try:
await asyncio.gather(Client().start(), add_some_tgs())
# ...or you can use a mass telegram function
await Client().mass_telegram(Template('secret key', 'tgid'), NationGroup.NEW_FOUNDS)

# or you can use multiple with asyncio.gather e.g.
# func1 = Client().mass_telegram(Template('secret key', 'tgid'), NationGroup.NEW_FOUNDS)
# func2 = Client().mass_telegram(Template('secret key', 'tgid'), NationGroup.NEW_REGION_MEMBERS, 'testregionia')
# await asyncio.gather(func1, func2)
except KeyboardInterrupt:
await Client().stop()

7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from setuptools import setup
@@ -24,7 +23,7 @@


setup(name='yatgl',
version='0.0.2',
version='1.0.0',
description='An asynchronous NationStates Telegram API library.',
long_description=long_description,
long_description_content_type='text/markdown',
@@ -33,6 +32,8 @@
license='GPLv3',
packages=['yatgl'],
install_requires=[
'aiohttp[speedups]'
'aiohttp[speedups]',
'beautifulsoup4',
'lxml'
],
zip_safe=False)
3 changes: 1 addition & 2 deletions yatgl/__init__.py
Original file line number Diff line number Diff line change
@@ -14,7 +14,6 @@
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""

from .client import Client, Template
from .client import Client, NationGroup, Template
175 changes: 162 additions & 13 deletions yatgl/client.py
Original file line number Diff line number Diff line change
@@ -17,11 +17,17 @@
"""

import asyncio
from logging import getLogger
from collections import deque
from collections.abc import Iterable
from enum import Enum
from logging import getLogger
from typing import NamedTuple

import aiohttp
from bs4 import BeautifulSoup

API_URL = 'https://www.nationstates.net/cgi-bin/api.cgi'
VERSION = '0.0.3'


logger = getLogger(__name__)
@@ -37,6 +43,15 @@ class TelegramRequest(NamedTuple):
recipient: str


class NationGroup(Enum):
NEW_WA_MEMBERS = 0
ALL_WA_MEMBERS = 1
NEW_FOUNDS = 2
ALL_WA_DELEGATES = 3
NEW_REGION_MEMBERS = 4
ALL_REGION_MEMBERS = 5


class _ClientMeta(type):
"""
Singleton metaclass for Client, making sure that two action queues never exist at once
@@ -69,11 +84,13 @@ class Client(metaclass=_ClientMeta):
>>>asyncio.run(Client().start())
"""
client_key: str = None
user_agent: str = None
delay: int = 185
sent = set()
queue: deque[TelegramRequest] = deque()
_task = None
_session: aiohttp.ClientSession
_tg_task = None
_queueing_task = None
_session: aiohttp.ClientSession = None

def __init__(self, **kwargs):
"""
@@ -84,6 +101,8 @@ def __init__(self, **kwargs):
"""
if 'client_key' in kwargs:
self.client_key = kwargs.pop('client_key')
if 'user_agent' in kwargs:
self.user_agent = kwargs.pop('user_agent')
if 'delay' in kwargs:
delay = kwargs.pop('delay')
if delay < 30:
@@ -106,19 +125,145 @@ async def start(self):
"""
if not self.client_key:
raise AttributeError('No client key provided.')
if not self._task:
self._task = asyncio.create_task(self._process_stack())
self._session = aiohttp.ClientSession()
await self._task
if not self.user_agent:
raise AttributeError('Please set a User Agent.')
if not self._tg_task:
self._tg_task = asyncio.create_task(self._process_stack())
if not self._session or self._session.closed:
self._session = aiohttp.ClientSession()
await self._tg_task

async def stop(self):
"""
Stops sending telegrams if the client has started, otherwise does nothing.
Stops sending telegrams and/or queueing if the client has started, otherwise does nothing.
"""
if self._task:
self._task.cancel()
if self._tg_task:
self._tg_task.cancel(), self._queueing_task.cancel()
await self._session.close()
self._task = None
self._tg_task, self._queueing_task = None, None

async def mass_telegram(self, template: Template, group: NationGroup, region: Iterable[str] = None):
"""
Starts the telegram queue while autoqueueing a certain group of nations using the API.
Ensure that a client key has been provided.
Note that when getting these nations, the client ignores ratelimits, which should be fine for most cases as
the requests are sparse enough that they're well under, but might break e.g. if targeting nations joining one of
50 regions, in which it may be time to reevaluate your region's foreign policy.
:param template: The template to send to the nations.
:param group: The group of nations to target specified by the enum :class:`NationGroup`.
:param region: A list of regions.
"""
if not self._session or self._session.closed:
self._session = aiohttp.ClientSession()
self._queueing_task = asyncio.create_task(self._mass_queue(template, group, region))
await asyncio.gather(self.start(), self._queueing_task)

async def _mass_queue(self, template: Template, group: NationGroup, regions: str | Iterable[str] | None):
if group in {NationGroup.ALL_REGION_MEMBERS, NationGroup.NEW_REGION_MEMBERS} and not regions:
raise AttributeError('Region(s) not provided to client.')
elif isinstance(regions, str):
regions = [regions]

# why did i code it like this?
if group.value % 2 == 1:
# here's where i throw good software principles out the book in favor of huge ass switch statements
match group:
case NationGroup.ALL_REGION_MEMBERS:
for region in regions:
for nation in await self._get_region_members(region):
self.queue_tg(template, nation)

case NationGroup.ALL_WA_MEMBERS:
for nation in await self._get_wa_members():
self.queue_tg(template, nation)

case NationGroup.ALL_WA_DELEGATES:
for nation in await self._get_wa_delegates():
self.queue_tg(template, nation)
else:
# generate a list of nations to not send messages to
existing = set()
if group is NationGroup.NEW_REGION_MEMBERS:
for region in regions:
existing.update(await self._get_region_members(region))
elif group is NationGroup.NEW_WA_MEMBERS:
existing = set(await self._get_wa_members())

while True:
match group:
case NationGroup.NEW_REGION_MEMBERS:
for region in regions:
for nation in await self._get_region_members(region):
if nation not in existing:
self.queue_tg(template, nation)
existing.add(nation)

case NationGroup.NEW_WA_MEMBERS:
for member in await self._get_wa_members():
if member not in existing:
self.queue_tg(template, member)
existing.add(member)

case NationGroup.NEW_FOUNDS:
for nation in await self._get_new_founds():
if nation not in existing:
self.queue_tg(template, nation)
existing.add(nation)

await asyncio.sleep(60)

async def _get_region_members(self, region: str) -> list[str]:
data = {
'q': 'nations',
'region': region
}
headers = {
'User-Agent': f'yatgl v{VERSION} Developed by nation=Notanam, used by nation={self.user_agent}'
}

async with self._session.post(API_URL, data=data, headers=headers) as resp:
parsed = BeautifulSoup(await resp.text(), 'xml')
return parsed.REGION.NATIONS.string.split(':')

async def _get_wa_members(self) -> list[str]:
data = {
'q': 'members',
'wa': '1'
}
headers = {
'User-Agent': f'yatgl v{VERSION} Developed by nation=Notanam, used by nation={self.user_agent}'
}

async with self._session.post(API_URL, data=data, headers=headers) as resp:
parsed = BeautifulSoup(await resp.text(), 'xml')
return parsed.WA.MEMBERS.string.split(',')

async def _get_wa_delegates(self) -> list[str]:
data = {
'q': 'delegates',
'wa': '1'
}
headers = {
'User-Agent': f'yatgl v{VERSION} Developed by nation=Notanam, used by nation={self.user_agent}'
}

async with self._session.post(API_URL, data=data, headers=headers) as resp:
parsed = BeautifulSoup(await resp.text(), 'xml')
return parsed.WA.DELEGATES.string.split(',')

async def _get_new_founds(self) -> list[str]:
data = {
'q': 'newnations'
}
headers = {
'User-Agent': f'yatgl v{VERSION} Developed by nation=Notanam, used by nation={self.user_agent}'
}

async with self._session.post(API_URL, data=data, headers=headers) as resp:
parsed = BeautifulSoup(await resp.text(), 'xml')
return parsed.WORLD.NEWNATIONS.string.split(',')

async def _process_stack(self):
while True:
@@ -137,8 +282,12 @@ async def _send_tg(self, telegram: TelegramRequest):
'key': telegram.template.secret_key,
'to': recipient
}
async with self._session.post('https://www.nationstates.net/cgi-bin/api.cgi', data=data) as resp:
if await resp.text() == 'queued':
headers = {
'User-Agent': f'yatgl v{VERSION} Developed by nation=Notanam, used by nation={self.user_agent}'
}

async with self._session.post(API_URL, data=data, headers=headers) as resp:
if 'queued' in await resp.text():
self.sent.add(recipient)
logger.info(f'Sent {telegram.template.tgid} to {telegram.recipient}')
else:

0 comments on commit abe7c99

Please sign in to comment.