From 932c75ad802b55e9ae204f8b451783e11d8d85bd Mon Sep 17 00:00:00 2001 From: Matteo Rosati Date: Mon, 12 Jan 2026 21:34:11 +0100 Subject: [PATCH] working version --- .kilocode/agents.md | 10 +++ .kilocode/plan.md | 18 +++++ .kilocode/python.md | 21 +++++ .kilocode/search.md | 5 ++ .kilocode/typescript.md | 26 ++++++ .kilocode/ultrathink.md | 16 ++++ README.md | 174 ++++++++++++++++++++++++++++++++++++++++ main.py | 82 ++++++++++++++++--- 8 files changed, 343 insertions(+), 9 deletions(-) create mode 100644 .kilocode/agents.md create mode 100644 .kilocode/plan.md create mode 100644 .kilocode/python.md create mode 100644 .kilocode/search.md create mode 100644 .kilocode/typescript.md create mode 100644 .kilocode/ultrathink.md diff --git a/.kilocode/agents.md b/.kilocode/agents.md new file mode 100644 index 0000000..82f1070 --- /dev/null +++ b/.kilocode/agents.md @@ -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. diff --git a/.kilocode/plan.md b/.kilocode/plan.md new file mode 100644 index 0000000..7473eab --- /dev/null +++ b/.kilocode/plan.md @@ -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. diff --git a/.kilocode/python.md b/.kilocode/python.md new file mode 100644 index 0000000..1670276 --- /dev/null +++ b/.kilocode/python.md @@ -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 `). +- **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. diff --git a/.kilocode/search.md b/.kilocode/search.md new file mode 100644 index 0000000..fae1da1 --- /dev/null +++ b/.kilocode/search.md @@ -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. diff --git a/.kilocode/typescript.md b/.kilocode/typescript.md new file mode 100644 index 0000000..89f40e6 --- /dev/null +++ b/.kilocode/typescript.md @@ -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. diff --git a/.kilocode/ultrathink.md b/.kilocode/ultrathink.md new file mode 100644 index 0000000..efe992d --- /dev/null +++ b/.kilocode/ultrathink.md @@ -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. diff --git a/README.md b/README.md index e69de29..69e4eed 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/main.py b/main.py index 52e6d89..ab9ccf8 100644 --- a/main.py +++ b/main.py @@ -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, ) @@ -64,4 +128,4 @@ def main(): if __name__ == "__main__": - main() \ No newline at end of file + main()