diff --git a/.springjavaformatconfig b/.springjavaformatconfig new file mode 100644 index 0000000..e69de29 diff --git a/HELP.md b/HELP.md index 19f2ee3..7e5ec73 100644 --- a/HELP.md +++ b/HELP.md @@ -1,6 +1,7 @@ # Getting Started ### Reference Documentation + For further reference, please consider the following sections: * [Official Apache Maven documentation](https://maven.apache.org/guides/index.html) diff --git a/README.md b/README.md index a0d0ab2..9da05e4 100755 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ # Lovebox Telegram Sender -The app allows sending messages via Telegram Bot to a single Lovebox instance. Text messages and photos with captions are supported. Other message types (e.g. Stickers, Audio, etc.) will lead to a default message. +The app allows sending messages via Telegram Bot to a single Lovebox instance. Text messages and photos with captions +are supported. Other message types (e.g. Stickers, Audio, etc.) will lead to a default message. ## Application Setup -To set up the app a few ids and values need to be retrieved on the Lovebox API. The following curl commands help to find the needed data on an existing account. Make sure you have set up your account via the Android or iOS app already. +To set up the app a few ids and values need to be retrieved on the Lovebox API. The following curl commands help to find +the needed data on an existing account. Make sure you have set up your account via the Android or iOS app already. ### Login with Password @@ -42,9 +44,9 @@ curl --location --request POST 'https://app-api.loveboxlove.com/v1/graphql' \ --header 'content-type: application/json' \ --header 'host: app-api.loveboxlove.com' \ --data-raw '{ - "operationName": null, + "operationName": "me", "variables": {}, - "query": "{\n me {\n _id\n firstName\n email\n boxes {\n _id\n color\n signature\n lovePercentage\n nickname\n notifications {\n disableUntil\n messageRead\n heartReceived\n __typename\n }\n admin {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n pairingCode\n isConnected\n isAdmin\n hardware\n hasColor\n hasColorBackup\n connectionDate\n __typename\n }\n relations {\n _id\n name\n relationType\n picture\n color\n streak\n boxId\n loveGoal\n streakDeadline\n reminders {\n day\n meridiem\n number\n weekday\n time\n __typename\n }\n specialDates {\n _id\n name\n date\n dateType\n __typename\n }\n addresses {\n firstname\n lastname\n streetAddress\n zipCode\n city\n country\n state\n __typename\n }\n __typename\n }\n roles\n device {\n _id\n appVersion\n os\n __typename\n }\n profile\n reminder\n premium\n beta\n fcmToken\n language\n loveCoins\n __typename\n }\n}\n" + "query": "query me {\n me {\n _id\n _id\n createdAt\n firstName\n email\n beta\n settings {\n streak\n loveGoal\n reminders {\n day\n meridiem\n number\n weekday\n time\n __typename\n }\n specialDates {\n _id\n name\n date\n dateType\n __typename\n }\n notifications {\n generalMessageRead\n generalHeartReceived\n marketingOffers\n marketingOffersPush\n marketingOffersEmail\n __typename\n }\n __typename\n }\n addresses {\n firstname\n lastname\n streetAddress\n zipCode\n city\n country\n state\n __typename\n }\n boxes {\n _id\n color\n companyId\n signature\n picture\n nickname\n notifications {\n disableUntil\n messageRead\n heartReceived\n __typename\n }\n admin {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n pairingCode\n isConnected\n isAdmin\n hardware\n hasColor\n connectionDate\n macAddress\n __typename\n }\n roles\n device {\n _id\n appVersion\n os\n __typename\n }\n profile\n reminder\n subscription {\n subscribed\n platform\n __typename\n }\n fcmToken\n language\n loveCoins\n lastSentMessage\n __typename\n }\n}\n" }' ``` @@ -53,19 +55,45 @@ curl --location --request POST 'https://app-api.loveboxlove.com/v1/graphql' \ "data": { "me": { "_id": "42c61f261f399d0016350b7f", + "createdAt": "2021-12-24T19:27:34.542Z", "firstName": "FirstName", "email": "me@email.com", + "beta": 0, + "settings": { + "streak": 1, + "loveGoal": "TwiceAWeek", + "reminders": [ + // ... + ], + "specialDates": [ + // ... + ], + "notifications": { + "generalMessageRead": true, + "generalHeartReceived": true, + "marketingOffers": true, + "marketingOffersPush": true, + "marketingOffersEmail": true, + "__typename": "NotificationUserSettings" + }, + "__typename": "Settings" + }, + "addresses": [], "boxes": [ { // lovebox.box-id "_id": "417a114e58e15a0214cf3612", "color": "#8A64FF", + "companyId": "", // lovebox.signature "signature": "Signature", - "lovePercentage": 100, + "picture": null, "nickname": "Nickname", "notifications": { - // ... + "disableUntil": null, + "messageRead": true, + "heartReceived": true, + "__typename": "NotificationSettings" }, "admin": { "_id": "61c61ecc71010a00161789f2", @@ -79,46 +107,28 @@ curl --location --request POST 'https://app-api.loveboxlove.com/v1/graphql' \ "isAdmin": false, "hardware": "C2", "hasColor": true, - "hasColorBackup": null, "connectionDate": "2021-12-24T19:27:35.123Z", + "macAddress": "0CDC7ECF4FC4", "__typename": "BoxSettings" } ], - "relations": [ - { - // lovebox.relation-id - "_id": "33c67a2127d7be09142f4326", - "name": "Nickname", - "relationType": "other", - "picture": null, - "color": "#3399FF", - "streak": 5, - // lovebox.box-id - "boxId": "417a114e58e15a0214cf3612", - "loveGoal": "Daily", - "streakDeadline": "2021-12-29T22:59:59.999Z", - "reminders": [], - "addresses": [], - "__typename": "Relation" - } - ], "roles": [], "device": { // lovebox.device-id "_id": "42fab8322d8cec91", - "appVersion": "5.4.9", + "appVersion": "5.14.9", "os": "android", "__typename": "Device" }, "profile": { // ... }, - "reminder": null, - "premium": 0, - "beta": 0, + "reminder": 0, + "subscription": null, "fcmToken": "edMLqMyMrpoGig9ZHdFdvH:AdA91bGGwx3UEuYTdhYOWDIdwlm2b23B9Jjin3MCGbi7CmUSpCVHFlorfryygi5QUBQMUVUiGsDJIE3RliENFmsuWrOnf4cBba-mNT5032NoKlo9AdPU5YhuCOR0KIdAbCokR42Hru", "language": "en", "loveCoins": 10, + "lastSentMessage": "2023-01-01T17:55:34.890Z", "__typename": "User" } } @@ -127,13 +137,16 @@ curl --location --request POST 'https://app-api.loveboxlove.com/v1/graphql' \ ### Setting up a Telegram Bot -To create a chatbot on Telegram, you need to contact the [@BotFather](https://telegram.me/BotFather), which is a bot used to create other bots. +To create a chatbot on Telegram, you need to contact the [@BotFather](https://telegram.me/BotFather), which is a bot +used to create other bots. -The command you need is `/newbot` which leads to the next steps to create your bot. Follow the instructions and get the bot `username`, and `token`. +The command you need is `/newbot` which leads to the next steps to create your bot. Follow the instructions and get the +bot `username`, and `token`. ### Adjusting SpringBoot's application.properties -Running the app from the source needs adjustments according to your settings. Adjusting the `application.properties` in the sources or passing them as Java options or CLI arguments to the app. +Running the app from the source needs adjustments according to your settings. Adjusting the `application.properties` in +the sources or passing them as Java options or CLI arguments to the app. ```properties # Lovebox Login @@ -143,7 +156,6 @@ lovebox.password=mySecret # Lovebox Setting lovebox.signature=Signature lovebox.device-id=42fab8322d8cec91 -lovebox.relation-id=33c67a2127d7be09142f4326 lovebox.box-id=417a114e58e15a0214cf3612 # Telegram Bot Settings bot.username=Lovebox_bot @@ -151,7 +163,9 @@ bot.token=4072971853:ABEojZ42uNA6YYn_c7DF8RH0UOorqXuveSQ ``` ### Setting Environment Variables e.g. for Docker -The following snippet can be passed as `.env` and read by the `docker-compose.yml` or used to be passed directly to the `docker run` command. + +The following snippet can be passed as `.env` and read by the `docker-compose.yml` or used to be passed directly to +the `docker run` command. ```bash # Lovebox Login @@ -194,12 +208,17 @@ mvn spring-boot:build-image \ ### Fixing Known Issues with Missing Fonts -Since the app uses fonts, we need to make sure that fonts are part of the docker container. The containers produced above throw an exception when using them `java.lang.NullPointerException: Cannot load from short array because "sun.awt.FontConfiguration.head" is null` +Since the app uses fonts, we need to make sure that fonts are part of the docker container. The containers produced +above throw an exception when using +them `java.lang.NullPointerException: Cannot load from short array because "sun.awt.FontConfiguration.head" is null` [Andreas Ahlensdorf](https://github.com/aahlenst) describes nicely the font problem in his blog -post [Prerequisites for Font [Support in AdoptOpenJDK](https://blog.adoptopenjdk.net/2021/01/prerequisites-for-font-support-in-adoptopenjdk/). +post [Prerequisites for +Font [Support in AdoptOpenJDK](https://blog.adoptopenjdk.net/2021/01/prerequisites-for-font-support-in-adoptopenjdk/). -After more research, it seems that the only solution to add fonts to the build pack base image is to create an OCI run image by extending the base one. See the `Dockerfile.`base-cnb` file and how a patch with the additional font packages might look like. +After more research, it seems that the only solution to add fonts to the build pack base image is to create an OCI run +image by extending the base one. See the `Dockerfile.`base-cnb` file and how a patch with the additional font packages +might look like. Build the `runImage` locally with the following command. @@ -207,7 +226,9 @@ Build the `runImage` locally with the following command. docker build --no-cache -f Dockerfile.base-cnb -t patbaumgartner/run:base-cnb . ``` -Since we run the pull policy in the `mvn spring-boot:build-image` command with IF_NOT_PRESENT, we need to make sure that the newest version of the builder is locally available. Finally, pass to the `spring-boot-maven-plugin` the `runImage` to build the docker container containing the fonts. +Since we run the pull policy in the `mvn spring-boot:build-image` command with IF_NOT_PRESENT, we need to make sure that +the newest version of the builder is locally available. Finally, pass to the `spring-boot-maven-plugin` the `runImage` +to build the docker container containing the fonts. ```bash mvn spring-boot:build-image \ @@ -220,4 +241,10 @@ mvn spring-boot:build-image \ ## Credits -Reverse engineering (unpinning certificates) was done with [APKLab](https://github.com/APKLab/APKLab) and the [Lovebox APK](https://www.apkmonk.com/app/love.lovebox.loveboxapp/) provided by [apkmonk](https://www.apkmonk.com). Postman was used to capture the REST calls from the mobile app. The article [Capturing Http Requests](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/) covers everything needed. After a Postman update, [new certs](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/#troubleshooting-certificate-issues)](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/#troubleshooting-certificate-issues) need to be installed. +Reverse engineering (unpinning certificates) was done with [APKLab](https://github.com/APKLab/APKLab) and +the [Lovebox APK](https://www.apkmonk.com/app/love.lovebox.loveboxapp/) provided by [apkmonk](https://www.apkmonk.com). +Postman was used to capture the REST calls from the mobile app. The +article [Capturing Http Requests](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/) +covers everything needed. After a Postman +update, [new certs](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/#troubleshooting-certificate-issues)](https://learning.postman.com/docs/sending-requests/capturing-request-data/capturing-http-requests/#troubleshooting-certificate-issues) +need to be installed. diff --git a/pom.xml b/pom.xml index 69feb94..20b5e2f 100644 --- a/pom.xml +++ b/pom.xml @@ -1,14 +1,14 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 org.springframework.boot spring-boot-starter-parent - 3.0.0-RC2 - + 3.0.1 + com.patbaumgartner @@ -19,14 +19,14 @@ - - - + + + 6.0 4.2 17 - 2022.0.0-RC3 + 2022.0.0 6.4.0 diff --git a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/rest/clients/LoveboxRestClientProperties.java b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/rest/clients/LoveboxRestClientProperties.java index 415d90b..d9087c5 100644 --- a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/rest/clients/LoveboxRestClientProperties.java +++ b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/rest/clients/LoveboxRestClientProperties.java @@ -21,6 +21,5 @@ public class LoveboxRestClientProperties { private String boxId; /* Signature of message sender */ private String signature; - /* Recipient relation id */ - private String relationId; + } diff --git a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/services/LoveboxService.java b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/services/LoveboxService.java index 92e3bbc..2996600 100644 --- a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/services/LoveboxService.java +++ b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/services/LoveboxService.java @@ -92,12 +92,11 @@ public Tripple sendImageMessage(String imageAsBas if (restClientProperties.isEnabled()) { String token = loginAndResolveToken(); - String sendPixNoteQuery = "mutation sendPixNote($channel: ChannelsTypes, $appVersion: String, $postcardStripePaymentId: String, $postcardAddress: JSON, $postcardSettings: JSON, $postcardScheduledDate: Date, $postcardText: String, $recipientRelationId: String, $base64: String, $recipient: String, $date: Date, $options: JSON, $contentType: [String], $timezone: Int, $promotionCode: String) {\n sendPixNote(channel: $channel, appVersion: $appVersion, postcardStripePaymentId: $postcardStripePaymentId, postcardAddress: $postcardAddress, postcardSettings: $postcardSettings, postcardScheduledDate: $postcardScheduledDate, postcardText: $postcardText, recipientRelationId: $recipientRelationId, base64: $base64, recipient: $recipient, date: $date, contentType: $contentType, timezone: $timezone, options: $options, promotionCode: $promotionCode) {\n _id\n channel\n type\n recipient\n postcardStripePayment\n postcardAddress {\n firstname\n lastname\n country\n state\n streetAddress\n city\n zipCode\n __typename\n }\n postcardSettings {\n color\n fontFamily\n fontSize\n __typename\n }\n recipientRelation\n postcardText\n url\n date\n status {\n label\n __typename\n }\n statusList {\n label\n date\n __typename\n }\n senderUser {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n addedLoveCoins\n __typename\n }\n}\n"; + String sendPixNoteQuery = "mutation sendPixNote($channel: ChannelsTypes, $appVersion: String, $postcardStripePaymentId: String, $postcardAddress: JSON, $postcardSettings: JSON, $postcardScheduledDate: Date, $postcardText: String, $base64: String, $recipient: String, $date: Date, $options: JSON, $contentType: [String], $timezone: Int, $promotionCode: String) {\n sendPixNote(channel: $channel, appVersion: $appVersion, postcardStripePaymentId: $postcardStripePaymentId, postcardAddress: $postcardAddress, postcardSettings: $postcardSettings, postcardScheduledDate: $postcardScheduledDate, postcardText: $postcardText, base64: $base64, recipient: $recipient, date: $date, contentType: $contentType, timezone: $timezone, options: $options, promotionCode: $promotionCode) {\n _id\n channel\n type\n recipient\n postcardStripePayment\n postcardAddress {\n firstname\n lastname\n country\n state\n streetAddress\n city\n zipCode\n __typename\n }\n postcardSettings {\n color\n fontFamily\n fontSize\n __typename\n }\n recipientRelation\n postcardText\n url\n date\n status {\n label\n __typename\n }\n statusList {\n label\n date\n __typename\n }\n senderUser {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n addedLoveCoins\n __typename\n }\n}\n"; Map sendPixNoteVariables = new HashMap<>(); sendPixNoteVariables.put("channel", "LOVEBOX"); sendPixNoteVariables.put("base64", imageAsBase64); sendPixNoteVariables.put("recipient", restClientProperties.getBoxId()); - sendPixNoteVariables.put("recipientRelationId", restClientProperties.getRelationId()); sendPixNoteVariables.put("contentType", new Object[]{}); Map options = new HashMap<>(); options.put("framesBase64", null); @@ -164,26 +163,32 @@ public String receiveWaterfallOfHearts() { } @SneakyThrows - public List> getMessagesByBox() { + public List> getMessages() { List> messageStatus = new ArrayList<>(); if (restClientProperties.isEnabled()) { String token = loginAndResolveToken(); // Get hearts rain - String getMessagesByBoxQuery = "query getMessagesByBox($boxId: String, $relationId: String, $messagesShown: Int!) {\n getMessagesByBox(boxId: $boxId, relationId: $relationId, messagesShown: $messagesShown) {\n _id\n channel\n content\n type\n recipient\n date\n status {\n label\n __typename\n }\n statusList {\n label\n date\n __typename\n }\n drawing {\n base64\n rotate\n __typename\n }\n base64\n bytes\n premium\n textOnly\n textCentered\n gifId\n url\n urlId\n frames\n senderUser {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n postcardAddress {\n firstname\n lastname\n streetAddress\n zipCode\n city\n country\n state\n __typename\n }\n postcardSettings {\n color\n fontFamily\n fontSize\n __typename\n }\n postcardScheduledDate\n estimatedArrivalDate\n __typename\n }\n}\n"; - Map getMessagesByBoxQueryVariables = new HashMap<>(); - getMessagesByBoxQueryVariables.put("relationId", restClientProperties.getRelationId()); - getMessagesByBoxQueryVariables.put("messagesShown", 0); + String getMessagesQuery = "query getMessages($getMessagesInput: GetMessagesInput) {\n getMessages(getMessagesInput: $getMessagesInput) {\n _id\n channel\n content\n type\n recipient\n date\n status {\n label\n __typename\n }\n statusList {\n label\n date\n __typename\n }\n drawing {\n base64\n rotate\n __typename\n }\n base64\n bytes\n premium\n textOnly\n textCentered\n gifId\n url\n urlId\n frames\n senderUser {\n _id\n firstName\n email\n __typename\n }\n privacyPolicy\n postcardText\n postcardAddress {\n firstname\n lastname\n streetAddress\n zipCode\n city\n country\n state\n __typename\n }\n postcardSettings {\n color\n fontFamily\n fontSize\n __typename\n }\n postcardScheduledDate\n estimatedArrivalDate\n __typename\n }\n}\n"; - GraphqlRequestBody getMessagesByBoxGraphqlRequestBody = new GraphqlRequestBody("getMessagesByBox", getMessagesByBoxQueryVariables, - getMessagesByBoxQuery); - ResponseEntity getMessagesByBoxResponse = restClient.graphql("Bearer " + token, getMessagesByBoxGraphqlRequestBody); - log.debug("Get messages by box response: {}", getMessagesByBoxResponse); + Map getMessagesInput = new HashMap<>(); + getMessagesInput.put("recipient", restClientProperties.getBoxId()); + getMessagesInput.put("limit", 10); + getMessagesInput.put("skip", 0); - JsonElement jsonRoot = JsonParser.parseString(getMessagesByBoxResponse.getBody()); + Map getMessagesQueryVariables = new HashMap<>(); + getMessagesQueryVariables.put("getMessagesInput", getMessagesInput); + + + GraphqlRequestBody getMessagesByBoxGraphqlRequestBody = new GraphqlRequestBody("getMessages", getMessagesQueryVariables, + getMessagesQuery); + ResponseEntity getMessagesResponse = restClient.graphql("Bearer " + token, getMessagesByBoxGraphqlRequestBody); + log.debug("Get messages by box response: {}", getMessagesResponse); + + JsonElement jsonRoot = JsonParser.parseString(getMessagesResponse.getBody()); JsonElement getMessagesByBox = jsonRoot.getAsJsonObject() - .get("data").getAsJsonObject().get("getMessagesByBox"); + .get("data").getAsJsonObject().get("getMessages"); if (!getMessagesByBox.isJsonNull()) { JsonArray messages = getMessagesByBox diff --git a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/telegram/LoveboxBot.java b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/telegram/LoveboxBot.java index b55958a..bf80b0f 100644 --- a/src/main/java/com/patbaumgartner/lovebox/telegram/sender/telegram/LoveboxBot.java +++ b/src/main/java/com/patbaumgartner/lovebox/telegram/sender/telegram/LoveboxBot.java @@ -43,7 +43,7 @@ public class LoveboxBot extends TelegramLongPollingBot { @Scheduled(fixedRate = 10_000) public void readMessageBox() { - List> messages = loveboxService.getMessagesByBox(); + List> messages = loveboxService.getMessages(); messages.forEach(p -> { loveboxMessageStore.computeIfPresent(p.left(), (key, value) -> { if (!value.equals(p.right())) { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 76e5e3c..9b4db47 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,17 +1,13 @@ # Application Settings lovebox.enabled=true logging.level.com.patbaumgartner=debug - # Lovebox Login lovebox.email=me@email.com lovebox.password=mySecret - # Lovebox Setting lovebox.signature=Signature lovebox.device-id=42fab8322d8cec91 -lovebox.relation-id=33c67a2127d7be09142f4326 lovebox.box-id=417a114e58e15a0214cf3612 - # Telegram Bot Settings bot.username=Lovebox_bot bot.token=4072971853:ABEojZ42uNA6YYn_c7DF8RH0UOorqXuveSQ \ No newline at end of file diff --git a/src/test/java/com/patbaumgartner/lovebox/telegram/sender/LoveboxTelegramSenderApplicationTests.java b/src/test/java/com/patbaumgartner/lovebox/telegram/sender/LoveboxTelegramSenderApplicationTests.java index 4746029..352d4d1 100644 --- a/src/test/java/com/patbaumgartner/lovebox/telegram/sender/LoveboxTelegramSenderApplicationTests.java +++ b/src/test/java/com/patbaumgartner/lovebox/telegram/sender/LoveboxTelegramSenderApplicationTests.java @@ -6,7 +6,7 @@ @SpringBootTest @TestPropertySource( - properties = {"lovebox.enabled=false", "telegrambots.enabled=false"} + properties = {"lovebox.enabled=false", "telegrambots.enabled=false"} ) class LoveboxTelegramSenderApplicationTests {