forked from fair/matrix-puppeteer-line
Compare commits
No commits in common. "1fffbc625ca71e4828145a2c2577417f699a5ac3" and "9d1d6e379c41f259eebf402136ffff062f639d9c" have entirely different histories.
1fffbc625c
...
9d1d6e379c
|
@ -2,7 +2,7 @@
|
|||
A very hacky Matrix-LINE bridge based on running LINE's Chrome extension in Puppeteer.
|
||||
Fork of [https://mau.dev/tulir/mautrix-amp/](mautrix-amp).
|
||||
|
||||
## Features, roadmap, and limitations
|
||||
## Features & roadmap
|
||||
[ROADMAP.md](ROADMAP.md)
|
||||
|
||||
## Setup
|
||||
|
|
58
ROADMAP.md
58
ROADMAP.md
|
@ -6,13 +6,11 @@
|
|||
* [x] Images
|
||||
* [ ] Files
|
||||
* [x] Stickers
|
||||
* [x] Notification for message send failure
|
||||
* [ ] Read receipts (currently eagerly-sent since message sync requires "reading" a chat)
|
||||
* [ ] Room metadata changes
|
||||
* [ ] Name
|
||||
* [ ] Avatar
|
||||
* [ ] Member events
|
||||
* [ ] Invite
|
||||
* [ ] Kick
|
||||
* LINE → Matrix
|
||||
* [ ] Message content
|
||||
* [x] Text
|
||||
|
@ -22,54 +20,45 @@
|
|||
* [ ] Location
|
||||
* [ ] Videos
|
||||
* [x] Stickers
|
||||
* [x] Emoji
|
||||
* [x] Sticons
|
||||
* [x] Single
|
||||
* [x] Multiple or mixed with text
|
||||
* [x] EmojiOne
|
||||
* [ ] Message unsend
|
||||
* [ ] Read receipts
|
||||
* [x] For most recently active chat
|
||||
* [ ] For any chat
|
||||
* [x] User metadata
|
||||
* [ ] Name
|
||||
* [x] On sync
|
||||
* [x] On initial sync
|
||||
* [ ] On change
|
||||
* [ ] Avatar
|
||||
* [x] On sync
|
||||
* [x] On initial sync
|
||||
* [ ] On change
|
||||
* [ ] Chat metadata
|
||||
* [ ] Name
|
||||
* [x] On sync
|
||||
* [x] On initial sync
|
||||
* [ ] On change
|
||||
* [ ] Icon
|
||||
* [x] On sync
|
||||
* [x] On initial sync
|
||||
* [ ] On change
|
||||
* [ ] Message history
|
||||
* [x] Message history
|
||||
* [x] When creating portal
|
||||
* [x] Missed messages
|
||||
* [x] Message timestamps
|
||||
* [ ] As many messages that are visible in LINE extension
|
||||
* [x] Chat types
|
||||
* [x] Direct chats
|
||||
* [x] Groups (named chats)
|
||||
* [x] Rooms (unnamed chats / "multi-user direct chats")
|
||||
* [ ] Membership actions
|
||||
* [ ] Join
|
||||
* [x] When message is sent by new participant
|
||||
* [x] On sync
|
||||
* [ ] At join time
|
||||
* [ ] Leave
|
||||
* [x] On sync
|
||||
* [ ] At leave time
|
||||
* [ ] Invite
|
||||
* [ ] Remove
|
||||
* [ ] Friend actions
|
||||
* [ ] Add friend
|
||||
* [ ] Block user
|
||||
* [ ] Unblock user
|
||||
* [x] Add member
|
||||
* [ ] Remove member
|
||||
* [ ] Block
|
||||
* Misc
|
||||
* [x] Automatic portal creation
|
||||
* [x] At startup
|
||||
* [x] When receiving invite or message
|
||||
* [ ] When sending message in new chat from LINE app
|
||||
* [x] Notification for message send failure
|
||||
* [ ] Provisioning API for logging in
|
||||
* [x] Use bridge bot for messages sent from LINE app (when double-puppeting is disabled and `bridge.invite_own_puppet_to_pm` is enabled)
|
||||
* [x] Use own Matrix account for messages sent from LINE app (when double-puppeting is enabled)
|
||||
|
@ -77,8 +66,8 @@
|
|||
* [ ] Multiple bridge users
|
||||
* [ ] Relay bridging
|
||||
|
||||
# Missing features
|
||||
## Missing from LINE
|
||||
## Missing features
|
||||
### Missing from LINE
|
||||
* Typing notifications
|
||||
* Message edits
|
||||
* Formatted messages
|
||||
|
@ -86,22 +75,13 @@
|
|||
* Timestamped read receipts
|
||||
* Read receipts between users other than yourself
|
||||
|
||||
## Missing from LINE on Chrome
|
||||
* Unlimited message history
|
||||
* Messages that are very old may not be available in LINE on Chrome at all, even after a full sync
|
||||
* Voice/video calls
|
||||
* No notification is sent when a call begins
|
||||
* When a call ends, an automated message of "Your OS version doesn't support this feature" is sent as an ordinary text message from the user who began the call
|
||||
### Missing from LINE on Chrome
|
||||
* Message redaction (delete/unsend)
|
||||
* But messages unsent from other LINE clients do disappear from LINE on Chrome
|
||||
* Replies
|
||||
* Appear as ordinary messages
|
||||
* Mentions
|
||||
* Appear as ordinary text
|
||||
* Audio message sending
|
||||
* But audio messages can be received
|
||||
* Location sending
|
||||
* But locations can be received
|
||||
* Voice/video calls
|
||||
* Unlimited message history
|
||||
|
||||
## Missing from matrix-puppeteer-line
|
||||
### Missing from matrix-puppeteer-line
|
||||
* TODO
|
||||
|
|
|
@ -55,11 +55,6 @@ async def login_do(evt: CommandEvent, gen: AsyncGenerator[Tuple[str, str], None]
|
|||
failure = False
|
||||
async for item in gen:
|
||||
if item[0] == "qr":
|
||||
message = "Open LINE on your primary device and scan this QR code:"
|
||||
content = TextMessageEventContent(body=message, msgtype=MessageType.NOTICE)
|
||||
content.set_reply(evt.event_id)
|
||||
await evt.az.intent.send_message(evt.room_id, content)
|
||||
|
||||
url = item[1]
|
||||
buffer = io.BytesIO()
|
||||
image = qrcode.make(url)
|
||||
|
@ -74,6 +69,7 @@ async def login_do(evt: CommandEvent, gen: AsyncGenerator[Tuple[str, str], None]
|
|||
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]
|
||||
|
@ -83,10 +79,9 @@ async def login_do(evt: CommandEvent, gen: AsyncGenerator[Tuple[str, str], None]
|
|||
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] == "login_success":
|
||||
await evt.reply("Successfully logged in, waiting for LINE to load...")
|
||||
elif item[0] in ("login_failure", "error"):
|
||||
elif item[0] in ("failure", "error"):
|
||||
# TODO Handle errors differently?
|
||||
failure = True
|
||||
reason = item[1]
|
||||
|
@ -96,7 +91,7 @@ async def login_do(evt: CommandEvent, gen: AsyncGenerator[Tuple[str, str], None]
|
|||
# else: pass
|
||||
|
||||
if not failure and evt.sender.command_status:
|
||||
await evt.reply("LINE loading complete")
|
||||
await evt.reply("Successfully logged in")
|
||||
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
|
||||
|
|
|
@ -635,15 +635,12 @@ class Portal(DBPortal, BasePortal):
|
|||
"content": self.bridge_info,
|
||||
}]
|
||||
invites = [source.mxid]
|
||||
|
||||
if self.config["bridge.encryption.default"] and self.matrix.e2ee:
|
||||
self.encrypted = True
|
||||
initial_state.append({
|
||||
"type": str(EventType.ROOM_ENCRYPTION),
|
||||
"content": {"algorithm": "m.megolm.v1.aes-sha2"},
|
||||
})
|
||||
if self.is_direct:
|
||||
invites.append(self.az.bot_mxid)
|
||||
# NOTE Set the room title even for direct chats, because
|
||||
# the LINE bot itself may appear in the title otherwise.
|
||||
#if self.encrypted or not self.is_direct:
|
||||
|
|
|
@ -119,12 +119,8 @@ class Client(RPCClient):
|
|||
data.append(("pin", req["pin"]))
|
||||
event.set()
|
||||
|
||||
async def success_handler(req: LoginCommand) -> None:
|
||||
data.append(("login_success", None))
|
||||
event.set()
|
||||
|
||||
async def failure_handler(req: LoginCommand) -> None:
|
||||
data.append(("login_failure", req.get("reason")))
|
||||
data.append(("failure", req.get("reason")))
|
||||
event.set()
|
||||
|
||||
async def cancel_watcher() -> None:
|
||||
|
@ -149,8 +145,7 @@ class Client(RPCClient):
|
|||
|
||||
self.add_event_handler("qr", qr_handler)
|
||||
self.add_event_handler("pin", pin_handler)
|
||||
self.add_event_handler("login_success", success_handler)
|
||||
self.add_event_handler("login_failure", failure_handler)
|
||||
self.add_event_handler("failure", failure_handler)
|
||||
try:
|
||||
while True:
|
||||
await event.wait()
|
||||
|
@ -163,5 +158,4 @@ class Client(RPCClient):
|
|||
finally:
|
||||
self.remove_event_handler("qr", qr_handler)
|
||||
self.remove_event_handler("pin", pin_handler)
|
||||
self.remove_event_handler("login_success", success_handler)
|
||||
self.remove_event_handler("login_failure", failure_handler)
|
||||
self.remove_event_handler("failure", failure_handler)
|
||||
|
|
|
@ -177,7 +177,7 @@ class User(DBUser, BaseUser):
|
|||
await portal.handle_remote_receipt(receipt)
|
||||
|
||||
async def handle_logged_out(self) -> None:
|
||||
await self.send_bridge_notice("Logged out of LINE. Please run either \"login-qr\" or \"login-email\" to log back in.")
|
||||
await self.send_bridge_notice("Logged out of LINE. Please run the \"login\" command to log back in.")
|
||||
if self._connection_check_task:
|
||||
self._connection_check_task.cancel()
|
||||
self._connection_check_task = None
|
||||
|
|
|
@ -135,19 +135,11 @@ export default class Client {
|
|||
})
|
||||
}
|
||||
|
||||
sendLoginSuccess() {
|
||||
this.log("Sending login success to client")
|
||||
sendFailure(reason) {
|
||||
this.log(`Sending failure to client${reason ? `: "${reason}"` : ""}`)
|
||||
return this._write({
|
||||
id: --this.notificationID,
|
||||
command: "login_success",
|
||||
})
|
||||
}
|
||||
|
||||
sendLoginFailure(reason) {
|
||||
this.log(`Sending login failure to client${reason ? `: "${reason}"` : ""}`)
|
||||
return this._write({
|
||||
id: --this.notificationID,
|
||||
command: "login_failure",
|
||||
command: "failure",
|
||||
reason,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -207,7 +207,7 @@ export default class MessagesPuppeteer {
|
|||
}
|
||||
|
||||
const result = await Promise.race([
|
||||
() => this.page.waitForSelector("#mainApp:not(.MdNonDisp)", {timeout: 2000})
|
||||
() => this.page.waitForSelector("#wrap_message_sync", {timeout: 2000})
|
||||
.then(value => {
|
||||
loginSuccess = true
|
||||
return value
|
||||
|
@ -227,13 +227,11 @@ export default class MessagesPuppeteer {
|
|||
delete this.login_email
|
||||
delete this.login_password
|
||||
|
||||
const messageSyncElement = loginSuccess ? await this.page.waitForSelector("#wrap_message_sync") : null
|
||||
if (!loginSuccess || !messageSyncElement) {
|
||||
if (!loginSuccess) {
|
||||
this._sendLoginFailure(result)
|
||||
return
|
||||
}
|
||||
|
||||
this._sendLoginSuccess()
|
||||
this.log("Waiting for sync")
|
||||
try {
|
||||
await this.page.waitForFunction(
|
||||
|
@ -244,12 +242,14 @@ export default class MessagesPuppeteer {
|
|||
// TODO Sometimes it gets stuck at 99%...??
|
||||
},
|
||||
{timeout: 10000}, // Assume 10 seconds is long enough
|
||||
messageSyncElement)
|
||||
result)
|
||||
} catch (err) {
|
||||
//this._sendLoginFailure(`Failed to sync: ${err}`)
|
||||
this.log("LINE's sync took too long, assume it's fine and carry on...")
|
||||
} finally {
|
||||
const syncText = await messageSyncElement.evaluate(e => e.innerText)
|
||||
const syncText = await this.page.evaluate(
|
||||
messageSyncElement => messageSyncElement.innerText,
|
||||
result)
|
||||
this.log(`Final sync text is: "${syncText}"`)
|
||||
}
|
||||
|
||||
|
@ -758,24 +758,14 @@ export default class MessagesPuppeteer {
|
|||
}
|
||||
}
|
||||
|
||||
_sendLoginSuccess() {
|
||||
this.error("Login success")
|
||||
if (this.client) {
|
||||
this.client.sendLoginSuccess().catch(err =>
|
||||
this.error("Failed to send login success to client:", err))
|
||||
} else {
|
||||
this.log("No client connected, not sending login success")
|
||||
}
|
||||
}
|
||||
|
||||
_sendLoginFailure(reason) {
|
||||
this.loginRunning = false
|
||||
this.error(`Login failure: ${reason ? reason : "cancelled"}`)
|
||||
if (this.client) {
|
||||
this.client.sendLoginFailure(reason).catch(err =>
|
||||
this.error("Failed to send login failure to client:", err))
|
||||
this.client.sendFailure(reason).catch(err =>
|
||||
this.error("Failed to send failure reason to client:", err))
|
||||
} else {
|
||||
this.log("No client connected, not sending login failure")
|
||||
this.log("No client connected, not sending failure reason")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue