# mautrix-twitter - A Matrix-Twitter DM puppeting bridge
# Copyright (C) 2020 Tulir Asokan
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
from mauigpapi.state import AndroidState
from mauigpapi.http import AndroidAPI
from mauigpapi.errors import (IGLoginTwoFactorRequiredError, IGLoginBadPasswordError,
IGLoginInvalidUserError, IGLoginError)
from mautrix.bridge.commands import HelpSection, command_handler
from .typehint import CommandEvent
SECTION_AUTH = HelpSection("Authentication", 10, "")
@command_handler(needs_auth=False, management_only=True, help_section=SECTION_AUTH,
help_text="Log in to Instagram", help_args="<_username_> <_password_>")
async def login(evt: CommandEvent) -> None:
if await evt.sender.is_logged_in():
await evt.reply("You're already logged in")
return
elif len(evt.args) < 2:
await evt.reply("**Usage:** `$cmdprefix+sp login `")
return
username = evt.args[0]
password = " ".join(evt.args[1:])
state = AndroidState()
state.device.generate(username)
api = AndroidAPI(state)
await api.simulate_pre_login_flow()
try:
resp = await api.login(username, password)
except IGLoginTwoFactorRequiredError as e:
tfa_info = e.body.two_factor_info
msg = "Username and password accepted, but you have two-factor authentication enabled.\n"
if tfa_info.sms_two_factor_on:
if tfa_info.totp_two_factor_on:
msg += (f"Send the code from your authenticator app "
f"or one sent to {tfa_info.obfuscated_phone_number} here.")
else:
msg += f"Send the code sent to {tfa_info.obfuscated_phone_number} here."
elif tfa_info.totp_two_factor_on:
msg += "Send the code from your authenticator app here."
else:
msg += ("Unfortunately, none of your two-factor authentication methods are currently "
"supported by the bridge.")
return
evt.sender.command_status = {
"action": "Login",
"room_id": evt.room_id,
"next": enter_login_2fa,
"username": tfa_info.username,
"2fa_identifier": tfa_info.two_factor_identifier,
"state": state,
"api": api,
}
except IGLoginInvalidUserError:
await evt.reply("Invalid username")
except IGLoginBadPasswordError:
await evt.reply("Incorrect password")
else:
evt.sender.state = state
user = resp.logged_in_user
await evt.reply(f"Successfully logged in as {user.full_name} ([@{user.username}]"
f"(https://instagram.com/{user.username}), user ID: {user.pk})")
await evt.sender.try_connect()
async def enter_login_2fa(evt: CommandEvent) -> None:
api: AndroidAPI = evt.sender.command_status["api"]
state: AndroidState = evt.sender.command_status["state"]
identifier = evt.sender.command_status["2fa_identifier"]
username = evt.sender.command_status["username"]
evt.sender.command_status = None
try:
user = await api.two_factor_login(username, code="".join(evt.args), identifier=identifier)
except IGLoginError as e:
await evt.reply(f"Failed to log in: {e.body.message}")
except Exception as e:
await evt.reply(f"Failed to log in: {e}")
evt.log.exception("Failed to log in")
else:
evt.sender.state = state
await evt.reply(f"Successfully logged in as {user.full_name} ([@{user.username}]"
f"(https://instagram.com/{user.username}), user ID: {user.pk})")
await evt.sender.try_connect()
@command_handler(needs_auth=True, help_section=SECTION_AUTH, help_text="Disconnect the bridge from"
"your Instagram account")
async def logout(evt: CommandEvent) -> None:
await evt.sender.logout()
await evt.reply("Successfully logged out")