working version

This commit is contained in:
Matteo Rosati
2026-01-12 21:34:11 +01:00
parent 6416ad2018
commit 932c75ad80
8 changed files with 343 additions and 9 deletions

10
.kilocode/agents.md Normal file
View File

@@ -0,0 +1,10 @@
# AGENT FILES
Always search the root folder for the following files, if they exist:
- `AGENTS.md`
- `CONSTITUTION.md`
- `GEMINI.md`
- `CLAUDE.md`
These files are generally used to configure the agent's behavior and capabilities. They are not required, but are recommended for most agents.

18
.kilocode/plan.md Normal file
View File

@@ -0,0 +1,18 @@
# PLANNING
You are an expert senior software engineer with 20+ years of experience in software development. You excel in planning and designing software systems.
**PLAN AND WAIT**: Before any code implementation you craft a complete, solid and detailed execution plan. You are a master of breaking down complex problems into smaller, manageable tasks. You are also a master of identifying dependencies and creating a clear roadmap for the project.
**ACTIONABLE PLANS**: Your plans are actionable and can be executed step by step, to track the progress of the implementation.
You will be given a problem to solve and you will create a plan to solve it. The plan will include:
1. A list of tasks to be completed
2. A list of dependencies between tasks
3. A list of resources needed to complete the tasks
4. A list of risks and mitigation strategies
5. A list of success criteria
**NEVER EXECUTE**: You will never execute the plan automatically. You will only create the plan and present it to the user.
**ALWAYS ASK**: You will always ask the user to execute the plan. You will then review the results and provide a detailed report on them.

21
.kilocode/python.md Normal file
View File

@@ -0,0 +1,21 @@
# PYTHON RULES
You are a Python expert with 15+ years of experience. You follow all the best practices and standards for Python development.
## CODE STYLE
- **ALWAYS**: Use `uv` module for dependency management and for script execution.
- **PREFER**: Use PEP 8 style guide for Python code.
- **ALWAYS**: Use type hints for function parameters and return values.
- **ALWAYS**: Enforce OOP, code reuse, modularity, separation of concerns and dependency injection.
- **ALWAYS**: Use docstrings for all functions and classes.
- **ALWAYS**: Use descriptive variable names.
- **ALWAYS**: Use constants for values that do not change.
- **ALWAYS**: Use list comprehensions instead of for loops when possible.
- **ALWAYS**: Use `with` statement for file operations.
- **PREFER**: Use `try`/`except` blocks for error handling.
- **ALWAYS**: Use `logging` module for logging.
- **PREFER**: Use `argparse` module for command line arguments.
- **ALWAYS**: Use `pyright` module for type checking before committing to a final code change (`uv run pyright <file>`).
- **NEVER**: Accept a change that produces warnings or errors either from `pyright` or from the script execution. If so, you must find a solution and address the issue.
- **NEVER**: Use outdated syntax, antipatterns, or deprecated features.

5
.kilocode/search.md Normal file
View File

@@ -0,0 +1,5 @@
# SEARCH
You have access to the Brave search tool. You can use it to search for information on the web.
**ALWAYS** use the Brave search tool to search for information about the user request. Use the information you get to get additional context.

26
.kilocode/typescript.md Normal file
View File

@@ -0,0 +1,26 @@
# TYPESCRIPT RULES
You are a TypeScript expert with 15+ years of experience. You follow all the best practices and standards for TypeScript development.
## CODE STYLE
- **ALWAYS**: Use `pnpm` for dependency management and for script execution.
- **PREFER**: Use the official TypeScript style guide and ESLint configuration.
- **ALWAYS**: Use type annotations for function parameters and return values.
- **ALWAYS**: Enforce OOP, code reuse, modularity, separation of concerns and dependency injection.
- **ALWAYS**: Use JSDoc comments for all functions and classes.
- **ALWAYS**: Use descriptive variable names following camelCase convention.
- **ALWAYS**: Use `const` and `readonly` for values that do not change.
- **ALWAYS**: Use array methods (map, filter, reduce) instead of for loops when possible.
- **ALWAYS**: Use `try`/`catch` blocks for error handling.
- **ALWAYS**: Use a logging library (e.g., winston, pino) for logging.
- **PREFER**: Use a CLI library (e.g., commander, yargs) for command line arguments.
- **ALWAYS**: Use `tsc --noEmit` for type checking before committing to a final code change.
- **ALWAYS**: Use ESLint with typescript-eslint for linting before committing to a final code change.
- **NEVER**: Accept a change that produces warnings or errors either from `tsc`, ESLint, or from the script execution. If so, you must find a solution and address the issue.
- **NEVER**: Use `any` type unless absolutely necessary. Use `unknown` instead when the type is truly unknown.
- **NEVER**: Use outdated syntax, antipatterns, or deprecated features.
- **ALWAYS**: Enable strict mode in tsconfig.json for comprehensive type checking.
- **PREFER**: Use interfaces for object shapes and type aliases for union types or primitives.
- **ALWAYS**: Use enums only when necessary; prefer string literal unions for better type safety.
- **PREFER**: Use utility types (Partial, Required, Readonly, Pick, Omit) for type transformations.

