diff --git a/.gitignore b/.gitignore
index 363f7dd..08e4297 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ __pycache__
/.eggs
profiles
+puppet/extension_files
/config.yaml
/registration.yaml
diff --git a/Dockerfile b/Dockerfile
index 3c07683..20b6f65 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -31,19 +31,19 @@ RUN apk add --no-cache \
chmod +x yq && mv yq /usr/bin/yq
-COPY requirements.txt /opt/mautrix-amp/requirements.txt
-COPY optional-requirements.txt /opt/mautrix-amp/optional-requirements.txt
-WORKDIR /opt/mautrix-amp
+COPY requirements.txt /opt/mautrix-line/requirements.txt
+COPY optional-requirements.txt /opt/mautrix-line/optional-requirements.txt
+WORKDIR /opt/mautrix-line
RUN apk add --virtual .build-deps python3-dev libffi-dev build-base \
&& pip3 install -r requirements.txt -r optional-requirements.txt \
&& apk del .build-deps
-COPY . /opt/mautrix-amp
+COPY . /opt/mautrix-line
RUN apk add git && pip3 install .[e2be] && apk del git \
# This doesn't make the image smaller, but it's needed so that the `version` command works properly
- && cp mautrix_amp/example-config.yaml . && rm -rf mautrix_amp
+ && cp mautrix_line/example-config.yaml . && rm -rf mautrix_line
VOLUME /data
ENV UID=1337 GID=1337
-CMD ["/opt/mautrix-amp/docker-run.sh"]
+CMD ["/opt/mautrix-line/docker-run.sh"]
diff --git a/docker-run.sh b/docker-run.sh
index 80f4ee3..98f46fe 100755
--- a/docker-run.sh
+++ b/docker-run.sh
@@ -2,10 +2,10 @@
# Define functions.
function fixperms {
- chown -R $UID:$GID /data /opt/mautrix-amp
+ chown -R $UID:$GID /data /opt/mautrix-line
}
-cd /opt/mautrix-amp
+cd /opt/mautrix-line
if [ ! -f /data/config.yaml ]; then
cp example-config.yaml /data/config.yaml
@@ -18,7 +18,7 @@ if [ ! -f /data/config.yaml ]; then
fi
if [ ! -f /data/registration.yaml ]; then
- python3 -m mautrix_amp -g -c /data/config.yaml -r /data/registration.yaml
+ python3 -m mautrix_line -g -c /data/config.yaml -r /data/registration.yaml
echo "Didn't find a registration file."
echo "Generated one for you."
echo "Copy that over to synapses app service directory."
@@ -27,4 +27,4 @@ if [ ! -f /data/registration.yaml ]; then
fi
fixperms
-exec su-exec $UID:$GID python3 -m mautrix_amp -c /data/config.yaml
+exec su-exec $UID:$GID python3 -m mautrix_line -c /data/config.yaml
diff --git a/mautrix_amp/commands/auth.py b/mautrix_amp/commands/auth.py
deleted file mode 100644
index 7a55257..0000000
--- a/mautrix_amp/commands/auth.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# 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 typing import Optional
-import io
-
-import qrcode
-import PIL as _
-
-from mautrix.types import MediaMessageEventContent, MessageType, ImageInfo, EventID
-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 into Android Messages")
-async def login(evt: CommandEvent) -> None:
- status = await evt.sender.client.start()
- if status.is_logged_in:
- await evt.reply("You're already logged in")
- return
- qr_event_id: Optional[EventID] = None
- async for url in evt.sender.client.login():
- buffer = io.BytesIO()
- image = qrcode.make(url)
- size = image.pixel_size
- image.save(buffer, "PNG")
- qr = buffer.getvalue()
- mxc = await evt.az.intent.upload_media(qr, "image/png", "login-qr.png", len(qr))
- content = MediaMessageEventContent(body=url, url=mxc, msgtype=MessageType.IMAGE,
- info=ImageInfo(mimetype="image/png", size=len(qr),
- width=size, height=size))
- if qr_event_id:
- content.set_edit(qr_event_id)
- await evt.az.intent.send_message(evt.room_id, content)
- else:
- content.set_reply(evt.event_id)
- qr_event_id = await evt.az.intent.send_message(evt.room_id, content)
- await evt.reply("Successfully logged in, now syncing")
- await evt.sender.sync()
diff --git a/mautrix_amp/__init__.py b/mautrix_line/__init__.py
similarity index 100%
rename from mautrix_amp/__init__.py
rename to mautrix_line/__init__.py
diff --git a/mautrix_amp/__main__.py b/mautrix_line/__main__.py
similarity index 88%
rename from mautrix_amp/__main__.py
rename to mautrix_line/__main__.py
index 770c343..4764e76 100644
--- a/mautrix_amp/__main__.py
+++ b/mautrix_line/__main__.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -30,13 +30,13 @@ from . import commands as _
class MessagesBridge(Bridge):
- module = "mautrix_amp"
- name = "mautrix-amp"
- command = "python -m mautrix-amp"
- description = ("A very hacky Matrix-SMS bridge based on using "
- "Android Messages for Web in Puppeteer.")
- repo_url = "https://github.com/tulir/mautrix-amp"
- real_user_content_key = "net.maunium.amp.puppet"
+ module = "mautrix_line"
+ name = "mautrix-line"
+ command = "python -m mautrix-line"
+ description = ("A very hacky Matrix-LINE bridge based on using"
+ "LINE's Chrome Store Extension in Puppeteer")
+ repo_url = "https://github.com/tulir/mautrix-line"
+ real_user_content_key = "net.maunium.line.puppet"
version = version
markdown_version = linkified_version
config_class = Config
diff --git a/mautrix_amp/commands/__init__.py b/mautrix_line/commands/__init__.py
similarity index 100%
rename from mautrix_amp/commands/__init__.py
rename to mautrix_line/commands/__init__.py
diff --git a/mautrix_line/commands/auth.py b/mautrix_line/commands/auth.py
new file mode 100644
index 0000000..308f913
--- /dev/null
+++ b/mautrix_line/commands/auth.py
@@ -0,0 +1,117 @@
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
+#
+# 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 typing import Optional, AsyncGenerator, Tuple
+import io
+
+import qrcode
+import PIL as _
+
+from mautrix.types import TextMessageEventContent, MediaMessageEventContent, MessageType, ImageInfo, EventID
+from mautrix.bridge.commands import HelpSection, command_handler
+
+from .typehint import CommandEvent
+
+SECTION_AUTH = HelpSection("Authentication", 10, "")
+
+
+async def login_prep(evt: CommandEvent, login_type: str) -> bool:
+ status = await evt.sender.client.start()
+ if status.is_logged_in:
+ await evt.reply("You're already logged in")
+ return False
+
+ if evt.sender.command_status is not None:
+ action = evt.sender.command_status["action"]
+ if action == "Login":
+ await evt.reply(
+ "A login is already in progress. Please follow the login instructions, "
+ "or use the `$cmdprefix+sp cancel` command to start over.")
+ else:
+ await evt.reply(f"Cannot login while a {action} command is active.")
+ return False
+
+ evt.sender.command_status = {
+ "action": "Login",
+ "login_type": login_type,
+ }
+ return True
+
+async def login_do(evt: CommandEvent, gen: AsyncGenerator[Tuple[str, str], None]) -> None:
+ qr_event_id: Optional[EventID] = None
+ pin_event_id: Optional[EventID] = None
+ failure = False
+ async for item in gen:
+ if item[0] == "qr":
+ url = item[1]
+ buffer = io.BytesIO()
+ image = qrcode.make(url)
+ size = image.pixel_size
+ image.save(buffer, "PNG")
+ qr = buffer.getvalue()
+ mxc = await evt.az.intent.upload_media(qr, "image/png", "login-qr.png", len(qr))
+ content = MediaMessageEventContent(body=url, url=mxc, msgtype=MessageType.IMAGE,
+ info=ImageInfo(mimetype="image/png", size=len(qr),
+ width=size, height=size))
+ if qr_event_id:
+ content.set_edit(qr_event_id)
+ await evt.az.intent.send_message(evt.room_id, content)
+ else:
+ content.set_reply(evt.event_id)
+ qr_event_id = await evt.az.intent.send_message(evt.room_id, content)
+ elif item[0] == "pin":
+ pin = item[1]
+ content = TextMessageEventContent(body=pin, msgtype=MessageType.NOTICE)
+ if pin_event_id:
+ content.set_edit(pin_event_id)
+ await evt.az.intent.send_message(evt.room_id, content)
+ else:
+ content.set_reply(evt.event_id)
+ pin_event_id = await evt.az.intent.send_message(evt.room_id, content)
+ elif item[0] in ("failure", "error"):
+ # TODO Handle errors differently?
+ reason = item[1]
+ failure = True
+ content = TextMessageEventContent(body=reason, msgtype=MessageType.NOTICE)
+ await evt.az.intent.send_message(evt.room_id, content)
+ # else: pass
+
+ if not failure and evt.sender.command_status:
+ await evt.reply("Successfully logged in, now syncing")
+ await evt.sender.sync()
+ # else command was cancelled or failed. Don't post message about it, "cancel" command or failure did already
+ evt.sender.command_status = None
+
+@command_handler(needs_auth=False, management_only=True, help_section=SECTION_AUTH,
+ help_text="Log into LINE via QR code")
+async def login_qr(evt: CommandEvent) -> None:
+ if not await login_prep(evt, "qr"):
+ return
+ gen = evt.sender.client.login(evt.sender)
+ await login_do(evt, gen)
+
+@command_handler(needs_auth=False, management_only=True, help_section=SECTION_AUTH,
+ help_text="Log into LINE via email/password",
+ help_args="<_email_> <_password_>")
+async def login_email(evt: CommandEvent) -> None:
+ if len(evt.args) != 2:
+ await evt.reply("Usage: `$cmdprefix+sp login `")
+ return
+ if not await login_prep(evt, "email"):
+ return
+ gen = evt.sender.client.login(
+ evt.sender,
+ login_data=dict(email=evt.args[0], password=evt.args[1]))
+ await login_do(evt, gen)
diff --git a/mautrix_amp/commands/conn.py b/mautrix_line/commands/conn.py
similarity index 89%
rename from mautrix_amp/commands/conn.py
rename to mautrix_line/commands/conn.py
index bdc79f6..6a935ff 100644
--- a/mautrix_amp/commands/conn.py
+++ b/mautrix_line/commands/conn.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -29,7 +29,7 @@ async def set_notice_room(evt: CommandEvent) -> None:
@command_handler(needs_auth=False, management_only=True, help_section=SECTION_CONNECTION,
- help_text="Check if you're logged into Android Messages")
+ help_text="Check if you're logged into LINE")
async def ping(evt: CommandEvent) -> None:
status = await evt.sender.client.start()
if status.is_logged_in:
diff --git a/mautrix_amp/commands/typehint.py b/mautrix_line/commands/typehint.py
similarity index 100%
rename from mautrix_amp/commands/typehint.py
rename to mautrix_line/commands/typehint.py
diff --git a/mautrix_amp/config.py b/mautrix_line/config.py
similarity index 95%
rename from mautrix_amp/config.py
rename to mautrix_line/config.py
index 5f09a8a..d18cb0a 100644
--- a/mautrix_amp/config.py
+++ b/mautrix_line/config.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/db/__init__.py b/mautrix_line/db/__init__.py
similarity index 100%
rename from mautrix_amp/db/__init__.py
rename to mautrix_line/db/__init__.py
diff --git a/mautrix_amp/db/message.py b/mautrix_line/db/message.py
similarity index 94%
rename from mautrix_amp/db/message.py
rename to mautrix_line/db/message.py
index b214e23..87db5a5 100644
--- a/mautrix_amp/db/message.py
+++ b/mautrix_line/db/message.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/db/portal.py b/mautrix_line/db/portal.py
similarity index 94%
rename from mautrix_amp/db/portal.py
rename to mautrix_line/db/portal.py
index ebe5915..b35a97c 100644
--- a/mautrix_amp/db/portal.py
+++ b/mautrix_line/db/portal.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/db/puppet.py b/mautrix_line/db/puppet.py
similarity index 90%
rename from mautrix_amp/db/puppet.py
rename to mautrix_line/db/puppet.py
index 41f129e..774fedd 100644
--- a/mautrix_amp/db/puppet.py
+++ b/mautrix_line/db/puppet.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/db/upgrade.py b/mautrix_line/db/upgrade.py
similarity index 91%
rename from mautrix_amp/db/upgrade.py
rename to mautrix_line/db/upgrade.py
index f3f531a..949d5cf 100644
--- a/mautrix_amp/db/upgrade.py
+++ b/mautrix_line/db/upgrade.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/db/user.py b/mautrix_line/db/user.py
similarity index 91%
rename from mautrix_amp/db/user.py
rename to mautrix_line/db/user.py
index 55e4941..de3ac86 100644
--- a/mautrix_amp/db/user.py
+++ b/mautrix_line/db/user.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/example-config.yaml b/mautrix_line/example-config.yaml
similarity index 94%
rename from mautrix_amp/example-config.yaml
rename to mautrix_line/example-config.yaml
index 57bbf13..2eeaa93 100644
--- a/mautrix_amp/example-config.yaml
+++ b/mautrix_line/example-config.yaml
@@ -39,18 +39,18 @@ appservice:
shared_secret: generate
# The unique ID of this appservice.
- id: amp
+ id: line
# Username of the appservice bot.
- bot_username: ampbot
+ bot_username: linebot
# Display name and avatar for bot. Set to "remove" to remove display name/avatar, leave empty
# to leave display name/avatar as-is.
- bot_displayname: Android Messages bridge bot
+ bot_displayname: LINE bridge bot
bot_avatar: mxc://maunium.net/VuvevQiMRlOxuBVMBNEZZrxi
# Community ID for bridged users (changes registration file) and rooms.
# Must be created manually.
#
- # Example: "+amp:example.com". Set to false to disable.
+ # Example: "+line:example.com". Set to false to disable.
community_id: false
# Authentication tokens for AS <-> HS communication. Autogenerated; do not modify.
@@ -66,11 +66,11 @@ metrics:
bridge:
# Localpart template of MXIDs for remote users.
# {userid} is replaced with the user ID (phone or name converted into a mxid-friendly format).
- username_template: "amp_{userid}"
+ username_template: "line_{userid}"
# Displayname template for remote users.
# {displayname} is replaced with the display name of the user.
# {phone} is replaced with the phone number or name of the user.
- displayname_template: "{displayname} (SMS)"
+ displayname_template: "{displayname} (LINE)"
# Maximum length of displayname
displayname_max_length: 100
@@ -129,7 +129,7 @@ bridge:
resend_bridge_info: false
# The prefix for commands. Only required in non-management rooms.
- command_prefix: "!am"
+ command_prefix: "!line"
# This bridge only supports a single user
user: "@admin:example.com"
@@ -139,7 +139,7 @@ puppeteer:
# Either unix or tcp
type: unix
# Only for type: unix
- path: /var/run/mautrix-amp/puppet.sock
+ path: /var/run/mautrix-line/puppet.sock
# Only for type: tcp
host: localhost
port: 29395
@@ -152,7 +152,7 @@ logging:
version: 1
formatters:
colored:
- (): mautrix_amp.util.ColorFormatter
+ (): mautrix_line.util.ColorFormatter
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
normal:
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
@@ -160,7 +160,7 @@ logging:
file:
class: logging.handlers.RotatingFileHandler
formatter: normal
- filename: ./mautrix-amp.log
+ filename: ./mautrix-line.log
maxBytes: 10485760
backupCount: 10
console:
diff --git a/mautrix_amp/get_version.py b/mautrix_line/get_version.py
similarity index 91%
rename from mautrix_amp/get_version.py
rename to mautrix_line/get_version.py
index 1c740d8..ea44856 100644
--- a/mautrix_amp/get_version.py
+++ b/mautrix_line/get_version.py
@@ -19,7 +19,7 @@ def run(cmd):
if os.path.exists(".git") and shutil.which("git"):
try:
git_revision = run(["git", "rev-parse", "HEAD"]).strip().decode("ascii")
- git_revision_url = f"https://github.com/tulir/mautrix-amp/commit/{git_revision}"
+ git_revision_url = f"https://github.com/tulir/mautrix-line/commit/{git_revision}"
git_revision = git_revision[:8]
except (subprocess.SubprocessError, OSError):
git_revision = "unknown"
diff --git a/mautrix_amp/matrix.py b/mautrix_line/matrix.py
similarity index 89%
rename from mautrix_amp/matrix.py
rename to mautrix_line/matrix.py
index 4c5e8ea..4d1a3cb 100644
--- a/mautrix_amp/matrix.py
+++ b/mautrix_line/matrix.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -47,4 +47,4 @@ class MatrixHandler(BaseMatrixHandler):
inviter.notice_room = room_id
await inviter.update()
await self.az.intent.send_notice(room_id, "This room has been marked as your "
- "Android Messages bridge notice room.")
+ "LINE bridge notice room.")
diff --git a/mautrix_amp/portal.py b/mautrix_line/portal.py
similarity index 98%
rename from mautrix_amp/portal.py
rename to mautrix_line/portal.py
index ce0b751..4a1fa2d 100644
--- a/mautrix_amp/portal.py
+++ b/mautrix_line/portal.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -268,7 +268,7 @@ class Portal(DBPortal, BasePortal):
@property
def bridge_info_state_key(self) -> str:
- return f"net.maunium.amp://androidmessages/{self.chat_id}"
+ return f"net.maunium.line://line/{self.chat_id}"
@property
def bridge_info(self) -> Dict[str, Any]:
@@ -276,8 +276,8 @@ class Portal(DBPortal, BasePortal):
"bridgebot": self.az.bot_mxid,
"creator": self.main_intent.mxid,
"protocol": {
- "id": "androidmessages",
- "displayname": "Android Messages",
+ "id": "line",
+ "displayname": "LINE",
"avatar_url": self.config["appservice.bot_avatar"],
},
"channel": {
diff --git a/mautrix_amp/puppet.py b/mautrix_line/puppet.py
similarity index 92%
rename from mautrix_amp/puppet.py
rename to mautrix_line/puppet.py
index da47cbe..2301d28 100644
--- a/mautrix_amp/puppet.py
+++ b/mautrix_line/puppet.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -56,8 +56,9 @@ class Puppet(DBPuppet, BasePuppet):
cls.mxid_template = SimpleTemplate(cls.config["bridge.username_template"], "userid",
prefix="@", suffix=f":{cls.hs_domain}", type=str)
secret = cls.config["bridge.login_shared_secret"]
- cls.login_shared_secret_map[cls.hs_domain] = secret.encode("utf-8") if secret else None
- cls.login_device_name = "Android Messages Bridge"
+ if secret:
+ cls.login_shared_secret_map[cls.hs_domain] = secret.encode("utf-8")
+ cls.login_device_name = "LINE Bridge"
async def update_info(self, info: Participant) -> None:
update = False
diff --git a/mautrix_amp/rpc/__init__.py b/mautrix_line/rpc/__init__.py
similarity index 100%
rename from mautrix_amp/rpc/__init__.py
rename to mautrix_line/rpc/__init__.py
diff --git a/mautrix_amp/rpc/client.py b/mautrix_line/rpc/client.py
similarity index 62%
rename from mautrix_amp/rpc/client.py
rename to mautrix_line/rpc/client.py
index 12b243e..e5232ed 100644
--- a/mautrix_amp/rpc/client.py
+++ b/mautrix_line/rpc/client.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -13,20 +13,17 @@
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see .
-from typing import AsyncGenerator, TypedDict, List, Dict, Callable, Awaitable, Any
+from typing import AsyncGenerator, TypedDict, List, Tuple, Dict, Callable, Awaitable, Any
from collections import deque
import asyncio
from .rpc import RPCClient
from .types import ChatListInfo, ChatInfo, Message, StartStatus
+from mautrix_line.rpc.types import RPCError
-class QRCommand(TypedDict):
- url: str
-
-
-class LoginComplete(Exception):
- pass
+class LoginCommand(TypedDict):
+ content: str
class Client(RPCClient):
@@ -66,22 +63,48 @@ class Client(RPCClient):
self.add_event_handler("message", wrapper)
- async def login(self) -> AsyncGenerator[str, None]:
+ # TODO Type hint for sender
+ async def login(self, sender, **login_data) -> AsyncGenerator[Tuple[str, str], None]:
+ login_data["login_type"] = sender.command_status["login_type"]
+
data = deque()
event = asyncio.Event()
- async def qr_handler(req: QRCommand) -> None:
- data.append(req["url"])
+ async def qr_handler(req: LoginCommand) -> None:
+ data.append(("qr", req["url"]))
event.set()
+ async def pin_handler(req: LoginCommand) -> None:
+ data.append(("pin", req["pin"]))
+ event.set()
+
+ async def failure_handler(req: LoginCommand) -> None:
+ data.append(("failure", req["reason"]))
+ event.set()
+
+ async def cancel_watcher() -> None:
+ try:
+ while sender.command_status is not None:
+ await asyncio.sleep(1)
+ await self._raw_request("cancel_login")
+ except asyncio.CancelledError:
+ pass
+ cancel_watcher_task = asyncio.create_task(cancel_watcher())
+
def login_handler(_fut: asyncio.Future) -> None:
+ cancel_watcher_task.cancel()
+ e = _fut.exception()
+ if e is not None:
+ data.append(("error", str(e)))
data.append(None)
event.set()
- login_future = await self._raw_request("login")
+ login_future = await self._raw_request("login", **login_data)
login_future.add_done_callback(login_handler)
self.add_event_handler("qr", qr_handler)
+ self.add_event_handler("pin", pin_handler)
+ self.add_event_handler("failure", failure_handler)
try:
while True:
await event.wait()
@@ -93,3 +116,5 @@ class Client(RPCClient):
event.clear()
finally:
self.remove_event_handler("qr", qr_handler)
+ self.remove_event_handler("pin", pin_handler)
+ self.remove_event_handler("failure", failure_handler)
diff --git a/mautrix_amp/rpc/rpc.py b/mautrix_line/rpc/rpc.py
similarity index 97%
rename from mautrix_amp/rpc/rpc.py
rename to mautrix_line/rpc/rpc.py
index 077e0ba..0603f1e 100644
--- a/mautrix_amp/rpc/rpc.py
+++ b/mautrix_line/rpc/rpc.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/rpc/types.py b/mautrix_line/rpc/types.py
similarity index 90%
rename from mautrix_amp/rpc/types.py
rename to mautrix_line/rpc/types.py
index cf8138a..9705cd6 100644
--- a/mautrix_amp/rpc/types.py
+++ b/mautrix_line/rpc/types.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/user.py b/mautrix_line/user.py
similarity index 96%
rename from mautrix_amp/user.py
rename to mautrix_line/user.py
index 2584fcc..934dc90 100644
--- a/mautrix_amp/user.py
+++ b/mautrix_line/user.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -30,7 +30,7 @@ from . import puppet as pu, portal as po
if TYPE_CHECKING:
from .__main__ import MessagesBridge
-METRIC_CONNECTED = Gauge("bridge_connected", "Users connected to Android Messages")
+METRIC_CONNECTED = Gauge("bridge_connected", "Users connected to LINE")
class User(DBUser, BaseUser):
@@ -49,6 +49,7 @@ class User(DBUser, BaseUser):
def __init__(self, mxid: UserID, notice_room: Optional[RoomID] = None) -> None:
super().__init__(mxid=mxid, notice_room=notice_room)
self._notice_room_lock = asyncio.Lock()
+ self.command_status = None
self.is_whitelisted = self.is_admin = self.config["bridge.user"] == mxid
self.log = self.log.getChild(self.mxid)
self._metric_value = defaultdict(lambda: False)
diff --git a/mautrix_amp/util/__init__.py b/mautrix_line/util/__init__.py
similarity index 100%
rename from mautrix_amp/util/__init__.py
rename to mautrix_line/util/__init__.py
diff --git a/mautrix_amp/util/color_log.py b/mautrix_line/util/color_log.py
similarity index 87%
rename from mautrix_amp/util/color_log.py
rename to mautrix_line/util/color_log.py
index 29e30f3..968cc0a 100644
--- a/mautrix_amp/util/color_log.py
+++ b/mautrix_line/util/color_log.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
diff --git a/mautrix_amp/version.py b/mautrix_line/version.py
similarity index 100%
rename from mautrix_amp/version.py
rename to mautrix_line/version.py
diff --git a/mautrix_amp/web/__init__.py b/mautrix_line/web/__init__.py
similarity index 100%
rename from mautrix_amp/web/__init__.py
rename to mautrix_line/web/__init__.py
diff --git a/mautrix_amp/web/provisioning_api.py b/mautrix_line/web/provisioning_api.py
similarity index 92%
rename from mautrix_amp/web/provisioning_api.py
rename to mautrix_line/web/provisioning_api.py
index e3da47e..7398595 100644
--- a/mautrix_amp/web/provisioning_api.py
+++ b/mautrix_line/web/provisioning_api.py
@@ -1,5 +1,5 @@
-# mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-# Copyright (C) 2020 Tulir Asokan
+# mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+# Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
#
# 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
@@ -64,8 +64,8 @@ class ProvisioningAPI:
return None
for part in auth_parts:
part = part.strip()
- if part.startswith("net.maunium.amp.auth-"):
- return part[len("net.maunium.amp.auth-"):]
+ if part.startswith("net.maunium.line.auth-"):
+ return part[len("net.maunium.line.auth-"):]
return None
def check_token(self, request: web.Request) -> Awaitable['u.User']:
@@ -94,7 +94,7 @@ class ProvisioningAPI:
user = await self.check_token(request)
data = {
"mxid": user.mxid,
- "amp": {
+ "line": {
"connected": True,
} if await user.is_logged_in() else None,
}
@@ -107,7 +107,7 @@ class ProvisioningAPI:
if status.is_logged_in:
raise web.HTTPConflict(text='{"error": "Already logged in"}', headers=self._headers)
- ws = web.WebSocketResponse(protocols=["net.maunium.amp.login"])
+ ws = web.WebSocketResponse(protocols=["net.maunium.line.login"])
await ws.prepare(request)
try:
async for url in user.client.login():
diff --git a/puppet/Dockerfile b/puppet/Dockerfile
index e9bc1ff..e00d63b 100644
--- a/puppet/Dockerfile
+++ b/puppet/Dockerfile
@@ -7,11 +7,11 @@ RUN echo $'\
RUN apk add --no-cache chromium@edge
-WORKDIR /opt/mautrix-amp/puppet
-RUN chown node:node /opt/mautrix-amp/puppet
+WORKDIR /opt/mautrix-line/puppet
+RUN chown node:node /opt/mautrix-line/puppet
USER node
COPY package.json yarn.lock ./
RUN yarn --production && rm -rf node_modules/puppeteer/.local-chromium
-COPY . /opt/mautrix-amp/puppet
+COPY . /opt/mautrix-line/puppet
CMD ["yarn", "start", "--config", "/data/config.json", "--browser", "/usr/lib/chromium/chrome", "--no-sandbox"]
diff --git a/puppet/example-config.json b/puppet/example-config.json
index 85b1a28..41cfd46 100644
--- a/puppet/example-config.json
+++ b/puppet/example-config.json
@@ -4,5 +4,6 @@
"path": "/var/run/mautrix-amp/puppet.sock"
},
"profile_dir": "./profiles",
- "url": "chrome-extension:///index.html"
+ "url": "chrome-extension:///index.html",
+ "extension_dir": "./extension_files"
}
diff --git a/puppet/package.json b/puppet/package.json
index 25fa99f..f0c24ec 100644
--- a/puppet/package.json
+++ b/puppet/package.json
@@ -1,23 +1,23 @@
{
- "name": "mautrix-amp-puppeteer",
+ "name": "mautrix-line-puppeteer",
"version": "0.1.0",
- "description": "Puppeteer module for mautrix-amp",
+ "description": "Puppeteer module for mautrix-line",
"repository": {
"type": "git",
- "url": "git+https://mau.dev/tulir/mautrix-amp.git"
+ "url": "git+https://mau.dev/tulir/mautrix-line.git"
},
"type": "module",
"main": "src/main.js",
"author": "Tulir Asokan ",
"license": "AGPL-3.0-or-later",
- "homepage": "https://mau.dev/tulir/mautrix-amp",
+ "homepage": "https://mau.dev/tulir/mautrix-line",
"scripts": {
"start": "node ./src/main.js"
},
"dependencies": {
"arg": "^4.1.3",
"chrono-node": "^2.1.7",
- "puppeteer": "5.1.0"
+ "puppeteer": "5.5.0"
},
"devDependencies": {
"babel-eslint": "^10.1.0",
diff --git a/puppet/src/api.js b/puppet/src/api.js
index df7058a..ca00e1d 100644
--- a/puppet/src/api.js
+++ b/puppet/src/api.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
diff --git a/puppet/src/client.js b/puppet/src/client.js
index 978f0a6..f60a874 100644
--- a/puppet/src/client.js
+++ b/puppet/src/client.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
@@ -116,6 +116,24 @@ export default class Client {
})
}
+ sendPIN(pin) {
+ this.log(`Sending PIN ${pin} to client`)
+ return this._write({
+ id: --this.notificationID,
+ command: "pin",
+ pin,
+ })
+ }
+
+ sendFailure(reason) {
+ this.log(`Sending failure "${reason}" to client`)
+ return this._write({
+ id: --this.notificationID,
+ command: "failure",
+ reason,
+ })
+ }
+
handleStart = async (req) => {
let started = false
if (this.puppet === null) {
@@ -205,7 +223,8 @@ export default class Client {
start: this.handleStart,
stop: this.handleStop,
disconnect: () => this.stop(),
- login: () => this.puppet.waitForLogin(),
+ login: req => this.puppet.waitForLogin(req.login_type, req.login_data),
+ cancel_login: () => this.puppet.cancelLogin(),
send: req => this.puppet.sendMessage(req.chat_id, req.text),
set_last_message_ids: req => this.puppet.setLastMessageIDs(req.msg_ids),
get_chats: () => this.puppet.getRecentChats(),
diff --git a/puppet/src/contentscript.js b/puppet/src/contentscript.js
index c81612a..c19dd42 100644
--- a/puppet/src/contentscript.js
+++ b/puppet/src/contentscript.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
@@ -32,6 +32,20 @@ window.__mautrixReceiveChanges = function (changes) {}
* @return {Promise}
*/
window.__mautrixReceiveQR = function (url) {}
+/**
+ * @return {Promise}
+ */
+window.__mautrixSendEmailCredentials = function () {}
+/**
+ * @param {string} pin - The login PIN.
+ * @return {Promise}
+ */
+window.__mautrixReceivePIN = function (pin) {}
+/**
+ * @param {Element} button - The button to click when a QR code or PIN expires.
+ * @return {Promise}
+ */
+window.__mautrixExpiry = function (button) {}
/**
* @param {number} id - The ID of the message that was sent
* @return {Promise}
@@ -41,7 +55,11 @@ window.__mautrixReceiveMessageID = function(id) {}
class MautrixController {
constructor() {
this.chatListObserver = null
- this.qrCodeObserver = null
+ this.qrChangeObserver = null
+ this.qrAppearObserver = null
+ this.emailAppearObserver = null
+ this.pinAppearObserver = null
+ this.expiryObserver = null
}
/**
@@ -312,6 +330,7 @@ class MautrixController {
if (this.chatListObserver !== null) {
this.removeChatListObserver()
}
+ /* TODO
this.chatListObserver = new MutationObserver(mutations => {
try {
this._observeChatListMutations(mutations)
@@ -320,6 +339,7 @@ class MautrixController {
}
})
this.chatListObserver.observe(element, { childList: true, subtree: true })
+ */
console.debug("Started chat list observer")
}
@@ -334,27 +354,132 @@ class MautrixController {
}
}
- addQRObserver(element) {
- if (this.qrCodeObserver !== null) {
- this.removeQRObserver()
+ addQRChangeObserver(element) {
+ if (this.qrChangeObserver !== null) {
+ this.removeQRChangeObserver()
}
- this.qrCodeObserver = new MutationObserver(changes => {
+ this.qrChangeObserver = new MutationObserver(changes => {
for (const change of changes) {
- if (change.attributeName === "data-qr-code" && change.target instanceof Element) {
- window.__mautrixReceiveQR(change.target.getAttribute("data-qr-code"))
+ if (change.attributeName === "title" && change.target instanceof Element) {
+ window.__mautrixReceiveQR(change.target.getAttribute("title"))
}
}
})
- this.qrCodeObserver.observe(element, {
+ this.qrChangeObserver.observe(element, {
attributes: true,
- attributeFilter: ["data-qr-code"],
+ attributeFilter: ["title"],
})
}
- removeQRObserver() {
- if (this.qrCodeObserver !== null) {
- this.qrCodeObserver.disconnect()
- this.qrCodeObserver = null
+ removeQRChangeObserver() {
+ if (this.qrChangeObserver !== null) {
+ this.qrChangeObserver.disconnect()
+ this.qrChangeObserver = null
+ }
+ }
+
+ addQRAppearObserver(element) {
+ if (this.qrAppearObserver !== null) {
+ this.removeQRAppearObserver()
+ }
+ this.qrAppearObserver = new MutationObserver(changes => {
+ for (const change of changes) {
+ for (const node of change.addedNodes) {
+ const qrElement = node.querySelector("#login_qrcode_area div[title]")
+ if (qrElement) {
+ window.__mautrixReceiveQR(qrElement.title)
+ window.__mautrixController.addQRChangeObserver(element)
+ return
+ }
+ }
+ }
+ })
+ this.qrAppearObserver.observe(element, {
+ childList: true,
+ })
+ }
+
+ removeQRAppearObserver() {
+ if (this.qrAppearObserver !== null) {
+ this.qrAppearObserver.disconnect()
+ this.qrAppearObserver = null
+ }
+ }
+
+ addEmailAppearObserver(element, login_type) {
+ if (this.emailAppearObserver !== null) {
+ this.removeEmailAppearObserver()
+ }
+ this.emailAppearObserver = new MutationObserver(changes => {
+ for (const change of changes) {
+ for (const node of change.addedNodes) {
+ const emailElement = node.querySelector("#login_email_btn")
+ if (emailElement) {
+ window.__mautrixSendEmailCredentials()
+ return
+ }
+ }
+ }
+ })
+ this.emailAppearObserver.observe(element, {
+ childList: true,
+ })
+ }
+
+ removeEmailAppearObserver() {
+ if (this.emailAppearObserver !== null) {
+ this.emailAppearObserver.disconnect()
+ this.emailAppearObserver = null
+ }
+ }
+
+ addPINAppearObserver(element, login_type) {
+ if (this.pinAppearObserver !== null) {
+ this.removePINAppearObserver()
+ }
+ this.pinAppearObserver = new MutationObserver(changes => {
+ for (const change of changes) {
+ for (const node of change.addedNodes) {
+ const pinElement = node.querySelector("div.mdCMN01Code")
+ if (pinElement) {
+ window.__mautrixReceivePIN(pinElement.innerText)
+ return
+ }
+ }
+ }
+ })
+ this.pinAppearObserver.observe(element, {
+ childList: true,
+ })
+ }
+
+ removePINAppearObserver() {
+ if (this.pinAppearObserver !== null) {
+ this.pinAppearObserver.disconnect()
+ this.pinAppearObserver = null
+ }
+ }
+
+ addExpiryObserver(element) {
+ if (this.expiryObserver !== null) {
+ this.removeExpiryObserver()
+ }
+ const button = element.querySelector("dialog button")
+ this.expiryObserver = new MutationObserver(changes => {
+ if (changes.length == 1 && !changes[0].target.getAttribute("class").includes("MdNonDisp")) {
+ window.__mautrixExpiry(button)
+ }
+ })
+ this.expiryObserver.observe(element, {
+ attributes: true,
+ attributeFilter: ["class"],
+ })
+ }
+
+ removeExpiryObserver() {
+ if (this.expiryObserver !== null) {
+ this.expiryObserver.disconnect()
+ this.expiryObserver = null
}
}
}
diff --git a/puppet/src/main.js b/puppet/src/main.js
index 88cee9f..c395ca5 100644
--- a/puppet/src/main.js
+++ b/puppet/src/main.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
-// Copyright (C) 2020 Tulir Asokan, Andrew Ferrazzutti
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
@@ -38,6 +38,7 @@ const config = JSON.parse(fs.readFileSync(configPath).toString())
MessagesPuppeteer.profileDir = config.profile_dir || MessagesPuppeteer.profileDir
MessagesPuppeteer.disableDebug = !!config.disable_debug
MessagesPuppeteer.url = config.url
+MessagesPuppeteer.extensionDir = config.extension_dir || MessagesPuppeteer.extensionDir
const api = new PuppetAPI(config.listen)
diff --git a/puppet/src/puppet.js b/puppet/src/puppet.js
index 37428ed..25c5764 100644
--- a/puppet/src/puppet.js
+++ b/puppet/src/puppet.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
@@ -27,8 +27,9 @@ export default class MessagesPuppeteer {
static executablePath = undefined
static disableDebug = false
static noSandbox = false
- static viewport = { width: 1920, height: 1080 }
+ //static viewport = { width: 1920, height: 1080 }
static url = undefined
+ static extensionDir = 'extension_files'
/**
*
@@ -61,14 +62,13 @@ export default class MessagesPuppeteer {
* Start the browser and open the messages for web page.
* This must be called before doing anything else.
*/
- async start(debug = false) {
+ async start() {
this.log("Launching browser")
- const pathToExtension = require('path').join(__dirname, 'extension_files');
- const extensionArgs = [
- `--disable-extensions-except=${pathToExtension}`,
- `--load-extension=${pathToExtension}`
- ];
+ const extensionArgs = [
+ `--disable-extensions-except=${MessagesPuppeteer.extensionDir}`,
+ `--load-extension=${MessagesPuppeteer.extensionDir}`
+ ]
this.browser = await puppeteer.launch({
executablePath: MessagesPuppeteer.executablePath,
@@ -85,67 +85,182 @@ export default class MessagesPuppeteer {
this.page = await this.browser.newPage()
}
this.log("Opening", MessagesPuppeteer.url)
- await this.page.goto(MessagesPuppeteer.url)
+ await this.page.setBypassCSP(true) // Needed to load content scripts
+ await this._preparePage(true)
- this.log("Injecting content script")
- await this.page.addScriptTag({ path: "./src/contentscript.js", type: "module" })
this.log("Exposing functions")
await this.page.exposeFunction("__mautrixReceiveQR", this._receiveQRChange.bind(this))
+ await this.page.exposeFunction("__mautrixSendEmailCredentials", this._sendEmailCredentials.bind(this))
+ await this.page.exposeFunction("__mautrixReceivePIN", this._receivePIN.bind(this))
+ await this.page.exposeFunction("__mautrixExpiry", this._receiveExpiry.bind(this))
+ /* TODO
await this.page.exposeFunction("__mautrixReceiveMessageID",
id => this.sentMessageIDs.add(id))
await this.page.exposeFunction("__mautrixReceiveChanges",
this._receiveChatListChanges.bind(this))
await this.page.exposeFunction("__chronoParseDate", chrono.parseDate)
+ */
- this.log("Waiting for load")
- // Wait for the page to load (either QR code for login or chat list when already logged in)
- await Promise.race([
- this.page.waitForSelector("mw-main-container mws-conversations-list .conv-container",
- { visible: true, timeout: 60000 }),
- this.page.waitForSelector("mw-authentication-container mw-qr-code",
- { visible: true, timeout: 60000 }),
- this.page.waitForSelector("mw-unable-to-connect-container",
- { visible: true, timeout: 60000 }),
- ])
+ // NOTE Must *always* re-login on a browser session, so no need to check if already logged in
+ this.loginRunning = false
+ this.loginCancelled = false
this.taskQueue.start()
- if (await this.isLoggedIn()) {
- await this.startObserving()
- }
this.log("Startup complete")
}
+ async _preparePage(navigateTo) {
+ if (navigateTo) {
+ await this.page.goto(MessagesPuppeteer.url)
+ } else {
+ await this.page.reload()
+ }
+ this.log("Injecting content script")
+ await this.page.addScriptTag({ path: "./src/contentscript.js", type: "module" })
+ }
+
/**
- * Wait for the session to be logged in and monitor QR code changes while it's not.
+ * Wait for the session to be logged in and monitor changes while it's not.
*/
- async waitForLogin() {
+ async waitForLogin(login_type, login_data) {
if (await this.isLoggedIn()) {
return
}
- const qrSelector = "mw-authentication-container mw-qr-code"
- if (!await this.page.$("mat-slide-toggle.mat-checked")) {
- this.log("Clicking Remember Me button")
- await this.page.click("mat-slide-toggle:not(.mat-checked) > label")
- } else {
- this.log("Remember Me button already clicked")
+ this.loginRunning = true
+ this.loginCancelled = false
+
+ const loginContentArea = await this.page.waitForSelector("#login_content")
+
+ switch (login_type) {
+ case "qr": {
+ this.log("Running QR login")
+ const qrButton = await this.page.waitForSelector("#login_qr_btn")
+ await qrButton.click()
+
+ const qrElement = await this.page.waitForSelector("#login_qrcode_area div[title]", {visible: true})
+ const currentQR = await this.page.evaluate(element => element.title, qrElement)
+ this._receiveQRChange(currentQR)
+
+ await this.page.evaluate(
+ element => window.__mautrixController.addQRChangeObserver(element), qrElement)
+ await this.page.evaluate(
+ element => window.__mautrixController.addQRAppearObserver(element), loginContentArea)
+
+ break
}
- this.log("Fetching current QR code")
- const currentQR = await this.page.$eval(qrSelector,
- element => element.getAttribute("data-qr-code"))
- this._receiveQRChange(currentQR)
- this.log("Adding QR observer")
- await this.page.$eval(qrSelector,
- element => window.__mautrixController.addQRObserver(element))
- this.log("Waiting for login")
- await this.page.waitForSelector("mws-conversations-list .conv-container", {
- visible: true,
- timeout: 0,
- })
- this.log("Removing QR observer")
- await this.page.evaluate(() => window.__mautrixController.removeQRObserver())
+ case "email": {
+ this.log("Running email login")
+ if (!login_data) {
+ _sendLoginFailure("No login credentials provided for email login")
+ return
+ }
+
+ const emailButton = await this.page.waitForSelector("#login_email_btn")
+ await emailButton.click()
+
+ const emailArea = await this.page.waitForSelector("#login_email_area", {visible: true})
+ this.login_email = login_data["email"]
+ this.login_password = login_data["password"]
+ this._sendEmailCredentials()
+
+ await this.page.evaluate(
+ element => window.__mautrixController.addEmailAppearObserver(element), loginContentArea)
+
+ break
+ }
+ // TODO Phone number login
+ default:
+ _sendLoginFailure(`Invalid login type: ${login_type}`)
+ return
+ }
+
+ await this.page.evaluate(
+ element => window.__mautrixController.addPINAppearObserver(element), loginContentArea)
+ await this.page.$eval("#layer_contents",
+ element => window.__mautrixController.addExpiryObserver(element))
+
+ this.log("Waiting for login response")
+ let doneWaiting = false
+ let loginSuccess = false
+ const cancelableResolve = (promiseWithShortTimeout) => {
+ const executor = (resolve, reject) => {
+ promiseWithShortTimeout.then(
+ value => {
+ this.log(`Done: ${value}`)
+ doneWaiting = true
+ resolve(value)
+ },
+ reason => {
+ if (!doneWaiting) {
+ this.log(`Not done, waiting some more. ${reason}`)
+ setTimeout(executor, 3000, resolve, reject)
+ } else {
+ this.log(`Final fail. ${reason}`)
+ resolve()
+ }
+ }
+ )
+ }
+ return new Promise(executor)
+ }
+
+ const result = await Promise.race([
+ this.page.waitForSelector("#wrap_message_sync", {timeout: 2000})
+ .then(element => {
+ loginSuccess = true
+ return element
+ }),
+ this.page.waitForSelector("#login_incorrect", {visible: true, timeout: 2000})
+ .then(element => element.innerText),
+ this._waitForLoginCancel(),
+ ].map(promise => cancelableResolve(promise)))
+
+ this.log("Removing observers")
+ await this.page.evaluate(() => window.__mautrixController.removeQRChangeObserver())
+ await this.page.evaluate(() => window.__mautrixController.removeLoginChildrenObserver(element))
+ await this.page.evaluate(() => window.__mautrixController.removeExpiryObserver())
+ delete this.login_email
+ delete this.login_password
+
+ if (!loginSuccess) {
+ _sendLoginFailure(result)
+ return
+ }
+
+ this.log("Waiting for sync")
+ await this.page.waitForFunction(
+ messageSyncElement => {
+ const text = messageSyncElement.innerText
+ return text == 'Syncing messages... 100%'
+ },
+ {},
+ result)
+
await this.startObserving()
+ this.loginRunning = false
this.log("Login complete")
}
+ /**
+ * Cancel an ongoing login attempt.
+ */
+ async cancelLogin() {
+ if (this.loginRunning) {
+ this.loginCancelled = true
+ //await this._preparePage(false)
+ }
+ }
+
+ _waitForLoginCancel() {
+ return new Promise((resolve, reject) => {
+ console.log(`>>>>> ${this.loginCancelled}`)
+ if (this.loginCancelled) {
+ resolve()
+ } else {
+ reject()
+ }
+ })
+ }
+
/**
* Close the browser.
*/
@@ -166,14 +281,17 @@ export default class MessagesPuppeteer {
* @return {Promise} - Whether or not the session is logged in.
*/
async isLoggedIn() {
- return await this.page.$("mw-main-container mws-conversations-list") !== null
+ return await this.page.$("#wrap_message_sync") !== null
}
async isPermanentlyDisconnected() {
- return await this.page.$("mw-unable-to-connect-container") !== null
+ // TODO
+ //return await this.page.$("mw-unable-to-connect-container") !== null
+ return false
}
async isOpenSomewhereElse() {
+ /* TODO
try {
const text = await this.page.$eval("mws-dialog mat-dialog-content div",
elem => elem.textContent)
@@ -181,16 +299,15 @@ export default class MessagesPuppeteer {
} catch (err) {
return false
}
- }
-
- async clickDialogButton() {
- await this.page.click("mws-dialog mat-dialog-actions button")
+ */
+ return false
}
async isDisconnected() {
if (!await this.isLoggedIn()) {
return true
}
+ /* TODO
const offlineIndicators = await Promise.all([
this.page.$("mw-main-nav mw-banner mw-error-banner"),
this.page.$("mw-main-nav mw-banner mw-information-banner[title='Connecting']"),
@@ -198,6 +315,8 @@ export default class MessagesPuppeteer {
this.isOpenSomewhereElse(),
])
return offlineIndicators.some(indicator => Boolean(indicator))
+ */
+ return false
}
/**
@@ -206,8 +325,11 @@ export default class MessagesPuppeteer {
* @return {Promise<[ChatListInfo]>} - List of chat IDs in order of most recent message.
*/
async getRecentChats() {
+ /* TODO
return await this.page.$eval("mws-conversations-list .conv-container",
elem => window.__mautrixController.parseChatList(elem))
+ */
+ return null
}
/**
@@ -266,7 +388,7 @@ export default class MessagesPuppeteer {
async startObserving() {
this.log("Adding chat list observer")
- await this.page.$eval("mws-conversations-list .conv-container",
+ await this.page.$eval("#wrap_chat_list",
element => window.__mautrixController.addChatListObserver(element))
}
@@ -276,7 +398,9 @@ export default class MessagesPuppeteer {
}
_listItemSelector(id) {
- return `mws-conversation-list-item > a.list-item[href="/web/conversations/${id}"]`
+ // TODO
+ //return `mws-conversation-list-item > a.list-item[href="/web/conversations/${id}"]`
+ return ''
}
async _switchChatUnsafe(id) {
@@ -365,6 +489,20 @@ export default class MessagesPuppeteer {
}
}
+ async _sendEmailCredentials() {
+ this.log("Inputting login credentials")
+
+ // Triple-click email input field to select all existing text and replace it on type
+ const emailInput = await this.page.$("#line_login_email")
+ await emailInput.click({clickCount: 3})
+ await emailInput.type(this.login_email)
+
+ // Password input field always starts empty, so no need to select its text first
+ await this.page.type("#line_login_pwd", this.login_password)
+
+ await this.page.click("button#login_btn")
+ }
+
_receiveQRChange(url) {
if (this.client) {
this.client.sendQRCode(url).catch(err =>
@@ -373,4 +511,28 @@ export default class MessagesPuppeteer {
this.log("No client connected, not sending new QR")
}
}
+
+ _receivePIN(pin) {
+ if (this.client) {
+ this.client.sendPIN(`Your PIN is: ${pin}`).catch(err =>
+ this.error("Failed to send new PIN to client:", err))
+ } else {
+ this.log("No client connected, not sending new PIN")
+ }
+ }
+
+ _sendLoginFailure(reason) {
+ this.error(`Login failure: ${reason ? reason : 'cancelled'}`)
+ if (this.client) {
+ this.client.sendFailure(reason).catch(err =>
+ this.error("Failed to send failure reason to client:", err))
+ } else {
+ this.log("No client connected, not sending failure reason")
+ }
+ }
+
+ async _receiveExpiry(button) {
+ this.log("Something expired, clicking OK button to continue")
+ await this.page.click(button)
+ }
}
diff --git a/puppet/src/taskqueue.js b/puppet/src/taskqueue.js
index bf97a9a..77d07a6 100644
--- a/puppet/src/taskqueue.js
+++ b/puppet/src/taskqueue.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
diff --git a/puppet/src/util.js b/puppet/src/util.js
index 9362dc0..efbcfe4 100644
--- a/puppet/src/util.js
+++ b/puppet/src/util.js
@@ -1,5 +1,5 @@
-// mautrix-amp - A very hacky Matrix-SMS bridge based on using Android Messages for Web in Puppeteer
-// Copyright (C) 2020 Tulir Asokan
+// mautrix-line - A very hacky Matrix-LINE bridge based on using LINE's Chrome Store Extension in Puppeteer
+// Copyright (C) 2021 Tulir Asokan, Andrew Ferrazzutti
//
// 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
diff --git a/puppet/yarn.lock b/puppet/yarn.lock
index eeffcd0..6c7af9c 100644
--- a/puppet/yarn.lock
+++ b/puppet/yarn.lock
@@ -364,10 +364,10 @@ define-properties@^1.1.2, define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
-devtools-protocol@0.0.767361:
- version "0.0.767361"
- resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.767361.tgz#5977f2558b84f9df36f62501bdddb82f3ae7b66b"
- integrity sha512-ziRTdhEVQ9jEwedaUaXZ7kl9w9TF/7A3SXQ0XuqrJB+hMS62POHZUWTbumDN2ehRTfvWqTPc2Jw4gUl/jggmHA==
+devtools-protocol@0.0.818844:
+ version "0.0.818844"
+ resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.818844.tgz#d1947278ec85b53e4c8ca598f607a28fa785ba9e"
+ integrity sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg==
doctrine@1.5.0:
version "1.5.0"
@@ -918,11 +918,6 @@ lodash@^4.17.14, lodash@^4.17.19:
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52"
integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==
-mime@^2.0.3:
- version "2.4.6"
- resolved "https://registry.yarnpkg.com/mime/-/mime-2.4.6.tgz#e5b407c90db442f2beb5b162373d07b69affa4d1"
- integrity sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA==
-
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@@ -935,11 +930,6 @@ minimist@^1.2.0, minimist@^1.2.5:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
-mitt@^2.0.1:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/mitt/-/mitt-2.1.0.tgz#f740577c23176c6205b121b2973514eade1b2230"
- integrity sha512-ILj2TpLiysu2wkBbWjAmww7TkZb65aiQO+DkVdUTBpBXq+MHYiETENkKFMtsJZX1Lf4pe4QOrTSjIfUwN5lRdg==
-
mkdirp-classic@^0.5.2:
version "0.5.3"
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
@@ -967,6 +957,11 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
+node-fetch@^2.6.1:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
+ integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
+
normalize-package-data@^2.3.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -1162,17 +1157,16 @@ punycode@^2.1.0:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
-puppeteer@5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.1.0.tgz#e7bae2caa6e3a13a622755e4c27689d9812c38ca"
- integrity sha512-IZBFG8XcA+oHxYo5rEpJI/HQignUis2XPijPoFpNxla2O+WufonGsUsSqrhRXgBKOME5zNfhRdUY2LvxAiKlhw==
+puppeteer@5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-5.5.0.tgz#331a7edd212ca06b4a556156435f58cbae08af00"
+ integrity sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==
dependencies:
debug "^4.1.0"
- devtools-protocol "0.0.767361"
+ devtools-protocol "0.0.818844"
extract-zip "^2.0.0"
https-proxy-agent "^4.0.0"
- mime "^2.0.3"
- mitt "^2.0.1"
+ node-fetch "^2.6.1"
pkg-dir "^4.2.0"
progress "^2.0.1"
proxy-from-env "^1.0.0"