commit c0cf0bc40c0dd4100d96c7836f36f86289e707be Author: Oliver Hattshire Date: Thu Oct 23 19:17:24 2025 -0300 Initial commit - Implemented Login diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e886e9f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +__pycache__/ +.venv/ +*.egg-info/ +typings/ +persistent/ \ No newline at end of file diff --git a/Insthidge/__init__.py b/Insthidge/__init__.py new file mode 100644 index 0000000..b8090a2 --- /dev/null +++ b/Insthidge/__init__.py @@ -0,0 +1,14 @@ +from slidge import entrypoint + +from . import gateway, session + + +def main(): + entrypoint("Insthidge") + + +__all__ = ( + "gateway", + "main", + "session", +) diff --git a/Insthidge/__main__.py b/Insthidge/__main__.py new file mode 100644 index 0000000..992d307 --- /dev/null +++ b/Insthidge/__main__.py @@ -0,0 +1,3 @@ +from Insthidge import main + +main() diff --git a/Insthidge/gateway.py b/Insthidge/gateway.py new file mode 100644 index 0000000..ef84048 --- /dev/null +++ b/Insthidge/gateway.py @@ -0,0 +1,71 @@ +import asyncio + +from instagrapi import Client +from instagrapi.exceptions import ( + BadPassword, + ChallengeRequired, + FeedbackRequired, + TwoFactorRequired, +) +from slidge import BaseGateway, GatewayUser +from slidge.command.register import RegistrationType, TwoFactorNotRequired +from slixmpp import JID + +from .utils import get_session_file + + +class Gateway(BaseGateway): + COMPONENT_NAME = "Instagram (Slidge)" + COMPONENT_TYPE = "instagram" + COMPONENT_AVATAR = ("https://upload.wikimedia.org/wikipedia/commons/thumb/e/e7/" + "Instagram_logo_2016.svg/640px-Instagram_logo_2016.svg.png") + + ROSTER_GROUP = "instagram" + + REGISTRATION_INSTRUCTIONS = "Enter instagram credentials" + REGISTRATION_TYPE = RegistrationType.TWO_FACTOR_CODE + + GROUPS = False + + def __init__(self): + super().__init__() + self.instagram_client = dict[str, Client]() + + async def validate(self, user_jid: JID, registration_form: dict[str, str | None]): + session_file = get_session_file(user_jid.bare) + client = Client() + try: + _ = await asyncio.to_thread(client.login, + registration_form["username"], + registration_form["password"] + ) + except TwoFactorRequired: + self.instagram_client[user_jid.bare] = client + except BadPassword as e: + raise ValueError("Bad Password", e, e.args) + except ChallengeRequired as e: + raise ValueError("Browser Challenge Required", e, e.args) + except FeedbackRequired as e: + raise ValueError("Action moderated, account may be blocked", e, e.args) + except Exception as e: + raise ValueError("Could not authenticate: %s - %s", e, e.args) + else: + _ = await asyncio.to_thread(client.dump_settings, session_file) + raise TwoFactorNotRequired + + async def validate_two_factor_code(self, user: GatewayUser, code: str): + session_file = get_session_file(user.jid.bare) + client = self.instagram_client[user.jid.bare] + try: + if await asyncio.to_thread(client.login, + user.legacy_module_data["username"], + user.legacy_module_data["password"], + verification_code=code): + if not await asyncio.to_thread(client.dump_settings, session_file): + raise IOError("Could not save session file: %s - %s", session_file) + except ChallengeRequired as e: + raise ValueError("Browser Challenge Required", e, e.args) + except FeedbackRequired as e: + raise ValueError("Action moderated, account may be blocked", e, e.args) + except Exception as e: + raise ValueError("Could not authenticate: %s - %s", e, e.args) diff --git a/Insthidge/session.py b/Insthidge/session.py new file mode 100644 index 0000000..64d8c47 --- /dev/null +++ b/Insthidge/session.py @@ -0,0 +1,25 @@ +import asyncio + +import instagrapi +from slidge import BaseSession + +from .utils import get_session_file + + +class Session(BaseSession): + + def __init__(self, *a, **kw): + super().__init__(*a, **kw) + + async def login(self): + client = instagrapi.Client() + session_file = get_session_file(self.user_jid.bare) + + client.load_settings(session_file) + await asyncio.to_thread(client.login, + self.user.legacy_module_data["username"], + self.user.legacy_module_data["password"]) + + self.log.info( + "Logged in: %s", str(self.user_jid.bare) + ) diff --git a/Insthidge/utils.py b/Insthidge/utils.py new file mode 100644 index 0000000..b561ed4 --- /dev/null +++ b/Insthidge/utils.py @@ -0,0 +1,5 @@ +from slidge import global_config + + +def get_session_file(user_bare_jid: str): + return str(global_config.HOME_DIR / user_bare_jid) \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c129beb --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Insthidge + +A XMPP puppeteering gateway based on Slidge and Instagrapi + +Chat with Instagram users wthout leaving XMPP. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..c21094f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,57 @@ +[project] +name = "Insthidge" +description = "A XMPP puppeteering gateway based on Slidge and Instagrapi" +authors = [ + {name = "Oliver Hattshire", email = "oliver@hattshire.dev"}, +] +readme = "README.md" +license = "Unlicense" +classifiers = [ + "Topic :: Internet :: XMPP", +] +requires-python = ">= 3.11" +keywords = ["xmpp", "chat", "instagram", "gateway", "bridge", "instant messaging"] +version = "0.0.0a1" +dependencies = [ + "slidge>=0.3,<0.4", + "instagrapi>=2.2,<3", +] + +[project.scripts] +Insthidge = "Insthidge:main" + +[dependency-groups] +dev = [ + "mypy>=1.14.1", + "ruff>=0.9.1", +] + +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.packages.find] +include = ["Insthidge"] + +[tool.setuptools_scm] + +[[tool.uv.index]] +name = "pypi" +url = "https://pypi.org/simple" + +[tool.mypy] +files = ["Insthidge"] +check_untyped_defs = true +strict = false + +[[tool.mypy.overrides]] +module = ["instagrapi.*"] +ignore_missing_imports = true + +[tool.ruff] +line-length = 88 + +[tool.ruff.lint] +select = ["I"] + +