16
.kilocode/ultrathink.md Normal file
View File

@@ -0,0 +1,16 @@
# ROSI'S ULTRATHINK PROTOCOL™
The Rosi's Ultrathink Protocol™ is used only if explicitly requested by the user. It adds an extra layer of reasoning to the standard workflow.
# STEPS
When in Ultrathink mode, you will:
1. **ALWAYS**: You will always notify the user when we're in Ultrathink mode with "🧠 ULTRATHINK MODE ACTIVATED 🧠".
2. **THINKING**: You will first think deeply about the user's request, the context of the conversation and the current codebase.
3. **BEST SOLUTION**: You accept a solution only if it is the best possible to the problem.
4. **ANALYZE**: You will analyze the solution and validate it only if it respects the current tools, versions and libraries.
5. **THINK AGAIN**: If an immediate solution is possible, hold on and think again. Challenge yourself and investigate if other possible better implementations are possible. If you find a better solution, you will use it instead of the first one, or merge the best ideas of the two solutions into a single, refined one.
6. **ZERO LAZINESS**: Being lazy is strictly prohibited. Do not propose something because "it's easier" or "it's faster". You will always find the best solution, even if it takes more time.
7. **LOW TRUST ON KNOWLEDGE**: Your knowledge is limited and likely outdated. You have at your disposal a web search tool via Brave, use it as much as you need.
8. **NO ASSUMPTIONS**: You will not make any assumptions about the user's intent or the context of the conversation. You will always ask for clarification if needed.

174
README.md
View File

