kopia lustrzana https://github.com/bellingcat/auto-archiver
Add instagrapi server scripts
rodzic
fbf51f61b9
commit
2a0dfaead2
|
@ -0,0 +1,19 @@
|
|||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
|
||||
# Install Poetry
|
||||
RUN pip install --upgrade pip
|
||||
RUN pip install poetry
|
||||
|
||||
# Copy all source code
|
||||
COPY . .
|
||||
|
||||
# Prevent Poetry from creating a virtual environment
|
||||
RUN poetry config virtualenvs.create false
|
||||
|
||||
# Install dependencies
|
||||
RUN poetry install --no-root
|
||||
|
||||
|
||||
# Use uvicorn to run the FastAPI app
|
||||
CMD ["poetry", "run", "uvicorn", "src.instaserver:app", "--host", "0.0.0.0", "--port", "8000"]
|
|
@ -0,0 +1,18 @@
|
|||
[project]
|
||||
name = "instaserver"
|
||||
version = "0.1.0"
|
||||
description = "A FastAPI InstagrAPI server"
|
||||
package-mode = false
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"fastapi (>=0.115.12,<0.116.0)",
|
||||
"instagrapi (>=2.1.3,<3.0.0)",
|
||||
"uvicorn (>=0.34.0,<0.35.0)",
|
||||
"pillow (>=11.1.0,<12.0.0)",
|
||||
"python-dotenv (>=1.1.0,<2.0.0)"
|
||||
]
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=2.0.0,<3.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
|
@ -0,0 +1,157 @@
|
|||
"""https://subzeroid.github.io/instagrapi/
|
||||
|
||||
Run using the following command:
|
||||
uvicorn src.instgrapinstance.instaserver:app --host 0.0.0.0 --port 8000 --reload
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from instagrapi import Client
|
||||
from instagrapi.exceptions import LoginRequired, BadCredentials
|
||||
|
||||
load_dotenv(dotenv_path="secrets/.env")
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||
|
||||
INSTAGRAM_USERNAME = os.getenv("INSTAGRAM_USERNAME")
|
||||
INSTAGRAM_PASSWORD = os.getenv("INSTAGRAM_PASSWORD")
|
||||
SESSION_FILE = "secrets/instagrapi_session.json"
|
||||
|
||||
app = FastAPI()
|
||||
cl = Client()
|
||||
|
||||
|
||||
@app.on_event("startup")
|
||||
def startup_event():
|
||||
"""Login automatically when server starts"""
|
||||
try:
|
||||
login_instagram()
|
||||
except RuntimeError as e:
|
||||
logging.error(f"API failed to start: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def login_instagram():
|
||||
"""Ensures Instagrapi is logged in and session is persistent"""
|
||||
if not INSTAGRAM_USERNAME or not INSTAGRAM_PASSWORD:
|
||||
raise RuntimeError("Instagram credentials are missing.")
|
||||
|
||||
if os.path.exists(SESSION_FILE):
|
||||
try:
|
||||
cl.load_settings(SESSION_FILE)
|
||||
cl.get_timeline_feed()
|
||||
logging.info("Using saved session.")
|
||||
return
|
||||
except LoginRequired:
|
||||
logging.info("Session expired. Logging in again...")
|
||||
|
||||
try:
|
||||
cl.login(INSTAGRAM_USERNAME, INSTAGRAM_PASSWORD)
|
||||
cl.dump_settings(SESSION_FILE)
|
||||
logging.info("Login successful, session saved.")
|
||||
except BadCredentials as bc:
|
||||
raise RuntimeError("Incorrect Instagram username or password.") from bc
|
||||
except Exception as e:
|
||||
raise RuntimeError(f"Login failed: {e}") from e
|
||||
|
||||
|
||||
@app.get("/v1/media/by/id")
|
||||
def get_media_by_id(id: str):
|
||||
"""Fetch post details by media ID"""
|
||||
logging.info(f"Fetching media by ID: {id}")
|
||||
try:
|
||||
media = cl.media_info(id)
|
||||
return media.model_dump()
|
||||
except Exception as e:
|
||||
logging.warning(f"Media not found for ID {id}: {e}")
|
||||
raise HTTPException(status_code=404, detail="Post not found") from e
|
||||
|
||||
|
||||
@app.get("/v1/media/by/code")
|
||||
def get_media_by_code(code: str):
|
||||
"""Fetch post details by shortcode"""
|
||||
logging.info(f"Fetching media by shortcode: {code}")
|
||||
try:
|
||||
media_id = cl.media_pk_from_code(code)
|
||||
media = cl.media_info(media_id)
|
||||
return media.model_dump()
|
||||
except Exception as e:
|
||||
logging.warning(f"Media not found for code {code}: {e}")
|
||||
raise HTTPException(status_code=404, detail="Post not found") from e
|
||||
|
||||
|
||||
@app.get("/v2/user/tag/medias")
|
||||
def get_user_tagged_medias(user_id: str, page_id: str = None):
|
||||
logging.info(f"Fetching tagged medias for user_id={user_id} page_id={page_id}")
|
||||
try:
|
||||
# Placeholder for now
|
||||
items, next_page_id = [], None
|
||||
return {"response": {"items": items}, "next_page_id": next_page_id}
|
||||
except Exception as e:
|
||||
logging.warning(f"Tagged media not found for {user_id}: {e}")
|
||||
raise HTTPException(status_code=404, detail="Tagged media not found") from e
|
||||
|
||||
|
||||
@app.get("/v1/user/highlights")
|
||||
def get_user_highlights(user_id: str):
|
||||
logging.info(f"Fetching highlights list for user_id={user_id}")
|
||||
try:
|
||||
highlights = cl.user_highlights(user_id)
|
||||
return [h.model_dump() for h in highlights]
|
||||
except Exception as e:
|
||||
logging.warning(f"Highlights not found for {user_id}: {e}")
|
||||
raise HTTPException(status_code=404, detail="No highlights found") from e
|
||||
|
||||
|
||||
@app.get("/v2/highlight/by/id")
|
||||
def get_highlight_by_id(id: str):
|
||||
logging.info(f"Fetching highlight details for id={id}")
|
||||
try:
|
||||
highlight = cl.highlight_info(id)
|
||||
return {"response": {"reels": {f"highlight:{id}": highlight.model_dump()}}}
|
||||
except Exception as e:
|
||||
logging.warning(f"Highlight not found for id {id}: {e}")
|
||||
raise HTTPException(status_code=404, detail="Highlight not found") from e
|
||||
|
||||
|
||||
@app.get("/v1/user/stories/by/username")
|
||||
def get_stories(username: str):
|
||||
logging.info(f"Fetching stories for username={username}")
|
||||
try:
|
||||
user_id = cl.user_id_from_username(username)
|
||||
stories = cl.user_stories(user_id)
|
||||
return [story.model_dump() for story in stories]
|
||||
except Exception as e:
|
||||
logging.warning(f"Stories not found for {username}: {e}")
|
||||
raise HTTPException(status_code=404, detail="Stories not found") from e
|
||||
|
||||
|
||||
@app.get("/v2/user/by/username")
|
||||
def get_user_by_username(username: str):
|
||||
logging.info(f"Fetching user profile for username={username}")
|
||||
try:
|
||||
user = cl.user_info_by_username(username)
|
||||
return {"user": user.model_dump()}
|
||||
except Exception as e:
|
||||
logging.warning(f"User not found: {username}: {e}")
|
||||
raise HTTPException(status_code=404, detail="User not found") from e
|
||||
|
||||
|
||||
@app.get("/v1/user/medias/chunk")
|
||||
def get_user_medias(user_id: str, end_cursor: str = None):
|
||||
logging.info(f"Fetching paginated medias for user_id={user_id}, end_cursor={end_cursor}")
|
||||
try:
|
||||
posts, next_cursor = cl.user_medias_paginated(user_id, end_cursor=end_cursor)
|
||||
return [[post.model_dump() for post in posts], next_cursor]
|
||||
except Exception as e:
|
||||
logging.warning(f"No posts found for user_id={user_id}: {e}")
|
||||
raise HTTPException(status_code=404, detail="No posts found") from e
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
Ładowanie…
Reference in New Issue