MkDocs, Readme, Files API, Automated session saving, v2.0.1

MkDocs: sphinx docstrings rewritten to google, improved config, written the major part of how-to.
Readme: centered title + logo, added badges, features list, updated changelog.

Improved Files API, added automatical session saving and restoring to Client.
Some changes in makefile and gitignore.
License Notice now refers to all contributors.
This commit is contained in:
DarkCat09 2022-08-26 16:14:07 +04:00
parent dc52f92985
commit 4892430f19
39 changed files with 1832 additions and 569 deletions

View file

@ -1 +0,0 @@
Very interesting information

165
docs/howto/auth.md Normal file
View file

@ -0,0 +1,165 @@
# How-To 1: Logging in
## Intro
Firstly, let's install the library using the command from ["Common install" section](../#common).
```bash
pip install python-aternos
```
Also, [register](https://aternos.org/go/) an Aternos account if you haven't one.
Now you are ready.
## Authorization with password
Import python-aternos module:
```python
from python_aternos import Client
```
Then, you can log in to your account using from_credentials method
specifying your username and password.
```python
at = Client.from_credentials('username', 'password')
```
This line will create Client object and save it to `at` variable.
Okay, we are logged in. What's next?
## Servers list
Request the list of your servers:
```python
servers = at.list_servers()
```
This variable must contain something like:
```python
[<python_aternos.atserver.AternosServer object at 0x7f97bd8b5690>]
```
If you have only one server in your account,
get it by the zero index:
```python
serv = servers[0]
```
Otherwise, iterate over the list to find it by IP or subdomain:
```python
# 1st way: For-loop
# Our server: test.aternos.me
# Find by IP (domain)
serv = None
for s in servers:
if s.domain == 'test.aternos.me':
serv = s
# Or find by subdomain
# (part before .aternos.me)
serv = None
for s in servers:
if s.subdomain == 'test':
serv = s
# Important check
if serv is None:
print('Not found!')
exit()
```
```python
# 2nd way: Dict comprehension
serv = {
'serv': s
for s in servers
if s.subdomain == 'test'
}.get('serv', None)
if serv is None:
print('Not found!')
exit()
```
`serv` is an AternosServer object. I'll explain it more detailed in the next part.
Now, let's just try to start and stop server:
```python
# Start
serv.start()
# Stop
serv.stop()
```
## Saving session
In the version `v2.0.1` and above,
python-aternos automatically saves and restores session cookie,
so you don't need to do it by yourself now.
Before, you should save session manually:
```python
# This code is useless in new versions,
# because they do it automatically.
from python_aternos import Client
at = Client.from_credentails('username', 'password')
myserv = at.list_servers()[0]
...
at.save_session()
# Closing python interpreter
# and opening it again
from python_aternos import Client
at = Client.restore_session()
myserv = at.list_servers()[0]
...
```
Function `save_session()` writes session cookie and cached servers list to `.aternos` file in your home directory.
`restore_session()` creates Client object from session cookie and restores servers list.
This feature reduces the count of network requests and allows you to log in and request servers much faster.
If you created a new server, but it doesn't appear in `list_servers` result, call it with `cache=False` argument.
```python
# Refreshing list
servers = at.list_servers(cache=False)
```
## Username, email, password
Change them using the corresponding methods:
```python
at.change_username('new1cool2username3')
at.change_password('old_password', 'new_password')
at.change_email('new@email.com')
```
## Hashing passwords
For security reasons, Aternos API takes MD5 hashed passwords, not plain.
`from_credentials` hashes your credentials and passes to `from_hashed` classmethod.
`change_password` also hashes passwords and calls `change_password_hashed`.
And you can use these methods too.
Python-Aternos contains a handy function `Client.md5encode` that can help you with it.
```python
>>> from python_aternos import Client
>>> Client.md5encode('old_password')
'0512f08120c4fef707bd5e2259c537d0'
>>> Client.md5encode('new_password')
'88162595c58939c4ae0b35f39892e6e7'
```
```python
from python_aternos import Client
my_passwd = '0512f08120c4fef707bd5e2259c537d0'
new_passwd = '88162595c58939c4ae0b35f39892e6e7'
at = Client.from_hashed('username', my_passwd)
at.change_password_hashed(my_passwd, new_passwd)
```

1
docs/howto/config.md Normal file
View file

@ -0,0 +1 @@
## Coming soon

1
docs/howto/discord.md Normal file
View file

@ -0,0 +1 @@
## Coming soon

233
docs/howto/files.md Normal file
View file

@ -0,0 +1,233 @@
# How-To 4: Files
## Intro
In python-aternos, all files on your Minecraft server
are represented as atfile.AternosFile objects.
They can be accessed through atfm.FileManager instance,
let's assign it to `fm` variable:
```python
>>> fm = serv.files()
```
## List directory contents
```python
>>> root = fm.list_dir('/')
[<python_aternos.atfile.AternosFile object at 0x7f1b0...>, ...]
```
## Get file by its path
```python
>>> myfile = fm.get_file('/server.properties')
<python_aternos.atfile.AternosFile object at 0x7f1b0...>
```
## File info
AternosFile object can point to
both a file and a directory
and contain almost the same properties and methods.
(So it's more correct to call it "Object in the server's filesystem",
but I chose an easier name for the class.)
- `path` - Full path to the file **including trailing** slash and **without leading** slash.
- `name` - Filename with extension **without trailing** slash.
- `dirname` - Full path to the directory which contains the file **without leading** slash.
- `is_file` and `is_dir` - File type in boolean.
- `ftype` - File type in `FileType` enum value:
- `FileType.file`
- `FileType.dir` and `FileType.directory`
- `size` - File size in bytes, float.
`0.0` for directories and
`-1.0` when error occures.
- `deleteable`, `downloadable` and `editable` are explained in the next section.
### File
```python
>>> f = root[5]
>>> f.path
'/server.properties'
>>> f.name
'server.properties'
>>> f.dirname
''
>>> f.is_file
False
>>> f.is_dir
True
>>> from python_aternos import FileType
>>> f.ftype == FileType.file
True
>>> f.ftype == FileType.directory
False
>>> f.size
1240.0
>>> f.deleteable
False
>>> f.downloadable
False
>>> f.editable
False
```
### Directory
```python
>>> f = root[2]
>>> f.path
'/config'
>>> f.name
'config'
>>> f.dirname
''
>>> f.is_file
False
>>> f.is_dir
True
>>> from python_aternos import FileType
>>> f.ftype == FileType.file
False
>>> f.ftype == FileType.directory
True
>>> f.ftype == FileType.dir
True
>>> f.size
0.0
>>> f.deleteable
False
>>> f.downloadable
True
>>> f.editable
False
```
## Methods
- `get_text` returns the file content from the Aternos editor page
(opens when you click on the file on web site).
- `set_text` is the same as "Save" button in the Aternos editor.
- `get_content` requests file downloading and
returns file content in `bytes` (not `str`).
If it is a directory, Aternos returns its content in a ZIP file.
- `set_content` like `set_text`, but takes `bytes` as an argument.
- `delete` removes file.
- `create` creates a new file inside this one
(if it's a directory, otherwise throws RuntimeWarning).
### Deletion and downloading rejection
In [Aternos Files tab](https://aternos.org/files),
some files can be removed with a red button, some of them is protected.
You can check if the file is deleteable this way:
```python
>>> f.deleteable
False
```
`delete()` method will warn you if it's undeleteable,
and then you'll probably get `FileError`
because of Aternos deletion denial.
The same thing with `downloadable`.
```python
>>> f.downloadable
True
```
`get_content()` will warn you if it's undownloadable.
And then you'll get `FileError`.
And `editable` means that you can click on the file
in Aternos "Files" tab to open editor.
`get_text()` will warn about editing denial.
### Creating files
Calling `create` method only available for directories
(check it via `f.is_dir`).
It takes two arguments:
- `name` - name of a new file,
- `ftype` - type of a new file, must be `FileType` enum value:
- `FileType.file`
- `FileType.dir` or `FileType.directory`
For example, let's create an empty config
for some Forge mod, I'll call it "testmod".
```python
# Import enum
from python_aternos import FileType
# Get configs directory
conf = fm.get_file('/config')
# Create empty file
conf.create('testmod.toml', FileType.file)
```
### Editing files
Let's edit `ops.json`.
It contains operators nicknames,
so the code below is the same as [Players API](../players/#list-types).
```python
import json
from python_aternos import Client
at = Client.from_credentials('username', 'password')
serv = at.list_servers()[0]
fm = serv.files()
ops = fm.get_file('/ops.json')
# If editable
use_get_text = True
# Check
if not ops.editable:
# One more check
if not ops.downloadable:
print('Error')
exit(0)
# If downloadable
use_get_text = False
def read():
if use_get_text:
return ops.get_text()
else:
return ops.get_content().decode('utf-8')
def write(content):
# set_text and set_content
# uses the same URLs.
# I prefer set_content
# but we need to convert content to bytes
content = content.encode('utf-8')
ops.set_content(content)
# It contains empty list [] by default
oper_raw = read()
# Convert to Python list
oper_lst = json.loads(oper_raw)
# Add an operator
oper_lst.append('DarkCat09')
# Convert back to JSON
oper_new = json.dumps(oper_lst)
# Write
ops.write(oper_new)
```

57
docs/howto/players.md Normal file
View file

@ -0,0 +1,57 @@
# How-To 3: Players lists
You can add a player to operators,
include in the whitelist or ban
using this feature.
## Common usage
It's pretty easy:
```python
from python_aternos import Client, Lists
...
whitelist = serv.players(Lists.whl)
whitelist.add('jeb_')
whitelist.remove('Notch')
whitelist.list_players()
# ['DarkCat09', 'jeb_']
```
## List types
| Name | Enum key |
|:----------:|:---------:|
| Whitelist |`Lists.whl`|
| Operators |`Lists.ops`|
| Banned |`Lists.ban`|
|Banned by IP|`Lists.ips`|
For example, I want to ban someone:
```python
serv.players(Lists.ban).add('someone')
```
And give myself operator rights:
```python
serv.players(Lists.ops).add('DarkCat09')
```
Unban someone:
```python
serv.players(Lists.ban).remove('someone')
```
Unban someone who I banned by IP:
```python
serv.players(Lists.ips).remove('anyone')
```
## Caching
If `list_players` doesn't show added players, call it with `cache=False` argument, like list_servers.
```python
lst = serv.players(Lists.ops)
lst.list_players(cache=False)
# ['DarkCat09', 'jeb_']
```

219
docs/howto/server.md Normal file
View file

@ -0,0 +1,219 @@
# How-To 2: Controlling Minecraft server
In the previous part we logged into account and started a server.
But python-aternos can do much more.
## Basic methods
```python
from python_aternos import Client
at = Client.from_credentials('username', 'password')
serv = at.list_servers()[0]
# Start
serv.start()
# Stop
serv.stop()
# Restart
serv.restart()
# Cancel starting
serv.cancel()
# Confirm starting
# at the end of a queue
serv.confirm()
```
## Starting
### Arguments
`start()` can be called with arguments:
- headstart (bool): Start server in headstart mode
which allows you to skip all queue.
- accepteula (bool): Automatically accept Mojang EULA.
If you want to launch your server instantly, use this code:
```python
serv.start(headstart=True)
```
### Errors
`start()` raises `ServerStartError` if Aternos denies request.
This object contains an error code, on which depends an error message.
- EULA was not accepted (code: `eula`) -
remove `accepteula=False` or run `serv.eula()` before startup.
- Server is already running (code: `already`) -
you don't need to start server, it is online.
- Incorrect software version installed (code: `wrongversion`) -
if you have *somehow* installed non-existent software version (e.g. `Vanilla 2.16.5`).
- File server is unavailable (code: `file`) -
problems in Aternos servers, view [https://status.aternos.gmbh](https://status.aternos.gmbh)
- Available storage size limit has been reached (code: `size`) -
files on your Minecraft server have reached 4GB limit
(for exmaple, too much mods or loaded chunks).
Always wrap `start` into try-catch.
```python
from python_aternos import ServerStartError
...
try:
serv.start()
except ServerStartError as err:
print(err.code) # already
print(err.message) # Server is already running
```
## Cancellation
Server launching can be cancelled only when you are waiting in a queue.
After queue, when the server starts and writes something to the log,
you can just `stop()` it, not `cancel()`.
## Server info
```python
>>> serv.address
'test.aternos.me:15172'
>>> serv.domain
'test.aternos.me'
>>> serv.subdomain
'test'
>>> serv.port
15172
>>> from python_aternos import Edition
>>> serv.edition
0
>>> serv.edition == Edition.java
True
>>> serv.edition == Edition.bedrock
False
>>> serv.software
'Forge'
>>> serv.version
'1.16.5 (36.2.34)'
>>> serv.players_list
['DarkCat09', 'jeb_']
>>> serv.players_count
2
>>> serv.slots
20
>>> print('Online:', serv.players_count, 'of', serv.slots)
Online: 2 of 20
>>> serv.motd
'§7Welcome to the §9Test Server§7!'
>>> from python_aternos import Status
>>> serv.css_class
'online'
>>> serv.status
'online'
>>> serv.status_num
1
>>> serv.status_num == Status.on
True
>>> serv.status_num == Status.off
False
>>> serv.status_num == Status.starting
False
>>> serv.restart()
# Title on web site: "Loading"
>>> serv.css_class
'loading'
>>> serv.status
'loading'
>>> serv.status_num
6
>>> serv.status_num == Status.loading
True
>>> serv.status_num == Status.preparing
False
>>> serv.status_num == Status.starting
False
# Title on web site: "Preparing"
>>> serv.css_class
'loading'
>>> serv.status
'preparing'
>>> serv.status_num
10
>>> serv.status_num == Status.preparing
True
>>> serv.status_num == Status.starting
False
>>> serv.status_num == Status.on
False
# Title on web site: "Starting"
>>> serv.css_class
'loading starting'
>>> serv.status
'starting'
>>> serv.status_num
2
>>> serv.status_num == Status.starting
True
>>> serv.status_num == Status.on
False
>>> serv.ram
2600
```
## Changing subdomain and MOTD
To change server subdomain or Message-of-the-Day,
just assign a new value to the corresponding fields:
```python
serv.subdomain = 'new-test-server123'
serv.motd = 'Welcome to the New Test Server!'
```
## Updating status
python-aternos don't refresh server information by default.
This can be done with [WebSockets API](websocket) automatically
(but it will be explained later in the 6th part of how-to guide),
or with `fetch()` method manually (much easier).
`fetch()` called also when an AternosServer object is created
to get info about the server:
- full address,
- MOTD,
- software,
- connected players,
- status,
- etc.
Use it if you want to see new data one time:
```python
import time
from python_aternos import Client
at = Client.from_credentials('username', 'password')
serv = at.list_servers()[0]
# Start
serv.start()
# Wait 10 sec
time.sleep(10)
# Check
serv.fetch()
print('Server is', serv.status) # Server is online
```
But this method is **not** a good choice if you want to get real-time updates.
Read [How-To 6: Real-time updates](websocket) about WebSockets API
and use it instead of refreshing data in a while-loop.

1
docs/howto/websocket.md Normal file
View file

@ -0,0 +1 @@
## Coming soon

View file

@ -1,4 +1,153 @@
![Python-Aternos Logo](https://i.ibb.co/3RXcXJ1/aternos-400.png)
# Python Aternos
<div align="center">
<img src="https://i.ibb.co/3RXcXJ1/aternos-400.png" alt="Python Aternos Logo">
<h1>
Python Aternos
<div>
<a href="https://pypi.org/project/python-aternos/">
<img src="https://img.shields.io/pypi/v/python-aternos">
</a>
<a href="https://www.apache.org/licenses/LICENSE-2.0.html">
<img src="https://img.shields.io/pypi/l/python-aternos">
</a>
<a href="https://github.com/DarkCat09/python-aternos/commits">
<img src="https://img.shields.io/github/last-commit/DarkCat09/python-aternos">
</a>
<a href="https://github.com/DarkCat09/python-aternos/issues">
<img src="https://img.shields.io/github/issues/DarkCat09/python-aternos">
</a>
</div>
</h1>
</div>
An unofficial Aternos API written in Python.
It uses [aternos](https://aternos.org/)' private API and html parsing.
Python Aternos supports:
- Logging in to account with password (plain or hashed) or `ATERNOS_SESSION` cookie value.
- Saving session to the file and restoring.
- Changing username, email and password.
- Parsing Minecraft servers list.
- Parsing server info by its ID.
- Starting/stoping server, restarting, confirming/cancelling launch.
- Updating server info in real-time (view WebSocket API).
- Changing server subdomain and MOTD (message-of-the-day).
- Managing files, settings, players (whitelist, operators, etc.)
> **Warning**
>
> According to the Aternos' [Terms of Service §5.2e](https://aternos.gmbh/en/aternos/terms#:~:text=Automatically%20accessing%20our%20website%20or%20automating%20actions%20on%20our%20website.),
> you must not use any software or APIs for automated access,
> beacuse they don't receive money from advertisting in this case.
>
> I always try to hide automated python-aternos requests
> using browser-specific headers/cookies,
> but you should make backups to restore your world
> if your account will be banned
> (view [#16](https://github.com/DarkCat09/python-aternos/issues/16)
> and [#46](https://github.com/DarkCat09/python-aternos/issues/46)).
## Install
### Common
```bash
$ pip install python-aternos
```
> **Note** for Windows users
>
> Install `lxml` package from [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml)
> if you have problems with it, and then execute
> `pip install --no-deps python-aternos`
### Development
```bash
$ git clone https://github.com/DarkCat09/python-aternos.git
$ cd python-aternos
$ pip install -e .
```
## Usage
To use Aternos API in your Python script, import it
and login with your username and password or MD5.
Then request the servers list using `list_servers()`.
You can start/stop your Aternos server, calling `start()` or `stop()`.
Here is an example how to use the API:
```python
# Import
from python_aternos import Client
# Log in
aternos = Client.from_credentials('example', 'test123')
# ----OR----
aternos = Client.from_hashed('example', 'cc03e747a6afbbcbf8be7668acfebee5')
# ----OR----
aternos = Client.restore_session()
# Returns AternosServer list
servs = aternos.list_servers()
# Get the first server by the 0 index
myserv = servs[0]
# Start
myserv.start()
# Stop
myserv.stop()
# You can also find server by IP
testserv = None
for serv in servs:
if serv.address == 'test.aternos.org':
testserv = serv
if testserv is not None:
# Prints a server softaware and its version
# (for example, "Vanilla 1.12.2")
print(testserv.software, testserv.version)
# Starts server
testserv.start()
```
## [More examples](https://github.com/DarkCat09/python-aternos/tree/main/examples)
## [Documentation](https://darkcat09.codeberg.page/aternos-docs/)
## [How-To Guide](https://darkcat09.codeberg.page/aternos-docs/howto/auth)
## Changelog
|Version|Description |
|:-----:|:-----------|
|v0.1|The first release.|
|v0.2|Fixed import problem.|
|v0.3|Implemented files API, added typization.|
|v0.4|Implemented configuration API, some bugfixes.|
|v0.5|The API was updated corresponding to new Aternos security methods. Huge thanks to [lusm554](https://github.com/lusm554).|
|**v0.6/v1.0.0**|Code refactoring, websockets API and session saving to prevent detecting automation access.|
|v1.0.x|Lots of bugfixes, changed versioning (SemVer).|
|v1.1.x|Documentation, unit tests, pylint, bugfixes, changes in atwss.|
|**v1.1.2/v2.0.0**|Solution for [#25](https://github.com/DarkCat09/python-aternos/issues/25) (Cloudflare bypassing), bugfixes in JS parser.|
|v2.0.x|Documentation, automatically saving/restoring session, improvements in Files API.|
|v2.1.x|Fixes in the implementation of websockets API.|
|**v2.2.x**|Using Node.js as a JS interpreter if it's installed.|
|v3.0.x|Full implementation of config and software API.|
|v3.1.x|Shared access API and Google Drive backups.|
## License
[License Notice](NOTICE):
```
Copyright 2021-2022 All contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```

View file

@ -1,39 +0,0 @@
::: python_aternos.atclient
options:
show_source: false
::: python_aternos.atserver
options:
show_source: false
::: python_aternos.atplayers
options:
show_source: false
::: python_aternos.atconf
options:
show_source: false
::: python_aternos.atfm
options:
show_source: false
::: python_aternos.atfile
options:
show_source: false
::: python_aternos.atwss
options:
show_source: false
::: python_aternos.atconnect
options:
show_source: false
::: python_aternos.atjsparse
options:
show_source: false
::: python_aternos.aterrors
options:
show_source: false

View file

@ -0,0 +1,2 @@
## `atclient` (Entry point)
### ::: python_aternos.atclient

2
docs/reference/atconf.md Normal file
View file

@ -0,0 +1,2 @@
## atconf
### ::: python_aternos.atconf

View file

@ -0,0 +1,2 @@
## atconnect
### ::: python_aternos.atconnect

View file

@ -0,0 +1,2 @@
## aterrors
### ::: python_aternos.aterrors

2
docs/reference/atfile.md Normal file
View file

@ -0,0 +1,2 @@
## atfile
### ::: python_aternos.atfile

2
docs/reference/atfm.md Normal file
View file

@ -0,0 +1,2 @@
## atfm
### ::: python_aternos.atfm

View file

@ -0,0 +1,2 @@
## atjsparse
### ::: python_aternos.atjsparse

View file

@ -0,0 +1,2 @@
## `atplayers`
### ::: python_aternos.atplayers

View file

@ -0,0 +1,2 @@
## `atserver`
### ::: python_aternos.atserver

2
docs/reference/atwss.md Normal file
View file

@ -0,0 +1,2 @@
## atwss
### ::: python_aternos.atwss