@@ -0,0 +1,174 @@
# Ingress Intel Report
A command-line tool for fetching and analyzing game events from the Ingress Intel API.
## Features
- **Geographic Filtering**: Query events within a bounding box
- **Event Classification**: Automatic event type detection (portal captures, links, resonators, etc.)
- **Player Tracking**: Filter events by specific players
- **Timestamp Filtering**: Filter events by time range
- **Coordinate Extraction**: Extract portal locations from events
## Installation
```bash
# Using uv (recommended)
uv sync
# Using pip
pip install -r requirements.txt
```
## Configuration
Set your Ingress cookie as an environment variable:
```bash
export INGRESS_COOKIE="your_cookie_here"
```
Or hardcode it in [`main.py`](main.py:82) (not recommended).
## Usage
### Basic Usage
```bash
# Get all events in default area (Venice, Italy)
python main.py
# Show help
python main.py --help
```
### Command Line Options
| Option | Type | Default | Description |
| ----------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--event-types` | Multiple strings | None | Filter by event types. Accepts one or more values from: `RESONATOR_DEPLOYED`, `RESONATOR_DESTROYED`, `PORTAL_CAPTURED`, `PORTAL_NEUTRALIZED`, `PORTAL_UNDER_ATTACK`, `LINK_CREATED`, `LINK_DESTROYED`, `CONTROL_FIELD_CREATED`, `UNKNOWN` |
| `--player-name` | String | None | Filter events by a specific player name |
| `--min-lat` | Integer | 45470259 | Minimum latitude (in microdegrees, E6 format) |
| `--min-lng` | Integer | 12244155 | Minimum longitude (in microdegrees, E6 format) |
| `--max-lat` | Integer | 45480370 | Maximum latitude (in microdegrees, E6 format) |
| `--max-lng` | Integer | 12298207 | Maximum longitude (in microdegrees, E6 format) |
| `--min-timestamp` | Timestamp | -1 | Minimum timestamp (milliseconds since epoch or ISO 8601 format) |
| `--max-timestamp` | Timestamp | -1 | Maximum timestamp (milliseconds since epoch or ISO 8601 format) |
### Timestamp Filtering
The `--min-timestamp` and `--max-timestamp` options support two formats:
**Format 1: Milliseconds (integer)**
```bash
python main.py --min-timestamp 1736659200000 --max-timestamp 1736745600000
```
**Format 2: ISO 8601 datetime string**
```bash
python main.py --min-timestamp "2026-01-12T00:00:00Z" --max-timestamp "2026-01-13T00:00:00Z"
```
**Examples:**
```bash
# Get events from last hour
python main.py --min-timestamp 1736659200000
# Get events for a specific day
python main.py --min-timestamp "2026-01-12T00:00:00Z" --max-timestamp "2026-01-13T00:00:00Z"
# Get events from a specific time onwards
python main.py --min-timestamp "2026-01-12T10:00:00Z"
# Get events up to a specific time
python main.py --max-timestamp "2026-01-12T12:00:00Z"
```
### Event Type Filtering
```bash
# Filter by single event type
python main.py --event-types PORTAL_CAPTURED
# Filter by multiple event types
python main.py --event-types PORTAL_CAPTURED LINK_CREATED CONTROL_FIELD_CREATED
# Filter by resonator events
python main.py --event-types RESONATOR_DEPLOYED RESONATOR_DESTROYED
```
### Player Filtering
```bash
# Filter by player name
python main.py --player-name Albicocca
# Combine with event type filter
python main.py --player-name Albicocca --event-types PORTAL_CAPTURED
```
### Geographic Filtering
Coordinates are in **E6 format** (microdegrees): multiply decimal degrees by 1,000,000.
```bash
# Custom geographic bounds
python main.py --min-lat 45470000 --max-lat 45480000 --min-lng 12240000 --max-lng 12300000
```
### Combining Filters
```bash
# Complex query with multiple filters
python main.py \
--min-timestamp "2026-01-12T10:00:00Z" \
--max-timestamp "2026-01-12T12:00:00Z" \
--event-types PORTAL_CAPTURED LINK_CREATED \
--player-name Albicocca \
--min-lat 45470000 \
--max-lat 45480000
```
## Event Types
| Event Type | Description |
| ----------------------- | ---------------------------------- |
| `RESONATOR_DEPLOYED` | Player deploys resonator on portal |
| `RESONATOR_DESTROYED` | Resonator destroyed |
| `PORTAL_CAPTURED` | Portal captured by player |
| `PORTAL_NEUTRALIZED` | Portal neutralized |
| `PORTAL_UNDER_ATTACK` | Portal being attacked |
| `LINK_CREATED` | Link created between portals |
| `LINK_DESTROYED` | Link destroyed |
| `CONTROL_FIELD_CREATED` | Control field created |
| `UNKNOWN` | Unrecognized event type |
## Output Format
Events are printed in the following format:
```
[2026-01-12 10:28:27] [PORTAL_CAPTURED] Albicocca captured L' Arboreto - Coords: 45471652, 12274703
[2026-01-12 10:28:26] [LINK_CREATED] Albicocca linked from L' Arboreto to Parco San Giuliano - Coords: 45471652, 12274703
```
## Project Structure
```
ingress/
├── main.py # CLI entry point
├── ingress.py # API client
├── models.py # Data models
├── pyproject.toml # Project configuration
└── README.md # This file
```
## Dependencies
- Python 3.13+
- requests 2.31.0+
## License
See project license file for details.

80
main.py
View File

@@ -5,17 +5,57 @@ from datetime import datetime
from ingress import IngressAPI
from models import EventType, Plext
def parse_timestamp(value: str) -> int:
"""
Parse timestamp from either milliseconds (int) or ISO 8601 string.
Args:
value: Either integer milliseconds or ISO 8601 string
Returns:
Timestamp in milliseconds since epoch
Raises:
ValueError: If format is invalid
"""
# Try parsing as integer (milliseconds)
try:
return int(value)
except ValueError:
pass
# Try parsing as ISO 8601 datetime
try:
# Handle 'Z' suffix (UTC)
iso_value = value.replace("Z", "+00:00")
dt = datetime.fromisoformat(iso_value)
return int(dt.timestamp() * 1000)
except ValueError:
raise ValueError(
f"Invalid timestamp format: {value}. "
"Use milliseconds (e.g., 1736659200000) or "
"ISO 8601 format (e.g., '2026-01-12T10:00:00Z')"
)
def print_plexts(plexts: List[Plext]):
for plext in plexts:
# Sort plexts by timestamp (ascending - oldest first)
sorted_plexts = sorted(plexts, key=lambda p: p.timestamp)
for plext in sorted_plexts:
dt = datetime.fromtimestamp(plext.timestamp / 1000.0)
timestamp_str = dt.strftime("%Y-%m-%d %H:%M:%S")
coords = plext.get_event_coordinates()
if coords:
lat, lng = coords
print(f"[{timestamp_str}] [{plext.get_event_type().name}] {plext.text} - Coords: {lat}, {lng}")
print(
f"[{timestamp_str}] [{plext.get_event_type().name}] {plext.text} - Coords: {lat}, {lng}"
)
else:
print(f"[{timestamp_str}] [{plext.get_event_type().name}] {plext.text}")
def main():
parser = argparse.ArgumentParser(description="Ingress Intel Report")
parser.add_argument(
@@ -26,17 +66,37 @@ def main():
help="List of event types to filter by.",
)
parser.add_argument("--player-name", type=str, help="Player name to filter by.")
parser.add_argument("--min-lat", type=int, default=45470259, help="Minimum latitude.")
parser.add_argument("--min-lng", type=int, default=12244155, help="Minimum longitude.")
parser.add_argument("--max-lat", type=int, default=45480370, help="Maximum latitude.")
parser.add_argument("--max-lng", type=int, default=12298207, help="Maximum longitude.")
parser.add_argument(
"--min-lat", type=int, default=45470259, help="Minimum latitude."
)
parser.add_argument(
"--min-lng", type=int, default=12244155, help="Minimum longitude."
)
parser.add_argument(
"--max-lat", type=int, default=45480370, help="Maximum latitude."
)
parser.add_argument(
"--max-lng", type=int, default=12298207, help="Maximum longitude."
)
parser.add_argument(
"--min-timestamp",
type=parse_timestamp,
default=-1,
help="Minimum timestamp (milliseconds since epoch or ISO 8601 format, e.g., '2026-01-12T10:00:00Z')",
)
parser.add_argument(
"--max-timestamp",
type=parse_timestamp,
default=-1,
help="Maximum timestamp (milliseconds since epoch or ISO 8601 format, e.g., '2026-01-12T10:00:00Z')",
)
args = parser.parse_args()
# It's recommended to set the INGRESS_COOKIE environment variable
# instead of hardcoding the cookie in the script.
cookie = os.getenv(
"INGRESS_COOKIE",
"csrftoken=3yUkOxJ2lkc49AStOe8JDpmP1vWI5WTXpV5hlMKTRB4sisPGLdv1IXMBvKfYAZmQ; sessionid=.eJyrViotTi3yTFGyUrJINTA2SbG0MDdPSjJPMjVU0gHLueYmZuYApYvyixNLMvVyE0tKUvMd0kGiesn5uUBVxanFxZn5eWGpRSAKqNRIqRYA3zUdAQ:1veJTk:haQGju5WSpAUlMygA1AF26nvx1I; ingress.intelmap.shflt=viz; _ncc=1; ingress.intelmap.zoom=16; ingress.intelmap.lng=12.271176494006568; ingress.intelmap.lat=45.47531344367309"
"csrftoken=3yUkOxJ2lkc49AStOe8JDpmP1vWI5WTXpV5hlMKTRB4sisPGLdv1IXMBvKfYAZmQ; sessionid=.eJyrViotTi3yTFGyUrJINTA2SbG0MDdPSjJPMjVU0gHLueYmZuYApYvyixNLMvVyE0tKUvMd0kGiesn5uUBVxanFxZn5eWGpRSAKqNRIqRYA3zUdAQ:1veJTk:haQGju5WSpAUlMygA1AF26nvx1I; ingress.intelmap.shflt=viz; _ncc=1; ingress.intelmap.zoom=16; ingress.intelmap.lng=12.271176494006568; ingress.intelmap.lat=45.47531344367309",
)
# This is the version from the curl command in json_doc.md
@@ -46,7 +106,9 @@ def main():
cookie=cookie,
)
event_types = [EventType[et] for et in args.event_types] if args.event_types else None
event_types = (
[EventType[et] for et in args.event_types] if args.event_types else None
)
try:
plexts = client.get_plexts(
@@ -54,6 +116,8 @@ def main():
min_lng_e6=args.min_lng,
max_lat_e6=args.max_lat,
max_lng_e6=args.max_lng,
min_timestamp_ms=args.min_timestamp,
max_timestamp_ms=args.max_timestamp,
event_types=event_types,
player_name=args.player_name,
)