spacecrew 1.1.0__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.
- spacecrew-1.1.0/.github/workflows/build.yml +28 -0
- spacecrew-1.1.0/.github/workflows/publish.yml +44 -0
- spacecrew-1.1.0/.gitignore +65 -0
- spacecrew-1.1.0/.pre-commit-config.yaml +21 -0
- spacecrew-1.1.0/LICENSE +21 -0
- spacecrew-1.1.0/PKG-INFO +62 -0
- spacecrew-1.1.0/README.md +47 -0
- spacecrew-1.1.0/pyproject.toml +24 -0
- spacecrew-1.1.0/src/spacecrew/__init__.py +3 -0
- spacecrew-1.1.0/src/spacecrew/cli.py +219 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["**"]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
name: Build package
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- uses: astral-sh/setup-uv@v5
|
|
20
|
+
|
|
21
|
+
- name: Build
|
|
22
|
+
run: uv build
|
|
23
|
+
|
|
24
|
+
- uses: actions/upload-artifact@v4
|
|
25
|
+
with:
|
|
26
|
+
name: dist
|
|
27
|
+
path: dist/
|
|
28
|
+
if-no-files-found: error
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
name: publish
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
|
|
7
|
+
permissions:
|
|
8
|
+
contents: read
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
publish:
|
|
12
|
+
name: Publish to PyPI
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- uses: astral-sh/setup-uv@v5
|
|
18
|
+
|
|
19
|
+
- name: Verify tag matches pyproject version
|
|
20
|
+
run: |
|
|
21
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
22
|
+
PYPROJECT_VERSION="$(python3 -c 'import tomllib; print(tomllib.load(open("pyproject.toml","rb"))["project"]["version"])')"
|
|
23
|
+
if [ "$TAG_VERSION" != "$PYPROJECT_VERSION" ]; then
|
|
24
|
+
echo "::error::tag version 'v$TAG_VERSION' does not match pyproject version '$PYPROJECT_VERSION'"
|
|
25
|
+
exit 1
|
|
26
|
+
fi
|
|
27
|
+
echo "version $TAG_VERSION matches"
|
|
28
|
+
|
|
29
|
+
- name: Build
|
|
30
|
+
run: uv build
|
|
31
|
+
|
|
32
|
+
- name: Check version does not already exist on PyPI
|
|
33
|
+
run: |
|
|
34
|
+
VERSION="${GITHUB_REF_NAME#v}"
|
|
35
|
+
if curl -fsSI "https://pypi.org/pypi/spacecrew/$VERSION/json" > /dev/null 2>&1; then
|
|
36
|
+
echo "::error::spacecrew $VERSION already exists on PyPI"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
echo "spacecrew $VERSION not found on PyPI, proceeding"
|
|
40
|
+
|
|
41
|
+
- name: Publish
|
|
42
|
+
env:
|
|
43
|
+
UV_PUBLISH_TOKEN: ${{ secrets.PYPI_TOKEN }}
|
|
44
|
+
run: uv publish
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
*.egg-info/
|
|
24
|
+
.installed.cfg
|
|
25
|
+
*.egg
|
|
26
|
+
MANIFEST
|
|
27
|
+
|
|
28
|
+
# PyInstaller
|
|
29
|
+
*.manifest
|
|
30
|
+
*.spec
|
|
31
|
+
|
|
32
|
+
# Installer logs
|
|
33
|
+
pip-log.txt
|
|
34
|
+
pip-delete-this-directory.txt
|
|
35
|
+
|
|
36
|
+
# Unit test / coverage reports
|
|
37
|
+
htmlcov/
|
|
38
|
+
.tox/
|
|
39
|
+
.nox/
|
|
40
|
+
.coverage
|
|
41
|
+
.coverage.*
|
|
42
|
+
.cache
|
|
43
|
+
nosetests.xml
|
|
44
|
+
coverage.xml
|
|
45
|
+
*.cover
|
|
46
|
+
.hypothesis/
|
|
47
|
+
.pytest_cache/
|
|
48
|
+
|
|
49
|
+
# Environments
|
|
50
|
+
.env
|
|
51
|
+
.venv
|
|
52
|
+
env/
|
|
53
|
+
venv/
|
|
54
|
+
ENV/
|
|
55
|
+
env.bak/
|
|
56
|
+
venv.bak/
|
|
57
|
+
|
|
58
|
+
# IDE
|
|
59
|
+
.idea/
|
|
60
|
+
.vscode/
|
|
61
|
+
*.swp
|
|
62
|
+
*.swo
|
|
63
|
+
|
|
64
|
+
# mypy
|
|
65
|
+
.mypy_cache/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
default_install_hook_types:
|
|
2
|
+
- commit-msg
|
|
3
|
+
- pre-commit
|
|
4
|
+
repos:
|
|
5
|
+
- repo: local
|
|
6
|
+
hooks:
|
|
7
|
+
- id: needs-prefix
|
|
8
|
+
name: commit message needs to have a prefix
|
|
9
|
+
language: pygrep
|
|
10
|
+
entry: '^(chore|ci|junk|test|feat|fix|build|docs|refactor)!?(\([a-z]+\))?: (?![A-Z])(?![a-z]+(ed|ing)\s).*$'
|
|
11
|
+
args:
|
|
12
|
+
- '--multiline'
|
|
13
|
+
- '--negate'
|
|
14
|
+
stages:
|
|
15
|
+
- commit-msg
|
|
16
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
17
|
+
rev: v6.0.0
|
|
18
|
+
hooks:
|
|
19
|
+
- id: trailing-whitespace
|
|
20
|
+
- id: end-of-file-fixer
|
|
21
|
+
- id: check-added-large-files
|
spacecrew-1.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jakub Ginał
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
spacecrew-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: spacecrew
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: Check who is in space right now
|
|
5
|
+
Project-URL: Homepage, https://github.com/jakub-ginal/spacecrew
|
|
6
|
+
Author: Jakub Ginał
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Requires-Dist: chafa-py>=1.2
|
|
11
|
+
Requires-Dist: pillow>=10.0
|
|
12
|
+
Requires-Dist: requests>=2.0
|
|
13
|
+
Requires-Dist: rich>=13.0
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# spacecrew
|
|
17
|
+
Spacecrew is a lightweight Python terminal program that shows who is in space right now.
|
|
18
|
+
|
|
19
|
+
<img width="800" height="600" alt="92_1x_shots_so" src="https://github.com/user-attachments/assets/dfd95ad7-904d-4495-9c98-b8e1751ea5f3" />
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## Tested Systems
|
|
23
|
+
|
|
24
|
+
* **CachyOS**
|
|
25
|
+
* **Ubuntu**
|
|
26
|
+
* **Debian**
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
Clone the repository and run the setup script to automatically install dependencies and configure the command:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
git clone https://github.com/jakub-ginal/spacecrew
|
|
34
|
+
cd spacecrew
|
|
35
|
+
chmod +x setup.sh
|
|
36
|
+
./setup.sh
|
|
37
|
+
```
|
|
38
|
+
Or run it via this single command:
|
|
39
|
+
```bash
|
|
40
|
+
git clone https://github.com/jakub-ginal/spacecrew && cd spacecrew && bash setup.sh && hash -r && cd ~ && rm -rf ~/spacecrew
|
|
41
|
+
```
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
Simply type `spacecrew` anywhere in your terminal.
|
|
45
|
+
Follow the interactive menu to select a mission or view specific astronaut details and photos.
|
|
46
|
+
|
|
47
|
+
### Time in Space
|
|
48
|
+
|
|
49
|
+
* **p (previous)**: Days from past missions.
|
|
50
|
+
* **c (current)**: Days from the current mission (tracked live).
|
|
51
|
+
<img width="800" height="600" alt="356_1x_shots_so" src="https://github.com/user-attachments/assets/a87a2e9d-3c5a-443c-b62f-ec19f2922f0d" />
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Uninstall
|
|
55
|
+
|
|
56
|
+
To completely remove the program and its configuration from your system, run:
|
|
57
|
+
```bash
|
|
58
|
+
cd ~ && rm -rf ~/spacecrew && rm -f ~/.local/bin/spacecrew && hash -r
|
|
59
|
+
```
|
|
60
|
+
## Data Source
|
|
61
|
+
|
|
62
|
+
Data provided by [International Space Station APIs](https://github.com/corquaid/international-space-station-APIs) by **Cormac Quaid**.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# spacecrew
|
|
2
|
+
Spacecrew is a lightweight Python terminal program that shows who is in space right now.
|
|
3
|
+
|
|
4
|
+
<img width="800" height="600" alt="92_1x_shots_so" src="https://github.com/user-attachments/assets/dfd95ad7-904d-4495-9c98-b8e1751ea5f3" />
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## Tested Systems
|
|
8
|
+
|
|
9
|
+
* **CachyOS**
|
|
10
|
+
* **Ubuntu**
|
|
11
|
+
* **Debian**
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Clone the repository and run the setup script to automatically install dependencies and configure the command:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
git clone https://github.com/jakub-ginal/spacecrew
|
|
19
|
+
cd spacecrew
|
|
20
|
+
chmod +x setup.sh
|
|
21
|
+
./setup.sh
|
|
22
|
+
```
|
|
23
|
+
Or run it via this single command:
|
|
24
|
+
```bash
|
|
25
|
+
git clone https://github.com/jakub-ginal/spacecrew && cd spacecrew && bash setup.sh && hash -r && cd ~ && rm -rf ~/spacecrew
|
|
26
|
+
```
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
Simply type `spacecrew` anywhere in your terminal.
|
|
30
|
+
Follow the interactive menu to select a mission or view specific astronaut details and photos.
|
|
31
|
+
|
|
32
|
+
### Time in Space
|
|
33
|
+
|
|
34
|
+
* **p (previous)**: Days from past missions.
|
|
35
|
+
* **c (current)**: Days from the current mission (tracked live).
|
|
36
|
+
<img width="800" height="600" alt="356_1x_shots_so" src="https://github.com/user-attachments/assets/a87a2e9d-3c5a-443c-b62f-ec19f2922f0d" />
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## Uninstall
|
|
40
|
+
|
|
41
|
+
To completely remove the program and its configuration from your system, run:
|
|
42
|
+
```bash
|
|
43
|
+
cd ~ && rm -rf ~/spacecrew && rm -f ~/.local/bin/spacecrew && hash -r
|
|
44
|
+
```
|
|
45
|
+
## Data Source
|
|
46
|
+
|
|
47
|
+
Data provided by [International Space Station APIs](https://github.com/corquaid/international-space-station-APIs) by **Cormac Quaid**.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "spacecrew"
|
|
3
|
+
version = "1.1.0"
|
|
4
|
+
description = "Check who is in space right now"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
authors = [{ name = "Jakub Ginał" }]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"rich>=13.0",
|
|
11
|
+
"requests>=2.0",
|
|
12
|
+
"chafa.py>=1.2",
|
|
13
|
+
"pillow>=10.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.urls]
|
|
17
|
+
Homepage = "https://github.com/jakub-ginal/spacecrew"
|
|
18
|
+
|
|
19
|
+
[project.scripts]
|
|
20
|
+
spacecrew = "spacecrew:main"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["hatchling"]
|
|
24
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from collections import defaultdict
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from io import BytesIO
|
|
6
|
+
|
|
7
|
+
import chafa
|
|
8
|
+
import requests
|
|
9
|
+
from PIL import Image
|
|
10
|
+
from rich.columns import Columns
|
|
11
|
+
from rich.console import Console
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
from rich.text import Text
|
|
15
|
+
from rich.tree import Tree
|
|
16
|
+
|
|
17
|
+
console = Console()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def clear_screen():
|
|
21
|
+
os.system("clear")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def fetch_space_data():
|
|
25
|
+
url = "https://corquaid.github.io/international-space-station-APIs/JSON/people-in-space.json"
|
|
26
|
+
headers = {"User-Agent": "Mozilla/5.0"}
|
|
27
|
+
try:
|
|
28
|
+
res = requests.get(url, headers=headers, timeout=5)
|
|
29
|
+
if res.status_code == 200:
|
|
30
|
+
return res.json()["people"]
|
|
31
|
+
except Exception:
|
|
32
|
+
pass
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def group_people_by_station(people):
|
|
37
|
+
iss_groups = defaultdict(list)
|
|
38
|
+
tiangong_groups = defaultdict(list)
|
|
39
|
+
|
|
40
|
+
for p in people:
|
|
41
|
+
craft = p.get("spacecraft", "Unknown")
|
|
42
|
+
if p.get("iss"):
|
|
43
|
+
iss_groups[craft].append(p)
|
|
44
|
+
else:
|
|
45
|
+
tiangong_groups[craft].append(p)
|
|
46
|
+
|
|
47
|
+
return iss_groups, tiangong_groups
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def build_tree_menu(people_count, iss_groups, tiangong_groups):
|
|
51
|
+
tree = Tree(f"[bold white]PEOPLE IN SPACE ({people_count})[/bold white]")
|
|
52
|
+
missions = []
|
|
53
|
+
num = 1
|
|
54
|
+
|
|
55
|
+
iss_node = tree.add("[bold green]ISS[/bold green]")
|
|
56
|
+
for craft, members in iss_groups.items():
|
|
57
|
+
m_node = iss_node.add(f"[bold cyan]{num}. {craft}[/bold cyan]")
|
|
58
|
+
for m in members:
|
|
59
|
+
m_node.add(f"{m.get('name')} ({m.get('country')})")
|
|
60
|
+
missions.append((craft, members))
|
|
61
|
+
num += 1
|
|
62
|
+
|
|
63
|
+
tiangong_node = tree.add("[bold deep_sky_blue]Tiangong[/bold deep_sky_blue]")
|
|
64
|
+
for craft, members in tiangong_groups.items():
|
|
65
|
+
m_node = tiangong_node.add(f"[bold cyan]{num}. {craft}[/bold cyan]")
|
|
66
|
+
for m in members:
|
|
67
|
+
m_node.add(f"{m.get('name')} ({m.get('country')})")
|
|
68
|
+
missions.append((craft, members))
|
|
69
|
+
num += 1
|
|
70
|
+
|
|
71
|
+
return tree, missions
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def fetch_photo_panel(url):
|
|
75
|
+
if not url:
|
|
76
|
+
return Panel(Text("No photo", style="dim red"), title="Photo", expand=False)
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
res = requests.get(url, headers={"User-Agent": "Mozilla/5.0"}, timeout=5)
|
|
80
|
+
if res.status_code == 200:
|
|
81
|
+
img = Image.open(BytesIO(res.content)).convert("RGB")
|
|
82
|
+
width, height = img.size
|
|
83
|
+
pixels = img.tobytes()
|
|
84
|
+
|
|
85
|
+
config = chafa.CanvasConfig()
|
|
86
|
+
config.width = 28
|
|
87
|
+
config.height = 16
|
|
88
|
+
|
|
89
|
+
canvas = chafa.Canvas(config)
|
|
90
|
+
canvas.draw_all_pixels(
|
|
91
|
+
chafa.PixelType.CHAFA_PIXEL_RGB8, pixels, width, height, width * 3
|
|
92
|
+
)
|
|
93
|
+
out = canvas.print().decode("utf-8")
|
|
94
|
+
|
|
95
|
+
return Panel(Text.from_ansi(out), title="Photo", expand=False)
|
|
96
|
+
except Exception:
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
return Panel(Text("No photo", style="dim red"), title="Photo", expand=False)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def create_mission_table(craft, members):
|
|
103
|
+
table = Table(title=f"Mission: {craft}")
|
|
104
|
+
table.add_column("No.", style="cyan", justify="right")
|
|
105
|
+
table.add_column("Name", style="bold white")
|
|
106
|
+
table.add_column("Country", style="green")
|
|
107
|
+
|
|
108
|
+
for i, m in enumerate(members, 1):
|
|
109
|
+
table.add_row(str(i), str(m.get("name")), str(m.get("country")))
|
|
110
|
+
|
|
111
|
+
return table
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def calculate_space_experience(launched_timestamp, previous_days):
|
|
115
|
+
if not launched_timestamp:
|
|
116
|
+
return "N/A", f"{previous_days} days"
|
|
117
|
+
|
|
118
|
+
dt = datetime.fromtimestamp(launched_timestamp)
|
|
119
|
+
launched_str = dt.strftime("%Y-%m-%d %H:%M UTC")
|
|
120
|
+
|
|
121
|
+
current_mission_days = (time.time() - launched_timestamp) / 86400
|
|
122
|
+
total_days = previous_days + current_mission_days
|
|
123
|
+
|
|
124
|
+
total_str = f"{total_days:.1f} days ({previous_days}p + {current_mission_days:.1f}c)"
|
|
125
|
+
return launched_str, total_str
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create_profile_table(person):
|
|
129
|
+
table = Table(title=f"Profile: {person.get('name')}")
|
|
130
|
+
table.add_column("Property", style="cyan")
|
|
131
|
+
table.add_column("Value", style="white", max_width=40)
|
|
132
|
+
|
|
133
|
+
table.add_row("Country", f"{person.get('country')} {person.get('flag', '')}")
|
|
134
|
+
table.add_row("Position", str(person.get("position") or "N/A"))
|
|
135
|
+
table.add_row("Agency", str(person.get("agency") or "N/A"))
|
|
136
|
+
table.add_row("Spacecraft", str(person.get("spacecraft") or "N/A"))
|
|
137
|
+
|
|
138
|
+
launched_ts = person.get("launched")
|
|
139
|
+
prev_days = person.get("days_in_space", 0)
|
|
140
|
+
launched_date, total_experience = calculate_space_experience(launched_ts, prev_days)
|
|
141
|
+
|
|
142
|
+
table.add_row("Launched At", launched_date)
|
|
143
|
+
table.add_row("Time in Space", total_experience)
|
|
144
|
+
|
|
145
|
+
links = [
|
|
146
|
+
("Image URL", person.get("image")),
|
|
147
|
+
("Wikipedia", person.get("url")),
|
|
148
|
+
("X (Twitter)", person.get("twitter")),
|
|
149
|
+
("Instagram", person.get("instagram")),
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
for label, link_url in links:
|
|
153
|
+
if link_url:
|
|
154
|
+
table.add_row(label, f"[link={link_url}]Open Link[/link]")
|
|
155
|
+
else:
|
|
156
|
+
table.add_row(label, "N/A")
|
|
157
|
+
|
|
158
|
+
return table
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def show_astronaut_view(person):
|
|
162
|
+
clear_screen()
|
|
163
|
+
photo_panel = fetch_photo_panel(person.get("image"))
|
|
164
|
+
profile_table = create_profile_table(person)
|
|
165
|
+
console.print(Columns([photo_panel, profile_table]))
|
|
166
|
+
input("\nPress Enter to return to menu...")
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def handle_mission_view(craft, members):
|
|
170
|
+
clear_screen()
|
|
171
|
+
console.print(create_mission_table(craft, members))
|
|
172
|
+
|
|
173
|
+
astro_choice = input("\nSelect astronaut number (or 'menu'/'quit'): ").strip().lower()
|
|
174
|
+
|
|
175
|
+
if astro_choice == "quit":
|
|
176
|
+
return "quit"
|
|
177
|
+
elif astro_choice == "menu":
|
|
178
|
+
return "menu"
|
|
179
|
+
|
|
180
|
+
if astro_choice.isdigit():
|
|
181
|
+
a_idx = int(astro_choice) - 1
|
|
182
|
+
if 0 <= a_idx < len(members):
|
|
183
|
+
show_astronaut_view(members[a_idx])
|
|
184
|
+
|
|
185
|
+
return "ok"
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def main():
|
|
189
|
+
while True:
|
|
190
|
+
clear_screen()
|
|
191
|
+
people = fetch_space_data()
|
|
192
|
+
|
|
193
|
+
if people is None:
|
|
194
|
+
console.print("[bold red]Error: No internet connection. Please check your network and try again.[/bold red]")
|
|
195
|
+
input("\nPress Enter to retry...")
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
iss_groups, tiangong_groups = group_people_by_station(people)
|
|
199
|
+
tree, missions = build_tree_menu(len(people), iss_groups, tiangong_groups)
|
|
200
|
+
|
|
201
|
+
console.print(tree)
|
|
202
|
+
choice = input("\n> ").strip().lower()
|
|
203
|
+
|
|
204
|
+
if choice == "quit":
|
|
205
|
+
break
|
|
206
|
+
elif choice == "menu":
|
|
207
|
+
continue
|
|
208
|
+
|
|
209
|
+
if choice.isdigit():
|
|
210
|
+
idx = int(choice) - 1
|
|
211
|
+
if 0 <= idx < len(missions):
|
|
212
|
+
craft, members = missions[idx]
|
|
213
|
+
result = handle_mission_view(craft, members)
|
|
214
|
+
if result == "quit":
|
|
215
|
+
break
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
main()
|