From ac520b41e5b73386f6913d67143fdfa9e6792e5c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Oct 2025 16:51:42 +0000 Subject: [PATCH 1/2] Add League of Legends data viewer Implemented a comprehensive LoL data viewer with the following features: - Fetch summoner information by name - Display match history with detailed statistics (KDA, CS, damage) - Show top 10 champion masteries - Support for all major LoL regions (JP, KR, NA, EU, etc.) - User-friendly Streamlit UI with sidebar configuration Technical changes: - Added Riot Games API client with rate limiting support - Created RiotAPI class for handling API requests - Implemented match history and champion mastery endpoints - Added secrets.toml.example for API key configuration - Updated README with comprehensive setup instructions - Added .gitignore to protect sensitive data The app allows users to search for any summoner and view their recent performance and champion statistics. Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .gitignore | 40 +++++ .streamlit/secrets.toml.example | 5 + README.md | 93 ++++++++++- requirements.txt | 1 + streamlit_app.py | 276 +++++++++++++++++++++++++++----- 5 files changed, 373 insertions(+), 42 deletions(-) create mode 100644 .gitignore create mode 100644 .streamlit/secrets.toml.example diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..447bb43cc6ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Streamlit secrets +.streamlit/secrets.toml + +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Virtual environments +venv/ +ENV/ +env/ + +# IDEs +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db diff --git a/.streamlit/secrets.toml.example b/.streamlit/secrets.toml.example new file mode 100644 index 000000000000..3ff01efbdbb1 --- /dev/null +++ b/.streamlit/secrets.toml.example @@ -0,0 +1,5 @@ +# Riot Games API Configuration +# Copy this file to secrets.toml and add your API key +# Get your API key from: https://developer.riotgames.com/ + +RIOT_API_KEY = "RGAPI-your-api-key-here" diff --git a/README.md b/README.md index 10dbdedd6091..6d436bdfa334 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,91 @@ -# Welcome to Streamlit! +# League of Legends Data Viewer -Edit `/streamlit_app.py` to customize this app to your heart's desire. :heart: +A Streamlit application for viewing League of Legends match history and champion mastery data using the Riot Games API. -If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community -forums](https://discuss.streamlit.io). +## Features + +- **Match History**: View the last 10 matches with detailed statistics + - Champion played + - KDA (Kills/Deaths/Assists) + - CS (Creep Score) + - Damage dealt to champions + - Game mode and duration + - Win/Loss indicator + +- **Champion Mastery**: Display top 10 champions by mastery + - Champion level + - Mastery points + +- **Multi-Region Support**: Supports all major LoL regions + - Japan (jp1) + - Korea (kr) + - North America (na1) + - Europe West (euw1) + - Europe Nordic & East (eun1) + - And more... + +## Setup + +### 1. Install Dependencies + +```bash +pip install -r requirements.txt +``` + +### 2. Get Riot API Key + +1. Visit [Riot Developer Portal](https://developer.riotgames.com/) +2. Sign in with your Riot account +3. Generate a Development API Key + +### 3. Configure API Key + +You have two options: + +**Option A: Using Streamlit Secrets (Recommended for deployment)** + +1. Copy the example secrets file: + ```bash + cp .streamlit/secrets.toml.example .streamlit/secrets.toml + ``` + +2. Edit `.streamlit/secrets.toml` and add your API key: + ```toml + RIOT_API_KEY = "RGAPI-your-actual-api-key" + ``` + +**Option B: Enter API Key in the App** + +Simply enter your API key in the sidebar when running the app. + +### 4. Run the Application + +```bash +streamlit run streamlit_app.py +``` + +## Usage + +1. Open the application in your browser +2. Select your region from the sidebar +3. Enter your Riot API Key (if not configured in secrets) +4. Enter a summoner name +5. Click "Fetch Data" + +## API Rate Limits + +Development API keys have rate limits: +- 20 requests every 1 second +- 100 requests every 2 minutes + +The app includes a small delay between requests to help avoid rate limiting. + +## Note + +- Make sure to select the correct region for the summoner you're searching +- API keys expire after 24 hours and need to be regenerated +- For production use, apply for a production API key from Riot + +## License + +This project is for educational purposes. League of Legends and Riot Games are trademarks or registered trademarks of Riot Games, Inc. diff --git a/requirements.txt b/requirements.txt index 502d7d1a0d19..5d02d05d5cce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ altair pandas streamlit +requests diff --git a/streamlit_app.py b/streamlit_app.py index 7a0ed1272052..16d173e5a7e4 100644 --- a/streamlit_app.py +++ b/streamlit_app.py @@ -1,40 +1,240 @@ -import altair as alt -import numpy as np -import pandas as pd import streamlit as st +import requests +import time +from typing import Optional, List, Dict -""" -# Welcome to Streamlit! - -Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:. -If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community -forums](https://discuss.streamlit.io). - -In the meantime, below is an example of what you can do with just a few lines of code: -""" - -num_points = st.slider("Number of points in spiral", 1, 10000, 1100) -num_turns = st.slider("Number of turns in spiral", 1, 300, 31) - -indices = np.linspace(0, 1, num_points) -theta = 2 * np.pi * num_turns * indices -radius = indices - -x = radius * np.cos(theta) -y = radius * np.sin(theta) - -df = pd.DataFrame({ - "x": x, - "y": y, - "idx": indices, - "rand": np.random.randn(num_points), -}) - -st.altair_chart(alt.Chart(df, height=700, width=700) - .mark_point(filled=True) - .encode( - x=alt.X("x", axis=None), - y=alt.Y("y", axis=None), - color=alt.Color("idx", legend=None, scale=alt.Scale()), - size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])), - )) +# Riot API Configuration +RIOT_API_KEY = st.secrets.get("RIOT_API_KEY", "") + +class RiotAPI: + """Riot Games API Client""" + + def __init__(self, api_key: str, region: str = "jp1", routing: str = "asia"): + self.api_key = api_key + self.region = region # Platform routing (jp1, kr, na1, etc.) + self.routing = routing # Regional routing (asia, americas, europe, sea) + self.headers = {"X-Riot-Token": api_key} + + def get_summoner_by_name(self, summoner_name: str) -> Optional[Dict]: + """Get summoner information by summoner name""" + url = f"https://{self.region}.api.riotgames.com/lol/summoner/v4/summoners/by-name/{summoner_name}" + try: + response = requests.get(url, headers=self.headers) + if response.status_code == 200: + return response.json() + else: + st.error(f"Error fetching summoner: {response.status_code} - {response.text}") + return None + except Exception as e: + st.error(f"Exception: {str(e)}") + return None + + def get_champion_mastery(self, puuid: str, count: int = 10) -> Optional[List[Dict]]: + """Get champion mastery for a summoner""" + url = f"https://{self.region}.api.riotgames.com/lol/champion-mastery/v4/champion-masteries/by-puuid/{puuid}/top" + try: + response = requests.get(url, headers=self.headers, params={"count": count}) + if response.status_code == 200: + return response.json() + else: + st.error(f"Error fetching champion mastery: {response.status_code}") + return None + except Exception as e: + st.error(f"Exception: {str(e)}") + return None + + def get_match_ids(self, puuid: str, count: int = 10) -> Optional[List[str]]: + """Get match IDs for a summoner""" + url = f"https://{self.routing}.api.riotgames.com/lol/match/v5/matches/by-puuid/{puuid}/ids" + try: + response = requests.get(url, headers=self.headers, params={"count": count}) + if response.status_code == 200: + return response.json() + else: + st.error(f"Error fetching match IDs: {response.status_code}") + return None + except Exception as e: + st.error(f"Exception: {str(e)}") + return None + + def get_match_details(self, match_id: str) -> Optional[Dict]: + """Get match details by match ID""" + url = f"https://{self.routing}.api.riotgames.com/lol/match/v5/matches/{match_id}" + try: + response = requests.get(url, headers=self.headers) + if response.status_code == 200: + return response.json() + else: + st.error(f"Error fetching match details: {response.status_code}") + return None + except Exception as e: + st.error(f"Exception: {str(e)}") + return None + + +def display_champion_mastery(mastery_data: List[Dict]): + """Display champion mastery information""" + st.subheader("🏆 Champion Mastery Top 10") + + if not mastery_data: + st.warning("No champion mastery data available") + return + + for idx, champ in enumerate(mastery_data, 1): + col1, col2, col3, col4 = st.columns([1, 3, 2, 2]) + with col1: + st.write(f"**#{idx}**") + with col2: + st.write(f"Champion ID: {champ['championId']}") + with col3: + st.write(f"Level: {champ['championLevel']}") + with col4: + st.write(f"Points: {champ['championPoints']:,}") + + +def display_match_history(match_data: List[Dict], summoner_puuid: str): + """Display match history""" + st.subheader("📊 Recent Match History") + + if not match_data: + st.warning("No match history available") + return + + for match in match_data: + info = match.get('info', {}) + participants = info.get('participants', []) + + # Find the player's data in participants + player_data = None + for participant in participants: + if participant.get('puuid') == summoner_puuid: + player_data = participant + break + + if not player_data: + continue + + # Display match info + game_mode = info.get('gameMode', 'Unknown') + game_duration = info.get('gameDuration', 0) // 60 # Convert to minutes + win = player_data.get('win', False) + + # Create expandable section for each match + with st.expander(f"{'✅ Victory' if win else '❌ Defeat'} - {game_mode} ({game_duration} min)"): + col1, col2, col3, col4 = st.columns(4) + + with col1: + st.metric("Champion", player_data.get('championName', 'Unknown')) + with col2: + kills = player_data.get('kills', 0) + deaths = player_data.get('deaths', 0) + assists = player_data.get('assists', 0) + st.metric("KDA", f"{kills}/{deaths}/{assists}") + with col3: + cs = player_data.get('totalMinionsKilled', 0) + player_data.get('neutralMinionsKilled', 0) + st.metric("CS", cs) + with col4: + damage = player_data.get('totalDamageDealtToChampions', 0) + st.metric("Damage", f"{damage:,}") + + +# Main App +st.title("âš”ī¸ League of Legends Data Viewer") +st.write("View match history and champion mastery for any summoner") + +# Sidebar for configuration +with st.sidebar: + st.header("Settings") + + # Region selection + region = st.selectbox( + "Region", + ["jp1", "kr", "na1", "euw1", "eun1", "br1", "la1", "la2", "oc1", "ru", "tr1"], + index=0 + ) + + # Routing mapping + routing_map = { + "jp1": "asia", "kr": "asia", + "na1": "americas", "br1": "americas", "la1": "americas", "la2": "americas", + "euw1": "europe", "eun1": "europe", "ru": "europe", "tr1": "europe", + "oc1": "sea" + } + routing = routing_map.get(region, "asia") + + # API Key input + api_key_input = st.text_input("Riot API Key", value=RIOT_API_KEY, type="password") + + st.info("Get your API key from [Riot Developer Portal](https://developer.riotgames.com/)") + +# Main content +summoner_name = st.text_input("Enter Summoner Name", placeholder="Hide on bush") + +if st.button("Fetch Data") and summoner_name and api_key_input: + api = RiotAPI(api_key_input, region=region, routing=routing) + + with st.spinner("Fetching summoner data..."): + summoner = api.get_summoner_by_name(summoner_name) + + if summoner: + st.success(f"Found summoner: {summoner['name']}") + + # Display basic info + col1, col2, col3 = st.columns(3) + with col1: + st.metric("Summoner Level", summoner['summonerLevel']) + with col2: + st.metric("Profile Icon", summoner['profileIconId']) + with col3: + st.write(f"**PUUID:** {summoner['puuid'][:8]}...") + + st.divider() + + # Fetch and display champion mastery + with st.spinner("Fetching champion mastery..."): + mastery_data = api.get_champion_mastery(summoner['puuid'], count=10) + + if mastery_data: + display_champion_mastery(mastery_data) + + st.divider() + + # Fetch and display match history + with st.spinner("Fetching match history..."): + match_ids = api.get_match_ids(summoner['puuid'], count=10) + + if match_ids: + matches = [] + progress_bar = st.progress(0) + for idx, match_id in enumerate(match_ids): + match_details = api.get_match_details(match_id) + if match_details: + matches.append(match_details) + progress_bar.progress((idx + 1) / len(match_ids)) + time.sleep(0.1) # Rate limiting + + progress_bar.empty() + display_match_history(matches, summoner['puuid']) + +elif summoner_name and not api_key_input: + st.warning("âš ī¸ Please enter your Riot API Key in the sidebar") + +# Instructions +with st.expander("â„šī¸ How to use"): + st.markdown(""" + ### Steps: + 1. Get your Riot API Key from [Riot Developer Portal](https://developer.riotgames.com/) + 2. Enter your API key in the sidebar + 3. Select your region + 4. Enter a summoner name + 5. Click "Fetch Data" + + ### Features: + - 📊 View recent match history (last 10 games) + - 🏆 View top 10 champion masteries + - 📈 Detailed match statistics (KDA, CS, damage, etc.) + + ### Note: + - Development API keys are rate-limited + - Make sure to select the correct region for the summoner + """) From 34d12ac285898c640c8c8e8ff9c4c909c9269892 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 28 Oct 2025 16:54:14 +0000 Subject: [PATCH 2/2] Simplify API key configuration to sidebar-only input Removed the secrets.toml configuration option and simplified the setup to only use sidebar input for the Riot API key. This makes the app easier to use and reduces setup complexity. Changes: - Removed st.secrets reference from streamlit_app.py - Deleted .streamlit/secrets.toml.example - Updated README to remove secrets.toml setup instructions - Added placeholder text to API key input field Users now enter their API key directly in the sidebar when using the app. Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .streamlit/secrets.toml.example | 5 ----- README.md | 24 ++---------------------- streamlit_app.py | 4 +--- 3 files changed, 3 insertions(+), 30 deletions(-) delete mode 100644 .streamlit/secrets.toml.example diff --git a/.streamlit/secrets.toml.example b/.streamlit/secrets.toml.example deleted file mode 100644 index 3ff01efbdbb1..000000000000 --- a/.streamlit/secrets.toml.example +++ /dev/null @@ -1,5 +0,0 @@ -# Riot Games API Configuration -# Copy this file to secrets.toml and add your API key -# Get your API key from: https://developer.riotgames.com/ - -RIOT_API_KEY = "RGAPI-your-api-key-here" diff --git a/README.md b/README.md index 6d436bdfa334..a52e056c941f 100644 --- a/README.md +++ b/README.md @@ -38,27 +38,7 @@ pip install -r requirements.txt 2. Sign in with your Riot account 3. Generate a Development API Key -### 3. Configure API Key - -You have two options: - -**Option A: Using Streamlit Secrets (Recommended for deployment)** - -1. Copy the example secrets file: - ```bash - cp .streamlit/secrets.toml.example .streamlit/secrets.toml - ``` - -2. Edit `.streamlit/secrets.toml` and add your API key: - ```toml - RIOT_API_KEY = "RGAPI-your-actual-api-key" - ``` - -**Option B: Enter API Key in the App** - -Simply enter your API key in the sidebar when running the app. - -### 4. Run the Application +### 3. Run the Application ```bash streamlit run streamlit_app.py @@ -68,7 +48,7 @@ streamlit run streamlit_app.py 1. Open the application in your browser 2. Select your region from the sidebar -3. Enter your Riot API Key (if not configured in secrets) +3. Enter your Riot API Key in the sidebar 4. Enter a summoner name 5. Click "Fetch Data" diff --git a/streamlit_app.py b/streamlit_app.py index 16d173e5a7e4..35d1ff54439b 100644 --- a/streamlit_app.py +++ b/streamlit_app.py @@ -3,8 +3,6 @@ import time from typing import Optional, List, Dict -# Riot API Configuration -RIOT_API_KEY = st.secrets.get("RIOT_API_KEY", "") class RiotAPI: """Riot Games API Client""" @@ -163,7 +161,7 @@ def display_match_history(match_data: List[Dict], summoner_puuid: str): routing = routing_map.get(region, "asia") # API Key input - api_key_input = st.text_input("Riot API Key", value=RIOT_API_KEY, type="password") + api_key_input = st.text_input("Riot API Key", type="password", placeholder="RGAPI-...") st.info("Get your API key from [Riot Developer Portal](https://developer.riotgames.com/)")