mirror of https://github.com/espruino/BangleApps
Add new module "reply" for canned responses
Adds a new module that enables replying to messagespull/3473/head
parent
722c289351
commit
6ee5b73052
|
@ -0,0 +1 @@
|
||||||
|
0.01: New Library!
|
|
@ -0,0 +1,23 @@
|
||||||
|
# Canned Replies Library
|
||||||
|
|
||||||
|
A library that handles replying to messages received from Gadgetbridge/Messages apps.
|
||||||
|
|
||||||
|
## Replying to a message
|
||||||
|
The user can define a set of canned responses via the customise page after installing the app, or alternatively if they have a keyboard installed, they can type a response back. The requesting app will receive either an object containing the full reply for GadgetBridge, or a string with the response from the user, depending on how they wish to handle the response.
|
||||||
|
|
||||||
|
## Integrating in your app
|
||||||
|
To use this in your app, simply call
|
||||||
|
|
||||||
|
```js
|
||||||
|
require("reply").reply(/*options*/{...}).then(result => ...);
|
||||||
|
```
|
||||||
|
|
||||||
|
The ```options``` object can contain the following:
|
||||||
|
|
||||||
|
- ```msg```: A message object containing a field ```id```, the ID to respond to. If this is included in options, the result of the promise will be an object as follows: ```{t: "notify", id: msg.id, n: "REPLY", msg: "USER REPLY"}```. If not included, the result of the promise will be an object, ```{msg: "USER REPLY"}```
|
||||||
|
- ```shouldReply```: Whether or not the library should send the response over Bluetooth with ```Bluetooth.println(...```. Useful if the calling app wants to handle the response a different way. Default is true.
|
||||||
|
- ```title```: The title to show at the top of the menu. Defaults to ```"Reply with:"```.
|
||||||
|
- ```fileOverride```: An override file to read canned responses from, which is an array of objects each with a ```text``` property. Default is ```replies.json```. Useful for apps which might want to make use of custom canned responses.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
Emojis are currently not supported.
|
Binary file not shown.
After Width: | Height: | Size: 720 B |
|
@ -0,0 +1,125 @@
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="../../css/spectre.min.css">
|
||||||
|
<link rel="stylesheet" href="../../css/spectre-icons.min.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<form id="replyForm">
|
||||||
|
<label class="label">New Custom Reply:</label>
|
||||||
|
<input class="form-input" id="msg" type="text" required></input>
|
||||||
|
<input class="btn btn-primary" type="submit" value="Add">
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
</form>
|
||||||
|
<button class="btn btn-primary" onclick="updateDevice()">Update Device</button>
|
||||||
|
<div>
|
||||||
|
<div id="loading">
|
||||||
|
<div class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<div class="loading loading-lg"></div>
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">Loading</p>
|
||||||
|
<p class="empty-subtitle">Syncing custom replies with your watch</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="empty" class="d-hide">
|
||||||
|
<div class="empty">
|
||||||
|
<div class="empty-icon">
|
||||||
|
<i class="icon icon-more-horiz"></i>
|
||||||
|
</div>
|
||||||
|
<p class="empty-title h5">No custom replies</p>
|
||||||
|
<p class="empty-subtitle">Use the field above to add a custom reply</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table id="replyList" class="table table-striped table-hover"></table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="../../core/lib/customize.js"></script>
|
||||||
|
<script>
|
||||||
|
const form = document.querySelector("#replyForm");
|
||||||
|
form.addEventListener("submit", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
saveReply();
|
||||||
|
});
|
||||||
|
var replies = [];
|
||||||
|
var fetching = true; // Bit of a hack
|
||||||
|
var el = document.getElementById('items');
|
||||||
|
function onInit(device) {
|
||||||
|
fetching = true;
|
||||||
|
Util.readStorageJSON("replies.json", (arr) => {
|
||||||
|
if (arr) {
|
||||||
|
replies = replies.concat(arr);
|
||||||
|
fetching = false;
|
||||||
|
renderReplyList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleVisibility() {
|
||||||
|
let empty = document.getElementById("empty");
|
||||||
|
let loading = document.getElementById("loading");
|
||||||
|
if (replies.length == 0) {
|
||||||
|
empty.setAttribute("class", "d-block");
|
||||||
|
loading.setAttribute("class", "d-hide");
|
||||||
|
}
|
||||||
|
else if (fetching) {
|
||||||
|
loading.setAttribute("class", "d-block");
|
||||||
|
empty.setAttribute("class", "d-hide");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
loading.setAttribute("class", "d-hide");
|
||||||
|
empty.setAttribute("class", "d-hide");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderReplyList() {
|
||||||
|
toggleVisibility();
|
||||||
|
let table = document.getElementById("replyList");
|
||||||
|
table.innerHTML = "";
|
||||||
|
if (!fetching) {
|
||||||
|
for (var i = 0; i < replies.length; i++) {
|
||||||
|
var reply = replies[i];
|
||||||
|
var li = document.createElement("tr");
|
||||||
|
var label = document.createElement("td");
|
||||||
|
label.innerHTML = reply.text + " ";
|
||||||
|
var deleteButton = document.createElement("button");
|
||||||
|
deleteButton.innerText = "Delete";
|
||||||
|
deleteButton.setAttribute("onclick", `deleteReply(${i})`);
|
||||||
|
deleteButton.setAttribute("class", "btn btn-error");
|
||||||
|
li.appendChild(label);
|
||||||
|
li.appendChild(deleteButton);
|
||||||
|
table.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteReply(index) {
|
||||||
|
replies.splice(index, 1);
|
||||||
|
renderReplyList(replies);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveReply() {
|
||||||
|
var reply = {};
|
||||||
|
reply.text = document.getElementById('msg').value;
|
||||||
|
replies.push(reply);
|
||||||
|
return updateDevice();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDevice() {
|
||||||
|
fetching = true;
|
||||||
|
renderReplyList();
|
||||||
|
Util.writeStorage("replies.json", JSON.stringify(replies), () => {
|
||||||
|
fetching = false;
|
||||||
|
renderReplyList();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
fetching = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -0,0 +1,75 @@
|
||||||
|
exports.reply = function (options) {
|
||||||
|
var keyboard = "textinput";
|
||||||
|
try {
|
||||||
|
keyboard = require(keyboard);
|
||||||
|
} catch (e) {
|
||||||
|
keyboard = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function constructReply(msg, replyText, resolve) {
|
||||||
|
var responseMessage = {msg: replyText};
|
||||||
|
if (msg.id) {
|
||||||
|
responseMessage = { t: "notify", id: msg.id, n: "REPLY", msg: replyText };
|
||||||
|
}
|
||||||
|
E.showMenu();
|
||||||
|
layout.setUI();
|
||||||
|
layout.render();
|
||||||
|
if (options.sendReply == null || options.sendReply) {
|
||||||
|
Bluetooth.println(JSON.stringify(result));
|
||||||
|
}
|
||||||
|
resolve(responseMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
var menu = {
|
||||||
|
"": {
|
||||||
|
title: options.title || /*LANG*/ "Reply with:",
|
||||||
|
back: function () {
|
||||||
|
E.showMenu();
|
||||||
|
layout.setUI();
|
||||||
|
layout.render();
|
||||||
|
reject("User pressed back");
|
||||||
|
},
|
||||||
|
}, // options
|
||||||
|
/*LANG*/ "Compose": function () {
|
||||||
|
keyboard.input().then((result) => {
|
||||||
|
constructReply(options.msg ?? {}, result, resolve);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
var replies =
|
||||||
|
require("Storage").readJSON(
|
||||||
|
options.fileOverride || "replies.json",
|
||||||
|
true
|
||||||
|
) || {};
|
||||||
|
replies.forEach((reply) => {
|
||||||
|
menu = Object.defineProperty(menu, reply.text, {
|
||||||
|
value: () => constructReply(options.msg ?? {}, reply.text, resolve),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!keyboard) delete menu[/*LANG*/ "Compose"];
|
||||||
|
|
||||||
|
if (replies.length == 0) {
|
||||||
|
if (!keyboard) {
|
||||||
|
E.showPrompt(
|
||||||
|
/*LANG*/ "Please install a keyboard app, or set a custom reply via the app loader!",
|
||||||
|
{
|
||||||
|
buttons: { Ok: true },
|
||||||
|
remove: function () {
|
||||||
|
layout.setUI();
|
||||||
|
layout.render();
|
||||||
|
reject(
|
||||||
|
"Please install a keyboard app, or set a custom reply via the app loader!"
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
keyboard.input().then((result) => {
|
||||||
|
constructReply(options.msg.id, result, resolve);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
E.showMenu(menu);
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,16 @@
|
||||||
|
{ "id": "reply",
|
||||||
|
"name": "Reply Library",
|
||||||
|
"version": "0.01",
|
||||||
|
"description": "A library for replying to text messages via predefined responses or keyboard",
|
||||||
|
"icon": "app.png",
|
||||||
|
"type": "module",
|
||||||
|
"provides_modules" : ["reply"],
|
||||||
|
"tags": "",
|
||||||
|
"supports" : ["BANGLEJS2"],
|
||||||
|
"readme": "README.md",
|
||||||
|
"interface": "interface.html",
|
||||||
|
"storage": [
|
||||||
|
{"name":"reply","url":"lib.js"}
|
||||||
|
],
|
||||||
|
"data": [{"name":"replies.json"}]
|
||||||
|
}
|
Loading…
Reference in New Issue