A Discord bot and API for managing meshcore contact advertisements.
This is a vibe-coding project. Here be dragons!
- Discord Bot: Manage your meshcore contact advertisements via slash commands
- Web API: RESTful API for programmatic access to contact data
- File-based Storage: Simple JSON storage using the datafiles library
- Contact Filtering: Search and filter contacts by type, name, and public key
- CORS Support: Access the API from any origin
For production deployments, use Podman containers:
# Build the container
podman build -t localhost/coretact:latest -f Containerfile .
# Deploy with Podman Play
podman play kube podman-play.yamlSee DEPLOYMENT.md for detailed deployment instructions.
- Python 3.12+
- uv package manager
- A Discord bot token (get one from Discord Developer Portal)
- Clone the repository:
git clone <repository-url>
cd coretact- Install dependencies with uv:
uv sync- Configure environment variables:
cp .env.default .env
# Edit .env and add your Discord bot token and owner ID- Run the bot or API server:
# Run the Discord bot
uv run python -m coretact bot
# Run the Web API server
uv run python -m coretact api
# Run the Web API server on a custom host/port
uv run python -m coretact api --host 127.0.0.1 --port 8080All commands use the /coretact prefix:
Add or update your meshcore contact advertisement.
Example:
/coretact add meshcore://110055365953947d253d213d7ab36df0be29ffb7a758049f657a6b32e1d00d66087d...
List contact advertisements. Without arguments, lists your own adverts. Optionally specify a user to list their adverts.
Remove one of your advertisements by providing the first 8+ characters of the public key.
Example:
/coretact remove 55365953
Search for contact advertisements in the current server with optional filters:
type: Filter by device type (companion, repeater, or room)key_prefix: Filter by public key prefixname: Filter by name (partial match)user: Filter by specific user
Download contacts as a JSON file with optional filters (same as search command).
Show statistics for the current server including total contacts, breakdown by type, and unique users.
The Web API provides programmatic access to contact data. All endpoints return JSON responses.
http://localhost:8080
GET /healthReturns the API health status and version.
Response:
{
"status": "ok",
"version": "1.0.0"
}GET /api/v1/mesh/{server_id}/contactsGet all contacts for a mesh (Discord server).
Query Parameters:
type(optional): Filter by device type (1=companion, 2=repeater, 3=room)key_prefix(optional): Filter by public key prefixname(optional): Filter by name (partial match)user_id(optional): Filter by Discord user ID
Example Request:
curl "http://localhost:8080/api/v1/mesh/123456789/contacts?type=1&name=Core"Response:
{
"contacts": [
{
"type": 1,
"name": "egrme.sh Core",
"custom_name": null,
"public_key": "55365953947d253d213d7ab36df0be29ffb7a758049f657a6b32e1d00d66087d",
"flags": 0,
"latitude": "0.0",
"longitude": "0.0",
"last_advert": 1760299414,
"last_modified": 1760299413,
"out_path": ""
}
]
}GET /api/v1/contact/{public_key}Get a single contact by public key (searches across all meshes).
Example Request:
curl "http://localhost:8080/api/v1/contact/55365953947d253d213d7ab36df0be29ffb7a758049f657a6b32e1d00d66087d"Response:
{
"type": 1,
"name": "egrme.sh Core",
"custom_name": null,
"public_key": "55365953947d253d213d7ab36df0be29ffb7a758049f657a6b32e1d00d66087d",
"flags": 0,
"latitude": "0.0",
"longitude": "0.0",
"last_advert": 1760299414,
"last_modified": 1760299413,
"out_path": "",
"advert_string": "meshcore://110055365953...",
"discord_server_id": "123456789",
"discord_user_id": "987654321"
}POST /api/v1/mesh/{server_id}/contacts/bulkGet specific contacts by public keys.
Request Body:
{
"public_keys": ["55365953...", "83c3e551..."],
"include_metadata": true
}Response: Same format as "Get Mesh Contacts" endpoint.
GET /api/v1/mesh/{server_id}/user/{user_id}/contactsGet all contacts for a specific Discord user.
Example Request:
curl "http://localhost:8080/api/v1/mesh/123456789/user/987654321/contacts"Response: Same format as "Get Mesh Contacts" endpoint.
GET /api/v1/meshList all meshes (Discord servers) with their basic information and contact counts.
Example Request:
curl "http://localhost:8080/api/v1/mesh"Response:
{
"meshes": [
{
"server_id": "123456789",
"name": "My Discord Server",
"description": "A server for meshcore enthusiasts",
"icon_url": "https://cdn.discordapp.com/icons/123456789/abcdef.png",
"contact_count": 42,
"created_at": 1760299414,
"updated_at": 1760299414
},
{
"server_id": "987654321",
"name": "Another Server",
"description": "",
"icon_url": "",
"contact_count": 15,
"created_at": 1760299500,
"updated_at": 1760299500
}
]
}GET /api/v1/mesh/{server_id}Get information about a mesh (Discord server) including name, description, icon, and contact count.
Example Request:
curl "http://localhost:8080/api/v1/mesh/123456789"Response:
{
"server_id": "123456789",
"name": "My Discord Server",
"description": "A server for meshcore enthusiasts",
"icon_url": "https://cdn.discordapp.com/icons/123456789/abcdef.png",
"contact_count": 42,
"created_at": 1760299414,
"updated_at": 1760299414
}GET /api/v1/mesh/{server_id}/statsGet statistics for a mesh.
Example Request:
curl "http://localhost:8080/api/v1/mesh/123456789/stats"Response:
{
"server_id": "123456789",
"total_adverts": 42,
"by_type": {
"companion": 25,
"repeater": 10,
"room": 7
},
"unique_users": 15,
"last_updated": 1760299414
}The API supports CORS and allows requests from any origin. All endpoints include the following CORS headers:
Access-Control-Allow-Origin: *Access-Control-Allow-Methods: GET, POST, OPTIONSAccess-Control-Allow-Headers: Content-Type
All error responses follow this format:
{
"error": "Error message",
"status": 400
}Common status codes:
400: Bad Request (invalid parameters)404: Not Found (resource doesn't exist)500: Internal Server Error
pytestcoretact/
├── coretact/
│ ├── __main__.py # CLI entry point
│ ├── bot.py # Discord bot initialization
│ ├── models.py # Data models (Advert, Contact)
│ ├── storage.py # Storage layer
│ ├── log.py # Logging configuration
│ ├── meshcore/
│ │ └── parser.py # Meshcore URL parser
│ ├── cogs/
│ │ └── coretact/ # Discord command handlers
│ │ └── __init__.py
│ └── api/
│ ├── __init__.py
│ ├── server.py # API server setup
│ ├── routes.py # API route handlers
│ └── middleware.py # CORS and error handling
├── tests/
│ ├── conftest.py # Pytest configuration
│ ├── test_storage.py # Storage tests
│ ├── test_advert_parser.py # Parser tests
│ └── test_api.py # API tests
└── storage/ # Contact data (git-ignored)
| Variable | Required | Description |
|---|---|---|
DISCORD_BOT_TOKEN |
Yes | Your Discord bot token |
DISCORD_BOT_OWNER_ID |
No | Your Discord user ID (for owner commands) |
AUTO_SYNC_COMMANDS |
No | Auto-sync commands on startup (default: true) |
| Variable | Required | Description |
|---|---|---|
WEB_API_HOST |
No | API host to bind to (default: 0.0.0.0) |
WEB_API_PORT |
No | API port to bind to (default: 8080) |
| Variable | Required | Description |
|---|---|---|
STORAGE_PATH |
No | Storage directory path (default: ./storage) |
| Variable | Required | Description |
|---|---|---|
LOG_LEVEL |
No | Logging level (default: INFO) |
[Add license information]
[Add contributing guidelines]