forked from fair/matrix-puppeteer-line
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			3286d7e6e2
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1fffbc625c | |||
| c5eea7b50b | 
| @@ -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 | ||||
| ## Features, roadmap, and limitations | ||||
| [ROADMAP.md](ROADMAP.md) | ||||
|  | ||||
| ## Setup | ||||
|   | ||||
							
								
								
									
										64
									
								
								ROADMAP.md
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								ROADMAP.md
									
									
									
									
									
								
							| @@ -6,11 +6,13 @@ | ||||
|     * [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 | ||||
| @@ -20,45 +22,54 @@ | ||||
|     * [ ] Location | ||||
|     * [ ] Videos | ||||
|     * [x] Stickers | ||||
|     * [x] Sticons | ||||
|       * [x] Single | ||||
|       * [x] Multiple or mixed with text | ||||
|     * [x] EmojiOne | ||||
|     * [x] Emoji | ||||
|   * [ ] Message unsend | ||||
|   * [ ] Read receipts | ||||
|     * [x] For most recently active chat | ||||
|     * [ ] For any chat | ||||
|   * [x] User metadata | ||||
|     * [ ] Name | ||||
|       * [x] On initial sync | ||||
|       * [x] On sync | ||||
|       * [ ] On change | ||||
|     * [ ] Avatar | ||||
|       * [x] On initial sync | ||||
|       * [x] On sync | ||||
|       * [ ] On change | ||||
|   * [ ] Chat metadata | ||||
|     * [ ] Name | ||||
|       * [x] On initial sync | ||||
|       * [x] On sync | ||||
|       * [ ] On change | ||||
|     * [ ] Icon | ||||
|       * [x] On initial sync | ||||
|       * [x] On sync | ||||
|       * [ ] On change | ||||
|   * [x] Message history | ||||
|   * [ ] 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 | ||||
|     * [x] Add member | ||||
|     * [ ] Remove member | ||||
|     * [ ] Block | ||||
|     * [ ] 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 | ||||
| * 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) | ||||
| @@ -66,8 +77,8 @@ | ||||
|   * [ ] Multiple bridge users | ||||
|   * [ ] Relay bridging | ||||
|  | ||||
| ## Missing features | ||||
| ### Missing from LINE | ||||
| # Missing features | ||||
| ## Missing from LINE | ||||
| * Typing notifications | ||||
| * Message edits | ||||
| * Formatted messages | ||||
| @@ -75,13 +86,22 @@ | ||||
| * Timestamped read receipts | ||||
| * Read receipts between users other than yourself | ||||
|  | ||||
| ### Missing from LINE on Chrome | ||||
| * Message redaction (delete/unsend) | ||||
| * Replies | ||||
| * Audio message sending | ||||
| * Location sending | ||||
| * Voice/video calls | ||||
| ## 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 | ||||
| * 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 | ||||
|  | ||||
| ### Missing from matrix-puppeteer-line | ||||
| ## Missing from matrix-puppeteer-line | ||||
| * TODO | ||||
|   | ||||
| @@ -55,6 +55,11 @@ 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) | ||||
| @@ -69,7 +74,6 @@ 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] | ||||
| @@ -79,9 +83,10 @@ 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] in ("failure", "error"): | ||||
|         elif item[0] == "login_success": | ||||
|             await evt.reply("Successfully logged in, waiting for LINE to load...") | ||||
|         elif item[0] in ("login_failure", "error"): | ||||
|             # TODO Handle errors differently? | ||||
|             failure = True | ||||
|             reason = item[1] | ||||
| @@ -91,7 +96,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("Successfully logged in") | ||||
|         await evt.reply("LINE loading complete") | ||||
|         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 | ||||
|   | ||||
| @@ -119,8 +119,12 @@ 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(("failure", req.get("reason"))) | ||||
|             data.append(("login_failure", req.get("reason"))) | ||||
|             event.set() | ||||
|  | ||||
|         async def cancel_watcher() -> None: | ||||
| @@ -145,7 +149,8 @@ class Client(RPCClient): | ||||
|  | ||||
|         self.add_event_handler("qr", qr_handler) | ||||
|         self.add_event_handler("pin", pin_handler) | ||||
|         self.add_event_handler("failure", failure_handler) | ||||
|         self.add_event_handler("login_success", success_handler) | ||||
|         self.add_event_handler("login_failure", failure_handler) | ||||
|         try: | ||||
|             while True: | ||||
|                 await event.wait() | ||||
| @@ -158,4 +163,5 @@ class Client(RPCClient): | ||||
|         finally: | ||||
|             self.remove_event_handler("qr", qr_handler) | ||||
|             self.remove_event_handler("pin", pin_handler) | ||||
|             self.remove_event_handler("failure", failure_handler) | ||||
|             self.remove_event_handler("login_success", success_handler) | ||||
|             self.remove_event_handler("login_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 the \"login\" command to log back in.") | ||||
|         await self.send_bridge_notice("Logged out of LINE. Please run either \"login-qr\" or \"login-email\" to log back in.") | ||||
|         if self._connection_check_task: | ||||
|             self._connection_check_task.cancel() | ||||
|             self._connection_check_task = None | ||||
|   | ||||
| @@ -135,11 +135,19 @@ export default class Client { | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	sendFailure(reason) { | ||||
| 		this.log(`Sending failure to client${reason ? `: "${reason}"` : ""}`) | ||||
| 	sendLoginSuccess() { | ||||
| 		this.log("Sending login success to client") | ||||
| 		return this._write({ | ||||
| 			id: --this.notificationID, | ||||
| 			command: "failure", | ||||
| 			command: "login_success", | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	sendLoginFailure(reason) { | ||||
| 		this.log(`Sending login failure to client${reason ? `: "${reason}"` : ""}`) | ||||
| 		return this._write({ | ||||
| 			id: --this.notificationID, | ||||
| 			command: "login_failure", | ||||
| 			reason, | ||||
| 		}) | ||||
| 	} | ||||
|   | ||||
| @@ -207,7 +207,7 @@ export default class MessagesPuppeteer { | ||||
| 		} | ||||
|  | ||||
| 		const result = await Promise.race([ | ||||
| 			() => this.page.waitForSelector("#wrap_message_sync", {timeout: 2000}) | ||||
| 			() => this.page.waitForSelector("#mainApp:not(.MdNonDisp)", {timeout: 2000}) | ||||
| 				.then(value => { | ||||
| 					loginSuccess = true | ||||
| 					return value | ||||
| @@ -227,11 +227,13 @@ export default class MessagesPuppeteer { | ||||
| 		delete this.login_email | ||||
| 		delete this.login_password | ||||
|  | ||||
| 		if (!loginSuccess) { | ||||
| 		const messageSyncElement = loginSuccess ? await this.page.waitForSelector("#wrap_message_sync") : null | ||||
| 		if (!loginSuccess || !messageSyncElement) { | ||||
| 			this._sendLoginFailure(result) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		this._sendLoginSuccess() | ||||
| 		this.log("Waiting for sync") | ||||
| 		try { | ||||
| 			await this.page.waitForFunction( | ||||
| @@ -242,14 +244,12 @@ export default class MessagesPuppeteer { | ||||
| 						// TODO Sometimes it gets stuck at 99%...?? | ||||
| 				}, | ||||
| 				{timeout: 10000}, // Assume 10 seconds is long enough | ||||
| 				result) | ||||
| 				messageSyncElement) | ||||
| 		} 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 this.page.evaluate( | ||||
| 				messageSyncElement => messageSyncElement.innerText, | ||||
| 				result) | ||||
| 			const syncText = await messageSyncElement.evaluate(e => e.innerText) | ||||
| 			this.log(`Final sync text is: "${syncText}"`) | ||||
| 		} | ||||
|  | ||||
| @@ -758,14 +758,24 @@ 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.sendFailure(reason).catch(err => | ||||
| 				this.error("Failed to send failure reason to client:", err)) | ||||
| 			this.client.sendLoginFailure(reason).catch(err => | ||||
| 				this.error("Failed to send login failure to client:", err)) | ||||
| 		} else { | ||||
| 			this.log("No client connected, not sending failure reason") | ||||
| 			this.log("No client connected, not sending login failure") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user