zucaro 0.5.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zucaro-0.5.1/.git-blame-ignore-revs +2 -0
- zucaro-0.5.1/.github/workflows/pypi.yml +57 -0
- zucaro-0.5.1/.gitignore +10 -0
- zucaro-0.5.1/LICENSE +22 -0
- zucaro-0.5.1/PKG-INFO +59 -0
- zucaro-0.5.1/README.md +44 -0
- zucaro-0.5.1/pyproject.toml +37 -0
- zucaro-0.5.1/requirements-dev.lock +30 -0
- zucaro-0.5.1/requirements.lock +30 -0
- zucaro-0.5.1/src/zucaro/__init__.py +16 -0
- zucaro-0.5.1/src/zucaro/__main__.py +6 -0
- zucaro-0.5.1/src/zucaro/account.py +232 -0
- zucaro-0.5.1/src/zucaro/cli/__init__.py +14 -0
- zucaro-0.5.1/src/zucaro/cli/account.py +127 -0
- zucaro-0.5.1/src/zucaro/cli/config.py +56 -0
- zucaro-0.5.1/src/zucaro/cli/instance.py +200 -0
- zucaro-0.5.1/src/zucaro/cli/main.py +62 -0
- zucaro-0.5.1/src/zucaro/cli/mod.py +57 -0
- zucaro-0.5.1/src/zucaro/cli/play.py +68 -0
- zucaro-0.5.1/src/zucaro/cli/utils.py +40 -0
- zucaro-0.5.1/src/zucaro/cli/version.py +85 -0
- zucaro-0.5.1/src/zucaro/config.py +135 -0
- zucaro-0.5.1/src/zucaro/downloader.py +163 -0
- zucaro-0.5.1/src/zucaro/errors.py +28 -0
- zucaro-0.5.1/src/zucaro/instance.py +281 -0
- zucaro-0.5.1/src/zucaro/java/SysDump.class +0 -0
- zucaro-0.5.1/src/zucaro/java/__init__.py +122 -0
- zucaro-0.5.1/src/zucaro/java_manager.py +166 -0
- zucaro-0.5.1/src/zucaro/launcher.py +116 -0
- zucaro-0.5.1/src/zucaro/library.py +159 -0
- zucaro-0.5.1/src/zucaro/logging.py +16 -0
- zucaro-0.5.1/src/zucaro/mod/__init__.py +4 -0
- zucaro-0.5.1/src/zucaro/mod/curse.py +257 -0
- zucaro-0.5.1/src/zucaro/mod/fabric.py +161 -0
- zucaro-0.5.1/src/zucaro/mod/forge.py +413 -0
- zucaro-0.5.1/src/zucaro/mod/ftb.py +159 -0
- zucaro-0.5.1/src/zucaro/mod/modrinth.py +278 -0
- zucaro-0.5.1/src/zucaro/mod/quilt.py +177 -0
- zucaro-0.5.1/src/zucaro/msapi.py +177 -0
- zucaro-0.5.1/src/zucaro/osinfo.py +39 -0
- zucaro-0.5.1/src/zucaro/rules.py +45 -0
- zucaro-0.5.1/src/zucaro/utils.py +63 -0
- zucaro-0.5.1/src/zucaro/version.py +422 -0
- zucaro-0.5.1/src/zucaro/windows.py +75 -0
- zucaro-0.5.1/src/zucaro/yggdrasil.py +75 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
name: Try to upload Python Package
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
id-token: write
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
release-build:
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.x"
|
|
21
|
+
|
|
22
|
+
- name: Python Setup Rye
|
|
23
|
+
uses: eifinger/setup-rye@v4.2.9
|
|
24
|
+
# optionally specify version:
|
|
25
|
+
# with:
|
|
26
|
+
# version: "0.44.0"
|
|
27
|
+
|
|
28
|
+
- name: Build release distributions with Rye
|
|
29
|
+
run: rye build
|
|
30
|
+
|
|
31
|
+
- name: Upload distributions
|
|
32
|
+
uses: actions/upload-artifact@v4
|
|
33
|
+
with:
|
|
34
|
+
name: release-dists
|
|
35
|
+
path: dist/
|
|
36
|
+
|
|
37
|
+
pypi-publish:
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
needs: release-build
|
|
40
|
+
permissions:
|
|
41
|
+
id-token: write
|
|
42
|
+
|
|
43
|
+
environment:
|
|
44
|
+
name: pypi
|
|
45
|
+
url: https://pypi.org/project/zucaro/${{ github.event.release.name }}
|
|
46
|
+
|
|
47
|
+
steps:
|
|
48
|
+
- name: Retrieve release distributions
|
|
49
|
+
uses: actions/download-artifact@v4
|
|
50
|
+
with:
|
|
51
|
+
name: release-dists
|
|
52
|
+
path: dist/
|
|
53
|
+
|
|
54
|
+
- name: Publish release distributions to PyPI
|
|
55
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
56
|
+
with:
|
|
57
|
+
packages-dir: dist/
|
zucaro-0.5.1/.gitignore
ADDED
zucaro-0.5.1/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2017-2024 Samuel Čavoj
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
zucaro-0.5.1/PKG-INFO
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zucaro
|
|
3
|
+
Version: 0.5.1
|
|
4
|
+
Summary: A very small CLI Minecraft launcher.
|
|
5
|
+
Author-email: Samuel Čavoj <samuel@cavoj.net>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.8
|
|
8
|
+
Requires-Dist: aiohttp==3.11.12
|
|
9
|
+
Requires-Dist: click~=8.1
|
|
10
|
+
Requires-Dist: colorama~=0.4
|
|
11
|
+
Requires-Dist: coloredlogs~=15.0
|
|
12
|
+
Requires-Dist: requests~=2.31
|
|
13
|
+
Requires-Dist: tqdm~=4.66
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# hey
|
|
17
|
+
|
|
18
|
+
This is a fork thats made to work as the backend of the picodulce launcher, this is a work in progress and a lot of things will change
|
|
19
|
+
|
|
20
|
+
this is a fork of the amazing work of [sammko](https://github.com/sammko/picomc)
|
|
21
|
+
|
|
22
|
+
zucaro
|
|
23
|
+
====
|
|
24
|
+
|
|
25
|
+
`zucaro` is a cross-platform command-line Minecraft launcher. It supports
|
|
26
|
+
all(?) officialy available Minecraft versions, account switching and
|
|
27
|
+
multiple separate instances of the game. The on-disk launcher file
|
|
28
|
+
structure mimics the vanilla launcher and as such most mod installers
|
|
29
|
+
(such as forge, fabric or optifine) should work with zucaro just fine,
|
|
30
|
+
though you will have to change the installation path.
|
|
31
|
+
Don't hesitate to report any problems you run into.
|
|
32
|
+
|
|
33
|
+
Usage
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
The quickest way to get started is to run
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
zucaro play
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
which, on the first launch, will ask you for your account details,
|
|
43
|
+
create an instance named `default` using the latest version of Minecraft
|
|
44
|
+
and launch it.
|
|
45
|
+
|
|
46
|
+
Of course, more advanced features are available. Try running
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
zucaro --help
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
and you should be able to figure it out. More detailed documentation
|
|
53
|
+
may appear someday in the future.
|
|
54
|
+
|
|
55
|
+
Development
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
For project management and dependency tracking `zucaro` uses
|
|
59
|
+
[Rye](https://rye-up.com/).
|
zucaro-0.5.1/README.md
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# hey
|
|
2
|
+
|
|
3
|
+
This is a fork thats made to work as the backend of the picodulce launcher, this is a work in progress and a lot of things will change
|
|
4
|
+
|
|
5
|
+
this is a fork of the amazing work of [sammko](https://github.com/sammko/picomc)
|
|
6
|
+
|
|
7
|
+
zucaro
|
|
8
|
+
====
|
|
9
|
+
|
|
10
|
+
`zucaro` is a cross-platform command-line Minecraft launcher. It supports
|
|
11
|
+
all(?) officialy available Minecraft versions, account switching and
|
|
12
|
+
multiple separate instances of the game. The on-disk launcher file
|
|
13
|
+
structure mimics the vanilla launcher and as such most mod installers
|
|
14
|
+
(such as forge, fabric or optifine) should work with zucaro just fine,
|
|
15
|
+
though you will have to change the installation path.
|
|
16
|
+
Don't hesitate to report any problems you run into.
|
|
17
|
+
|
|
18
|
+
Usage
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
The quickest way to get started is to run
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
zucaro play
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
which, on the first launch, will ask you for your account details,
|
|
28
|
+
create an instance named `default` using the latest version of Minecraft
|
|
29
|
+
and launch it.
|
|
30
|
+
|
|
31
|
+
Of course, more advanced features are available. Try running
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
zucaro --help
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
and you should be able to figure it out. More detailed documentation
|
|
38
|
+
may appear someday in the future.
|
|
39
|
+
|
|
40
|
+
Development
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
For project management and dependency tracking `zucaro` uses
|
|
44
|
+
[Rye](https://rye-up.com/).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zucaro"
|
|
3
|
+
version = "0.5.1"
|
|
4
|
+
description = "A very small CLI Minecraft launcher."
|
|
5
|
+
authors = [
|
|
6
|
+
{ name = "Samuel Čavoj", email = "samuel@cavoj.net" }
|
|
7
|
+
]
|
|
8
|
+
dependencies = [
|
|
9
|
+
"click~=8.1",
|
|
10
|
+
"coloredlogs~=15.0",
|
|
11
|
+
"colorama~=0.4",
|
|
12
|
+
"requests~=2.31",
|
|
13
|
+
"tqdm~=4.66",
|
|
14
|
+
"aiohttp==3.11.12",
|
|
15
|
+
]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
requires-python = ">= 3.8"
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
zucaro = "zucaro:main"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
25
|
+
|
|
26
|
+
[tool.rye]
|
|
27
|
+
managed = true
|
|
28
|
+
dev-dependencies = []
|
|
29
|
+
|
|
30
|
+
[tool.hatch.metadata]
|
|
31
|
+
allow-direct-references = true
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/zucaro"]
|
|
35
|
+
|
|
36
|
+
[tool.ruff.lint]
|
|
37
|
+
extend-select = ["I", "W"]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# generated by rye
|
|
2
|
+
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
+
#
|
|
4
|
+
# last locked with the following flags:
|
|
5
|
+
# pre: false
|
|
6
|
+
# features: []
|
|
7
|
+
# all-features: false
|
|
8
|
+
# with-sources: false
|
|
9
|
+
|
|
10
|
+
-e file:.
|
|
11
|
+
certifi==2024.2.2
|
|
12
|
+
# via requests
|
|
13
|
+
charset-normalizer==3.3.2
|
|
14
|
+
# via requests
|
|
15
|
+
click==8.1.7
|
|
16
|
+
# via zucaro
|
|
17
|
+
colorama==0.4.6
|
|
18
|
+
# via zucaro
|
|
19
|
+
coloredlogs==15.0.1
|
|
20
|
+
# via zucaro
|
|
21
|
+
humanfriendly==10.0
|
|
22
|
+
# via coloredlogs
|
|
23
|
+
idna==3.7
|
|
24
|
+
# via requests
|
|
25
|
+
requests==2.31.0
|
|
26
|
+
# via zucaro
|
|
27
|
+
tqdm==4.66.2
|
|
28
|
+
# via zucaro
|
|
29
|
+
urllib3==2.2.1
|
|
30
|
+
# via requests
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# generated by rye
|
|
2
|
+
# use `rye lock` or `rye sync` to update this lockfile
|
|
3
|
+
#
|
|
4
|
+
# last locked with the following flags:
|
|
5
|
+
# pre: false
|
|
6
|
+
# features: []
|
|
7
|
+
# all-features: false
|
|
8
|
+
# with-sources: false
|
|
9
|
+
|
|
10
|
+
-e file:.
|
|
11
|
+
certifi==2024.2.2
|
|
12
|
+
# via requests
|
|
13
|
+
charset-normalizer==3.3.2
|
|
14
|
+
# via requests
|
|
15
|
+
click==8.1.7
|
|
16
|
+
# via zucaro
|
|
17
|
+
colorama==0.4.6
|
|
18
|
+
# via zucaro
|
|
19
|
+
coloredlogs==15.0.1
|
|
20
|
+
# via zucaro
|
|
21
|
+
humanfriendly==10.0
|
|
22
|
+
# via coloredlogs
|
|
23
|
+
idna==3.7
|
|
24
|
+
# via requests
|
|
25
|
+
requests==2.31.0
|
|
26
|
+
# via zucaro
|
|
27
|
+
tqdm==4.66.2
|
|
28
|
+
# via zucaro
|
|
29
|
+
urllib3==2.2.1
|
|
30
|
+
# via requests
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
|
|
3
|
+
from zucaro.cli import zucaro_cli
|
|
4
|
+
|
|
5
|
+
MINPYVERSION = (3, 7, 0)
|
|
6
|
+
|
|
7
|
+
if sys.version_info < MINPYVERSION:
|
|
8
|
+
print(
|
|
9
|
+
"zucaro requires at least Python version "
|
|
10
|
+
"{}.{}.{}. You are using {}.{}.{}.".format(*MINPYVERSION, *sys.version_info)
|
|
11
|
+
)
|
|
12
|
+
sys.exit(1)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
zucaro_cli()
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
import aiohttp
|
|
3
|
+
import asyncio
|
|
4
|
+
from zucaro.errors import RefreshError, ValidationError
|
|
5
|
+
from zucaro.logging import logger
|
|
6
|
+
from zucaro.msapi import MicrosoftAuthApi
|
|
7
|
+
from zucaro.yggdrasil import MojangYggdrasil
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class NAMESPACE_NULL:
|
|
11
|
+
bytes = b""
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def generate_client_token():
|
|
15
|
+
# Any random string, this matches the behavior of the official launcher.
|
|
16
|
+
return str(uuid.uuid4().hex)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Account:
|
|
20
|
+
def __init__(self, **kwargs):
|
|
21
|
+
self.__dict__.update(self.DEFAULTS)
|
|
22
|
+
self.__dict__.update(kwargs)
|
|
23
|
+
|
|
24
|
+
def __repr__(self):
|
|
25
|
+
return self.name
|
|
26
|
+
|
|
27
|
+
def to_dict(self):
|
|
28
|
+
return {k: getattr(self, k) for k in self.DEFAULTS.keys()}
|
|
29
|
+
|
|
30
|
+
def save(self):
|
|
31
|
+
self._am.save(self)
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def from_config(cls, am, name, config):
|
|
35
|
+
if config.get("microsoft", False):
|
|
36
|
+
c = MicrosoftAccount
|
|
37
|
+
elif config.get("online", False):
|
|
38
|
+
c = OnlineAccount
|
|
39
|
+
else:
|
|
40
|
+
c = OfflineAccount
|
|
41
|
+
return c(name=name, _am=am, **config)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class OfflineAccount(Account):
|
|
45
|
+
DEFAULTS = {"uuid": "-", "online": False}
|
|
46
|
+
access_token = "-"
|
|
47
|
+
|
|
48
|
+
@classmethod
|
|
49
|
+
def new(cls, am, name):
|
|
50
|
+
u = uuid.uuid3(NAMESPACE_NULL, "OfflinePlayer:{}".format(name)).hex
|
|
51
|
+
return cls(name=name, uuid=u, _am=am)
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def gname(self):
|
|
55
|
+
return self.name
|
|
56
|
+
|
|
57
|
+
async def refresh(self):
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def can_launch_game(self):
|
|
61
|
+
return True
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class OnlineAccount(Account):
|
|
65
|
+
DEFAULTS = {
|
|
66
|
+
"uuid": "-",
|
|
67
|
+
"online": True,
|
|
68
|
+
"gname": "-",
|
|
69
|
+
"access_token": "-",
|
|
70
|
+
"is_authenticated": False,
|
|
71
|
+
"username": "-",
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fresh = False
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
def new(cls, am, name, username):
|
|
78
|
+
return cls(name=name, username=username, _am=am)
|
|
79
|
+
|
|
80
|
+
async def validate(self):
|
|
81
|
+
r = await self._am.yggdrasil.validate(self.access_token)
|
|
82
|
+
if r:
|
|
83
|
+
self.fresh = True
|
|
84
|
+
return r
|
|
85
|
+
|
|
86
|
+
async def refresh(self, force=False):
|
|
87
|
+
if self.fresh and not force:
|
|
88
|
+
return False
|
|
89
|
+
if self.is_authenticated:
|
|
90
|
+
if await self.validate():
|
|
91
|
+
return
|
|
92
|
+
else:
|
|
93
|
+
try:
|
|
94
|
+
refresh = await self._am.yggdrasil.refresh(self.access_token)
|
|
95
|
+
self.access_token, self.uuid, self.gname = refresh
|
|
96
|
+
self.fresh = True
|
|
97
|
+
return True
|
|
98
|
+
except RefreshError as e:
|
|
99
|
+
logger.error(
|
|
100
|
+
"Failed to refresh access_token," " please authenticate again."
|
|
101
|
+
)
|
|
102
|
+
self.is_authenticated = False
|
|
103
|
+
raise e
|
|
104
|
+
finally:
|
|
105
|
+
self.save()
|
|
106
|
+
else:
|
|
107
|
+
raise AccountError("Not authenticated.")
|
|
108
|
+
|
|
109
|
+
async def authenticate(self, password):
|
|
110
|
+
self.access_token, self.uuid, self.gname = await self._am.yggdrasil.authenticate(
|
|
111
|
+
self.username, password
|
|
112
|
+
)
|
|
113
|
+
self.is_authenticated = True
|
|
114
|
+
self.fresh = True
|
|
115
|
+
self.save()
|
|
116
|
+
|
|
117
|
+
def can_launch_game(self):
|
|
118
|
+
return self.is_authenticated
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class MicrosoftAccount(Account):
|
|
122
|
+
DEFAULTS = {
|
|
123
|
+
"uuid": "-",
|
|
124
|
+
"online": True,
|
|
125
|
+
"microsoft": True,
|
|
126
|
+
"gname": "-",
|
|
127
|
+
"access_token": "-",
|
|
128
|
+
"refresh_token": "-",
|
|
129
|
+
"is_authenticated": False,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def new(cls, am, name):
|
|
134
|
+
return cls(name=name, _am=am)
|
|
135
|
+
|
|
136
|
+
async def refresh(self, force=False):
|
|
137
|
+
if not self.is_authenticated:
|
|
138
|
+
raise RefreshError("Account is not authenticated, cannot refresh")
|
|
139
|
+
try:
|
|
140
|
+
valid = await self._am.msapi.validate(self.access_token)
|
|
141
|
+
except ValidationError as e:
|
|
142
|
+
raise RefreshError(e)
|
|
143
|
+
if valid:
|
|
144
|
+
logger.debug("msa: token still valid")
|
|
145
|
+
return False
|
|
146
|
+
else:
|
|
147
|
+
logger.debug("msa: token not valid anymore, refreshing")
|
|
148
|
+
self.access_token, self.refresh_token = await self._am.msapi.refresh(
|
|
149
|
+
self.refresh_token
|
|
150
|
+
)
|
|
151
|
+
self.save()
|
|
152
|
+
return True
|
|
153
|
+
|
|
154
|
+
async def authenticate(self):
|
|
155
|
+
self.access_token, self.refresh_token = await self._am.msapi.authenticate()
|
|
156
|
+
profile = await self._am.msapi.get_profile(self.access_token)
|
|
157
|
+
self.gname = profile["name"]
|
|
158
|
+
self.uuid = profile["id"]
|
|
159
|
+
self.is_authenticated = True
|
|
160
|
+
self.save()
|
|
161
|
+
|
|
162
|
+
def can_launch_game(self):
|
|
163
|
+
return self.is_authenticated
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class AccountError(ValueError):
|
|
167
|
+
def __str__(self):
|
|
168
|
+
return " ".join(self.args)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
DEFAULT_CONFIG = {
|
|
172
|
+
"default": None,
|
|
173
|
+
"accounts": {},
|
|
174
|
+
"client_token": generate_client_token(),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class AccountManager:
|
|
179
|
+
CONFIG_FILE = "accounts.json"
|
|
180
|
+
|
|
181
|
+
def __init__(self, launcher):
|
|
182
|
+
self.config = launcher.config_manager.get(self.CONFIG_FILE, init=DEFAULT_CONFIG)
|
|
183
|
+
self.yggdrasil = MojangYggdrasil(self.config["client_token"])
|
|
184
|
+
self.msapi = MicrosoftAuthApi()
|
|
185
|
+
|
|
186
|
+
def list(self):
|
|
187
|
+
return self.config["accounts"].keys()
|
|
188
|
+
|
|
189
|
+
def get(self, name):
|
|
190
|
+
try:
|
|
191
|
+
acc = Account.from_config(self, name, self.config["accounts"][name])
|
|
192
|
+
acc.is_default = self.config["default"] == name
|
|
193
|
+
return acc
|
|
194
|
+
except KeyError as ke:
|
|
195
|
+
raise AccountError("Account does not exist:", name) from ke
|
|
196
|
+
|
|
197
|
+
def exists(self, name):
|
|
198
|
+
return name in self.config["accounts"]
|
|
199
|
+
|
|
200
|
+
def get_default(self):
|
|
201
|
+
default = self.config["default"]
|
|
202
|
+
if not default:
|
|
203
|
+
raise AccountError("Default account not configured.")
|
|
204
|
+
return self.get(default)
|
|
205
|
+
|
|
206
|
+
def is_default(self, name):
|
|
207
|
+
return name == self.config["default"]
|
|
208
|
+
|
|
209
|
+
def set_default(self, account):
|
|
210
|
+
self.config["default"] = account.name
|
|
211
|
+
|
|
212
|
+
def add(self, account):
|
|
213
|
+
if self.exists(account.name):
|
|
214
|
+
raise AccountError("An account already exists with that name.")
|
|
215
|
+
if not self.config["default"] and not self.config["accounts"]:
|
|
216
|
+
self.config["default"] = account.name
|
|
217
|
+
self.save(account)
|
|
218
|
+
|
|
219
|
+
def save(self, account):
|
|
220
|
+
self.config["accounts"][account.name] = account.to_dict()
|
|
221
|
+
# HACK This doesn't trip the crappy dirty flag on config, set manually
|
|
222
|
+
self.config.dirty = True
|
|
223
|
+
|
|
224
|
+
def remove(self, name):
|
|
225
|
+
try:
|
|
226
|
+
if self.config["default"] == name:
|
|
227
|
+
self.config["default"] = None
|
|
228
|
+
del self.config["accounts"][name]
|
|
229
|
+
# HACK This doesn't trip the crappy dirty flag on config, set manually
|
|
230
|
+
self.config.dirty = True
|
|
231
|
+
except KeyError:
|
|
232
|
+
raise AccountError("Account does not exist:", name)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from .account import register_account_cli
|
|
2
|
+
from .config import register_config_cli
|
|
3
|
+
from .instance import register_instance_cli
|
|
4
|
+
from .main import zucaro_cli
|
|
5
|
+
from .mod import register_mod_cli
|
|
6
|
+
from .play import register_play_cli
|
|
7
|
+
from .version import register_version_cli
|
|
8
|
+
|
|
9
|
+
register_account_cli(zucaro_cli)
|
|
10
|
+
register_version_cli(zucaro_cli)
|
|
11
|
+
register_instance_cli(zucaro_cli)
|
|
12
|
+
register_config_cli(zucaro_cli)
|
|
13
|
+
register_mod_cli(zucaro_cli)
|
|
14
|
+
register_play_cli(zucaro_cli)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import click
|
|
2
|
+
|
|
3
|
+
from zucaro.account import (
|
|
4
|
+
AccountError,
|
|
5
|
+
MicrosoftAccount,
|
|
6
|
+
OfflineAccount,
|
|
7
|
+
OnlineAccount,
|
|
8
|
+
RefreshError,
|
|
9
|
+
)
|
|
10
|
+
from zucaro.cli.utils import pass_account_manager, coro
|
|
11
|
+
from zucaro.logging import logger
|
|
12
|
+
from zucaro.yggdrasil import AuthenticationError
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def account_cmd(fn):
|
|
16
|
+
return click.argument("account")(fn)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
def account_cli():
|
|
21
|
+
"""Manage your accounts."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@account_cli.command("list")
|
|
26
|
+
@click.option("--ms", is_flag=True, help="List the online accounts")
|
|
27
|
+
@pass_account_manager
|
|
28
|
+
def _list(am, ms):
|
|
29
|
+
"""List available accounts."""
|
|
30
|
+
alist = am.list()
|
|
31
|
+
if ms:
|
|
32
|
+
alist = [acc for acc in alist if isinstance(am.get(acc), MicrosoftAccount)]
|
|
33
|
+
if alist:
|
|
34
|
+
lines = ("{}{}".format("* " if am.is_default(u) else " ", u) for u in alist)
|
|
35
|
+
print("\n".join(lines))
|
|
36
|
+
else:
|
|
37
|
+
logger.info("No accounts.")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@account_cli.command()
|
|
41
|
+
@account_cmd
|
|
42
|
+
@click.argument("mojang_username", required=False)
|
|
43
|
+
@click.option("--ms", "--microsoft", "microsoft", is_flag=True, default=False)
|
|
44
|
+
@pass_account_manager
|
|
45
|
+
def create(am, account, mojang_username, microsoft):
|
|
46
|
+
"""Create an account."""
|
|
47
|
+
try:
|
|
48
|
+
if mojang_username:
|
|
49
|
+
if microsoft:
|
|
50
|
+
logger.error("Do not use --microsoft with mojang_username argument")
|
|
51
|
+
return
|
|
52
|
+
acc = OnlineAccount.new(am, account, mojang_username)
|
|
53
|
+
elif microsoft:
|
|
54
|
+
acc = MicrosoftAccount.new(am, account)
|
|
55
|
+
else:
|
|
56
|
+
acc = OfflineAccount.new(am, account)
|
|
57
|
+
am.add(acc)
|
|
58
|
+
except AccountError as e:
|
|
59
|
+
logger.error("Could not create account: %s", e)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@account_cli.command()
|
|
63
|
+
@account_cmd
|
|
64
|
+
@pass_account_manager
|
|
65
|
+
@coro
|
|
66
|
+
async def authenticate(am, account):
|
|
67
|
+
"""Retrieve access token from Mojang servers using password."""
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
a = am.get(account)
|
|
71
|
+
except AccountError:
|
|
72
|
+
logger.error("AccountError", exc_info=True)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
if isinstance(a, OfflineAccount):
|
|
77
|
+
logger.error("Offline accounts cannot be authenticated")
|
|
78
|
+
elif isinstance(a, OnlineAccount):
|
|
79
|
+
import getpass
|
|
80
|
+
|
|
81
|
+
p = getpass.getpass("Password: ")
|
|
82
|
+
await a.authenticate(p) # Await the coroutine
|
|
83
|
+
elif isinstance(a, MicrosoftAccount):
|
|
84
|
+
await a.authenticate()
|
|
85
|
+
else:
|
|
86
|
+
logger.error("Unknown account type")
|
|
87
|
+
except AuthenticationError as e:
|
|
88
|
+
logger.error("Authentication failed: %s", e)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@account_cli.command()
|
|
92
|
+
@account_cmd
|
|
93
|
+
@pass_account_manager
|
|
94
|
+
def refresh(am, account):
|
|
95
|
+
"""Refresh access token with Mojang servers."""
|
|
96
|
+
try:
|
|
97
|
+
a = am.get(account)
|
|
98
|
+
a.refresh()
|
|
99
|
+
except (AccountError, RefreshError) as e:
|
|
100
|
+
logger.error("Could not refresh account: %s", e)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@account_cli.command()
|
|
104
|
+
@account_cmd
|
|
105
|
+
@pass_account_manager
|
|
106
|
+
def remove(am, account):
|
|
107
|
+
"""Remove the account."""
|
|
108
|
+
try:
|
|
109
|
+
am.remove(account)
|
|
110
|
+
except AccountError as e:
|
|
111
|
+
logger.error("Could not remove account: %s", e)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@account_cli.command()
|
|
115
|
+
@account_cmd
|
|
116
|
+
@pass_account_manager
|
|
117
|
+
def setdefault(am, account):
|
|
118
|
+
"""Set the account as default."""
|
|
119
|
+
try:
|
|
120
|
+
default = am.get(account)
|
|
121
|
+
am.set_default(default)
|
|
122
|
+
except AccountError as e:
|
|
123
|
+
logger.error("Could not set default account: %s", e)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def register_account_cli(zucaro_cli):
|
|
127
|
+
zucaro_cli.add_command(account_cli, name="account")
|