mirror of
https://github.com/pelgraine/Meck.git
synced 2026-06-11 00:34:50 +02:00
Updated channel sharing so channel is immediately added upon receipt of DM, no additional steps required
This commit is contained in:
@@ -661,7 +661,7 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
|
||||
_voiceEnvHandler(from.name, text);
|
||||
}
|
||||
|
||||
// Intercept channel share messages before BLE gets them
|
||||
// Intercept channel share messages -- auto-add the channel
|
||||
if (text && strncmp(text, MECK_CH_PREFIX, MECK_CH_PREFIX_LEN) == 0) {
|
||||
const char* payload = text + MECK_CH_PREFIX_LEN;
|
||||
const char* sep = strchr(payload, '|');
|
||||
@@ -677,11 +677,53 @@ void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t
|
||||
if (hexLen >= 32) {
|
||||
uint8_t secret[16];
|
||||
mesh::Utils::fromHex(secret, 16, hexStr);
|
||||
addPendingInvite(chName, secret, from.name);
|
||||
Serial.printf("Channel invite from %s: '%s'\n", from.name, chName);
|
||||
|
||||
// Check if channel already exists (by name)
|
||||
bool exists = false;
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails existing;
|
||||
if (getChannel(i, existing) && existing.name[0] != '\0'
|
||||
&& strcmp(existing.name, chName) == 0) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
// Find empty slot and add
|
||||
ChannelDetails newCh;
|
||||
memset(&newCh, 0, sizeof(newCh));
|
||||
strncpy(newCh.name, chName, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
memcpy(newCh.channel.secret, secret, 16);
|
||||
|
||||
bool added = false;
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails existing;
|
||||
if (!getChannel(i, existing) || existing.name[0] == '\0') {
|
||||
if (setChannel(i, newCh)) {
|
||||
saveChannels();
|
||||
added = true;
|
||||
Serial.printf("Channel '%s' added from %s at idx %d\n",
|
||||
chName, from.name, i);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (added && _ui) {
|
||||
char buf[64];
|
||||
snprintf(buf, sizeof(buf), "Channel '%s' added from %s", chName, from.name);
|
||||
_ui->showAlert(buf, 3000);
|
||||
} else if (!added) {
|
||||
Serial.printf("Channel '%s' from %s: no empty slot\n", chName, from.name);
|
||||
}
|
||||
} else {
|
||||
Serial.printf("Channel '%s' from %s: already exists\n", chName, from.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Sanitise display text
|
||||
// Sanitise display text for the DM conversation
|
||||
char sanitised[64];
|
||||
snprintf(sanitised, sizeof(sanitised), "Shared channel: %s", chName);
|
||||
queueMessage(from, TXT_TYPE_PLAIN, pkt, sender_timestamp, NULL, 0, sanitised);
|
||||
@@ -1355,7 +1397,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
memset(_sent_track, 0, sizeof(_sent_track));
|
||||
_sent_track_idx = 0;
|
||||
memset(_pendingInvites, 0, sizeof(_pendingInvites));
|
||||
_admin_contact_idx = -1;
|
||||
_discoveredCount = 0;
|
||||
_discoveryActive = false;
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#define FIRMWARE_VER_CODE 11
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "15 May 2026"
|
||||
#define FIRMWARE_BUILD_DATE "23 May 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "Meck v1.10"
|
||||
#define FIRMWARE_VERSION "Meck v1.11"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -100,18 +100,10 @@ struct DiscoveredNode {
|
||||
bool already_in_contacts; // true if contact was auto-added or already known
|
||||
};
|
||||
|
||||
// Channel invite received via DM -- stored in RAM until accepted/dismissed
|
||||
#define MAX_PENDING_INVITES 8
|
||||
// Channel share DM prefix
|
||||
#define MECK_CH_PREFIX "[MECK:CH]"
|
||||
#define MECK_CH_PREFIX_LEN 9
|
||||
|
||||
struct PendingChannelInvite {
|
||||
char name[32]; // channel name
|
||||
uint8_t secret[16]; // channel secret (CIPHER_KEY_SIZE bytes)
|
||||
char senderName[32]; // who shared it
|
||||
bool active; // is this slot in use
|
||||
};
|
||||
|
||||
class MyMesh : public BaseChatMesh, public DataStoreHost {
|
||||
public:
|
||||
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
|
||||
@@ -269,48 +261,6 @@ public:
|
||||
_store->saveMainIdentity(self_id);
|
||||
}
|
||||
|
||||
// --- Pending channel invites (received via DM) ---
|
||||
int getPendingInviteCount() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
const PendingChannelInvite* getPendingInvite(int idx) const {
|
||||
int seen = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) {
|
||||
if (seen == idx) return &_pendingInvites[i];
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
bool addPendingInvite(const char* name, const uint8_t* secret, const char* senderName) {
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (!_pendingInvites[i].active) {
|
||||
strncpy(_pendingInvites[i].name, name, 31);
|
||||
_pendingInvites[i].name[31] = '\0';
|
||||
memcpy(_pendingInvites[i].secret, secret, 16);
|
||||
strncpy(_pendingInvites[i].senderName, senderName, 31);
|
||||
_pendingInvites[i].senderName[31] = '\0';
|
||||
_pendingInvites[i].active = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // no free slots
|
||||
}
|
||||
void removePendingInvite(int idx) {
|
||||
int seen = 0;
|
||||
for (int i = 0; i < MAX_PENDING_INVITES; i++) {
|
||||
if (_pendingInvites[i].active) {
|
||||
if (seen == idx) { _pendingInvites[i].active = false; return; }
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
@@ -336,7 +286,6 @@ private:
|
||||
mutable bool _forceNextImport = false;
|
||||
bool _deferSaves = false;
|
||||
unsigned long _lastUserInput = 0; // millis() of last keypress -- defer saves until idle
|
||||
PendingChannelInvite _pendingInvites[MAX_PENDING_INVITES]; // RAM-only pending channel shares
|
||||
uint32_t pending_login;
|
||||
uint32_t pending_status;
|
||||
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
|
||||
|
||||
@@ -165,7 +165,6 @@ enum SettingsRowType : uint8_t {
|
||||
ROW_CH_HEADER, // "--- Channels ---" separator
|
||||
ROW_CHANNEL, // A channel entry (dynamic, index stored separately)
|
||||
ROW_ADD_CHANNEL, // "+ Add Channel (# = public)"
|
||||
ROW_PENDING_INVITE, // Pending channel invite (param = invite index)
|
||||
#ifdef HAS_SDCARD
|
||||
ROW_EXPORT_IMPORT_SUBMENU, // Folder row: "Export/Import >>"
|
||||
ROW_EXPORT_TO_SD, // "Export to SD >>" (enters flags sub-screen)
|
||||
@@ -445,10 +444,6 @@ private:
|
||||
}
|
||||
}
|
||||
addRow(ROW_ADD_CHANNEL);
|
||||
// Pending channel invites (received via DM)
|
||||
for (int pi = 0; pi < the_mesh.getPendingInviteCount(); pi++) {
|
||||
addRow(ROW_PENDING_INVITE, pi);
|
||||
}
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
} else if (_subScreen == SUB_OTA_TOOLS) {
|
||||
// --- OTA Tools sub-screen ---
|
||||
@@ -2156,16 +2151,6 @@ public:
|
||||
display.print(tmp);
|
||||
break;
|
||||
|
||||
case ROW_PENDING_INVITE: {
|
||||
const PendingChannelInvite* inv = the_mesh.getPendingInvite(_rows[i].param);
|
||||
if (inv) {
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::YELLOW);
|
||||
snprintf(tmp, sizeof(tmp), "Pending: %s", inv->name);
|
||||
display.print(tmp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
case ROW_OTA_TOOLS_SUBMENU:
|
||||
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
|
||||
@@ -2662,9 +2647,25 @@ public:
|
||||
ContactInfo ci_info;
|
||||
if (the_mesh.getContactByIdx(_shareContacts[ci], ci_info)) {
|
||||
display.setCursor(bx + 4, iy + 1);
|
||||
if (ci_info.flags & 0x01) {
|
||||
display.print("* ");
|
||||
}
|
||||
display.print(ci_info.name);
|
||||
}
|
||||
}
|
||||
|
||||
// Scroll indicator
|
||||
if (_shareContactCount > maxVisible) {
|
||||
int sbX = bx + bw - 4;
|
||||
int sbH = listBot - listTop;
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.drawRect(sbX, listTop, 3, sbH);
|
||||
int thumbH = max(4, (maxVisible * sbH) / _shareContactCount);
|
||||
int maxScroll = _shareContactCount - maxVisible;
|
||||
if (maxScroll < 1) maxScroll = 1;
|
||||
int thumbY = listTop + (_sharePickerScroll * (sbH - thumbH)) / maxScroll;
|
||||
display.fillRect(sbX + 1, thumbY + 1, 1, thumbH - 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Footer hint
|
||||
@@ -2825,7 +2826,9 @@ public:
|
||||
} else if (_editMode == EDIT_CONFIRM) {
|
||||
// Footer already covered by overlay
|
||||
} else {
|
||||
if (_subScreen != SUB_NONE) {
|
||||
if (_subScreen == SUB_CHANNELS) {
|
||||
display.print("Q:Bk C:Share");
|
||||
} else if (_subScreen != SUB_NONE) {
|
||||
display.print("Q:Back");
|
||||
} else {
|
||||
display.print("Q:Bk");
|
||||
@@ -3658,34 +3661,6 @@ public:
|
||||
startEditText("");
|
||||
break;
|
||||
|
||||
case ROW_PENDING_INVITE: {
|
||||
// Accept pending channel invite
|
||||
const PendingChannelInvite* inv = the_mesh.getPendingInvite(_rows[_cursor].param);
|
||||
if (inv) {
|
||||
ChannelDetails newCh;
|
||||
memset(&newCh, 0, sizeof(newCh));
|
||||
strncpy(newCh.name, inv->name, sizeof(newCh.name));
|
||||
newCh.name[31] = '\0';
|
||||
memcpy(newCh.channel.secret, inv->secret, 16);
|
||||
|
||||
// Find next empty slot
|
||||
bool added = false;
|
||||
for (uint8_t i = 0; i < MAX_GROUP_CHANNELS; i++) {
|
||||
ChannelDetails existing;
|
||||
if (!the_mesh.getChannel(i, existing) || existing.name[0] == '\0') {
|
||||
if (the_mesh.setChannel(i, newCh)) {
|
||||
the_mesh.saveChannels();
|
||||
Serial.printf("Settings: Accepted channel '%s' at idx %d\n", inv->name, i);
|
||||
added = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
the_mesh.removePendingInvite(_rows[_cursor].param);
|
||||
rebuildRows();
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef MECK_OTA_UPDATE
|
||||
case ROW_OTA_TOOLS_SUBMENU:
|
||||
_savedTopCursor = _cursor;
|
||||
@@ -3775,19 +3750,12 @@ public:
|
||||
}
|
||||
|
||||
// X: delete channel (when on a channel row, idx > 0)
|
||||
// dismiss pending invite (when on a pending invite row)
|
||||
if (c == 'x' || c == 'X') {
|
||||
if (_rows[_cursor].type == ROW_CHANNEL && _rows[_cursor].param > 0) {
|
||||
_editMode = EDIT_CONFIRM;
|
||||
_confirmAction = 1;
|
||||
return true;
|
||||
}
|
||||
if (_rows[_cursor].type == ROW_PENDING_INVITE) {
|
||||
the_mesh.removePendingInvite(_rows[_cursor].param);
|
||||
rebuildRows();
|
||||
Serial.println("Settings: dismissed pending channel invite");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// N: cycle notification preference (All -> Mentions -> None -> All)
|
||||
@@ -3804,19 +3772,47 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
// S: share channel with a contact via DM
|
||||
if (c == 's' || c == 'S') {
|
||||
// C: share channel with a contact via DM (channels sub-screen only)
|
||||
if ((c == 'c' || c == 'C') && _subScreen == SUB_CHANNELS) {
|
||||
if (_rows[_cursor].type == ROW_CHANNEL) {
|
||||
_shareChannelIdx = _rows[_cursor].param;
|
||||
// Populate contact list with DM-capable contacts
|
||||
// Populate contact list with DM-capable contacts, favourites first
|
||||
_shareContactCount = 0;
|
||||
int numContacts = the_mesh.getNumContacts();
|
||||
// First pass: favourites
|
||||
for (int ci = 0; ci < numContacts && _shareContactCount < SHARE_MAX_CONTACTS; ci++) {
|
||||
ContactInfo contact;
|
||||
if (the_mesh.getContactByIdx(ci, contact) && contact.type == ADV_TYPE_CHAT) {
|
||||
if (the_mesh.getContactByIdx(ci, contact) && contact.type == ADV_TYPE_CHAT
|
||||
&& (contact.flags & 0x01)) {
|
||||
_shareContacts[_shareContactCount++] = ci;
|
||||
}
|
||||
}
|
||||
int favCount = _shareContactCount;
|
||||
// Second pass: non-favourites
|
||||
for (int ci = 0; ci < numContacts && _shareContactCount < SHARE_MAX_CONTACTS; ci++) {
|
||||
ContactInfo contact;
|
||||
if (the_mesh.getContactByIdx(ci, contact) && contact.type == ADV_TYPE_CHAT
|
||||
&& !(contact.flags & 0x01)) {
|
||||
_shareContacts[_shareContactCount++] = ci;
|
||||
}
|
||||
}
|
||||
// Sort each group alphabetically by name
|
||||
auto sortRange = [&](int start, int end) {
|
||||
for (int a = start; a < end - 1; a++) {
|
||||
for (int b = a + 1; b < end; b++) {
|
||||
ContactInfo ca, cb;
|
||||
the_mesh.getContactByIdx(_shareContacts[a], ca);
|
||||
the_mesh.getContactByIdx(_shareContacts[b], cb);
|
||||
if (strcasecmp(ca.name, cb.name) > 0) {
|
||||
int tmp = _shareContacts[a];
|
||||
_shareContacts[a] = _shareContacts[b];
|
||||
_shareContacts[b] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
sortRange(0, favCount);
|
||||
sortRange(favCount, _shareContactCount);
|
||||
_sharePickerIdx = 0;
|
||||
_sharePickerScroll = 0;
|
||||
_editMode = EDIT_SHARE_PICK;
|
||||
|
||||
@@ -683,7 +683,7 @@ public:
|
||||
#endif
|
||||
y += 10;
|
||||
display.setColor(DisplayDriver::YELLOW);
|
||||
display.drawTextCentered(display.width() / 2, y, "[R] Trace [J] Games ");
|
||||
display.drawTextCentered(display.width() / 2, y, "[R] Trace [J] Games ");
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
y += 14;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user