Init Commit
This commit is contained in:
commit
071c582edd
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
.env
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
# Use official Python image
|
||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# Install dependencies for psql
|
||||||
|
RUN apt-get update && apt-get install -y postgresql-client && rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
|
# Copy project files
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 80
|
||||||
|
|
||||||
|
# Run Flask app
|
||||||
|
CMD ["python", "main.py"]
|
||||||
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Python Flask web based app for crawling steam friends using Steam API
|
||||||
27
docker-compose.yml
Normal file
27
docker-compose.yml
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
services:
|
||||||
|
web:
|
||||||
|
build: .
|
||||||
|
container_name: steam-crawl
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
restart: no
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
entrypoint: ["./wait-for-db.sh", "db", "python", "main.py"]
|
||||||
|
db:
|
||||||
|
image: postgres:16
|
||||||
|
container_name: postgres-db
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${POSTGRES_USER}
|
||||||
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
|
POSTGRES_DB: ${POSTGRES_DB}
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- pgdata:/var/lib/postgresql/data
|
||||||
|
restart: always
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
pgdata:
|
||||||
6
env_example
Normal file
6
env_example
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
POSTGRES_USER=steamuser
|
||||||
|
POSTGRES_PASSWORD=steampass
|
||||||
|
POSTGRES_DB=steam_db
|
||||||
|
DATABASE_URL=postgresql+psycopg2://steamuser:steampass@db:5432/steam_db
|
||||||
|
STEAM_API_KEY=YOUR_STEAM_KEY
|
||||||
|
FLASK_DEBUG=False
|
||||||
31
main.py
Normal file
31
main.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from flask import render_template, jsonify
|
||||||
|
from modules.db import db, User
|
||||||
|
from modules.app import app
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
import modules.steam_api as api
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
app.config['STEAM_API_KEY'] = os.getenv('STEAM_API_KEY')
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv("DATABASE_URL")
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
app.config['DEBUG'] = os.getenv('DEBUG')
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
db.init_app(app)
|
||||||
|
db.create_all() # Create tables
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def hello():
|
||||||
|
return render_template("main.html.jinja")
|
||||||
|
|
||||||
|
@app.route("/user/<steam_id>")
|
||||||
|
def user_route(steam_id):
|
||||||
|
user = api.get_user(steam_id)
|
||||||
|
if not user:
|
||||||
|
return jsonify({"error": "User not found"}), 404
|
||||||
|
return jsonify(user.to_dict())
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(host="0.0.0.0", port=80, debug=True)
|
||||||
4
modules/app.py
Normal file
4
modules/app.py
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
50
modules/db.py
Normal file
50
modules/db.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
db = SQLAlchemy()
|
||||||
|
|
||||||
|
class User(db.Model):
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
steam_id: Mapped[int] = mapped_column(db.BigInteger, primary_key=True)
|
||||||
|
personaname: Mapped[str] = mapped_column(db.String(80))
|
||||||
|
profile_url: Mapped[str] = mapped_column(db.String(200))
|
||||||
|
avatar: Mapped[str] = mapped_column(db.String(200))
|
||||||
|
avatar_medium: Mapped[str] = mapped_column(db.String(200))
|
||||||
|
avatar_full: Mapped[str] = mapped_column(db.String(200))
|
||||||
|
community_visibility_state: Mapped[int] = mapped_column(db.Integer)
|
||||||
|
profile_state: Mapped[int] = mapped_column(db.Integer)
|
||||||
|
last_logoff: Mapped[int] = mapped_column(db.Integer)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"steam_id": self.steam_id,
|
||||||
|
"personaname": self.personaname,
|
||||||
|
"profile_url": self.profile_url,
|
||||||
|
"avatar": self.avatar,
|
||||||
|
"avatar_medium": self.avatar_medium,
|
||||||
|
"avatar_full": self.avatar_full,
|
||||||
|
"community_visibility_state": self.community_visibility_state,
|
||||||
|
"profile_state": self.profile_state,
|
||||||
|
"last_logoff": self.last_logoff,
|
||||||
|
}
|
||||||
|
|
||||||
|
class APICall(db.Model):
|
||||||
|
__tablename__ = "api_calls"
|
||||||
|
|
||||||
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
|
name: Mapped[str] = mapped_column(db.String(80), unique=True, nullable=False)
|
||||||
|
call_count: Mapped[int] = mapped_column(db.Integer, default=0, nullable=False)
|
||||||
|
first_called: Mapped[datetime] = mapped_column(db.DateTime, default=None, nullable=True)
|
||||||
|
last_called: Mapped[datetime] = mapped_column(db.DateTime, default=datetime.utcnow, nullable=False)
|
||||||
|
|
||||||
|
def register_call(self):
|
||||||
|
"""Increment call count and update timestamp."""
|
||||||
|
if self.call_count is None:
|
||||||
|
self.call_count = 0
|
||||||
|
if self.first_called is None:
|
||||||
|
self.first_called = datetime.now()
|
||||||
|
self.call_count = self.call_count + 1
|
||||||
|
self.last_called = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
52
modules/steam_api.py
Normal file
52
modules/steam_api.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import requests
|
||||||
|
from .app import app
|
||||||
|
from .db import db, User, APICall
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
API_NAME: Final = "STEAM_API"
|
||||||
|
|
||||||
|
def record_api_call():
|
||||||
|
api_call = APICall.query.filter_by(name=API_NAME).first()
|
||||||
|
if not api_call:
|
||||||
|
api_call = APICall(name=API_NAME)
|
||||||
|
db.session.add(api_call)
|
||||||
|
api_call.register_call()
|
||||||
|
return api_call
|
||||||
|
|
||||||
|
def get_user(steam_id):
|
||||||
|
# 1. Check DB
|
||||||
|
user = User.query.filter_by(steam_id=steam_id).first()
|
||||||
|
if user:
|
||||||
|
app.logger.debug("User fetch from DB")
|
||||||
|
return user
|
||||||
|
|
||||||
|
# 2. Fetch from Steam API
|
||||||
|
url = "https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v2/"
|
||||||
|
params = {"key": app.config['STEAM_API_KEY'], "steamids": steam_id}
|
||||||
|
resp = requests.get(url, params=params)
|
||||||
|
api = record_api_call()
|
||||||
|
data = resp.json()
|
||||||
|
players = data.get("response", {}).get("players", [])
|
||||||
|
if not players:
|
||||||
|
app.logger.debug(f"User not found (API called: {api.call_count})")
|
||||||
|
return None
|
||||||
|
|
||||||
|
player = players[0]
|
||||||
|
|
||||||
|
# 3. Save to DB
|
||||||
|
user = User(
|
||||||
|
steam_id=steam_id,
|
||||||
|
personaname=player.get("personaname"),
|
||||||
|
profile_url=player.get("profileurl"),
|
||||||
|
avatar=player.get("avatar"),
|
||||||
|
avatar_medium=player.get("avatarmedium"),
|
||||||
|
avatar_full=player.get("avatarfull"),
|
||||||
|
community_visibility_state=player.get("communityvisibilitystate"),
|
||||||
|
profile_state=player.get("profilestate"),
|
||||||
|
last_logoff=player.get("lastlogoff")
|
||||||
|
)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
app.logger.debug(f"User fetch from API (API called: {api.call_count})")
|
||||||
|
return user
|
||||||
5
requirements.txt
Normal file
5
requirements.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
flask==3.1.2
|
||||||
|
Flask-SQLAlchemy==3.0.3
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
python-dotenv==1.1.1
|
||||||
|
requests==2.31.0
|
||||||
7
templates/main.html.jinja
Normal file
7
templates/main.html.jinja
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<head>
|
||||||
|
<title>Steam Crawl</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Test</h1>
|
||||||
|
</body>
|
||||||
16
wait-for-db.sh
Executable file
16
wait-for-db.sh
Executable file
@ -0,0 +1,16 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# wait-for-db.sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
host="$1"
|
||||||
|
shift
|
||||||
|
cmd="$@"
|
||||||
|
|
||||||
|
until PGPASSWORD=$POSTGRES_PASSWORD psql -h "$host" -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c '\q'; do
|
||||||
|
echo "Waiting for database at $host..."
|
||||||
|
sleep 2
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "Database is ready, executing command..."
|
||||||
|
exec $cmd
|
||||||
Loading…
x
Reference in New Issue
Block a user