When a new companion identity is created, its contact store starts empty. The repeater already knows about dozens (or hundreds) of mesh nodes via its adverts table, but the companion has to rediscover them all from scratch.
Proposed solution
A one-time "seed" operation that imports the repeater's known adverts into a companion's companion_contacts table.
1. New method on sqlite_handler
Reuses the existing companion_upsert_contact() pattern (ON CONFLICT upsert, dict-based, BLOB pubkeys):
defcompanion_import_repeater_contacts(self,companion_hash:str)->int:"""Import repeater adverts into a companion's contact store (one-time seed)."""try:withsqlite3.connect(self.sqlite_path)asconn:conn.row_factory=sqlite3.Rowrows=conn.execute("SELECT pubkey, node_name, contact_type, latitude, longitude, last_seen ""FROM adverts WHERE pubkey IS NOT NULL").fetchall()type_map={"companion":1,"repeater":2,"room_server":3}count=0forrowinrows:contact={"pubkey":bytes.fromhex(row["pubkey"]),"name":row["node_name"]or"","adv_type":type_map.get(row["contact_type"],0),"gps_lat":row["latitude"]or0.0,"gps_lon":row["longitude"]or0.0,"last_advert_timestamp":int(row["last_seen"]or0),"lastmod":int(row["last_seen"]or0),}ifself.companion_upsert_contact(companion_hash,contact):count+=1returncountexceptExceptionase:logger.error(f"Failed to import repeater contacts: {e}")return0
After the SQL import, call bridge.contacts.load_from_dicts() to hot-reload into memory.
Why this approach
Reuses companion_upsert_contact() — handles TEXT→BLOB pubkey conversion, string→int adv_type mapping, and ON CONFLICT dedup
Follows the existing dict-based pattern used by companion_save_contacts and companion_load_contacts
Idempotent — safe to call multiple times (upsert skips or updates existing contacts)
No new tables or schema changes
Usage
Intended as a one-time seed when setting up a new companion identity, triggered from the dashboard UI.
## Problem
When a new companion identity is created, its contact store starts empty. The repeater already knows about dozens (or hundreds) of mesh nodes via its `adverts` table, but the companion has to rediscover them all from scratch.
## Proposed solution
A one-time "seed" operation that imports the repeater's known adverts into a companion's `companion_contacts` table.
### 1. New method on `sqlite_handler`
Reuses the existing `companion_upsert_contact()` pattern (ON CONFLICT upsert, dict-based, BLOB pubkeys):
```python
def companion_import_repeater_contacts(self, companion_hash: str) -> int:
"""Import repeater adverts into a companion's contact store (one-time seed)."""
try:
with sqlite3.connect(self.sqlite_path) as conn:
conn.row_factory = sqlite3.Row
rows = conn.execute(
"SELECT pubkey, node_name, contact_type, latitude, longitude, last_seen "
"FROM adverts WHERE pubkey IS NOT NULL"
).fetchall()
type_map = {"companion": 1, "repeater": 2, "room_server": 3}
count = 0
for row in rows:
contact = {
"pubkey": bytes.fromhex(row["pubkey"]),
"name": row["node_name"] or "",
"adv_type": type_map.get(row["contact_type"], 0),
"gps_lat": row["latitude"] or 0.0,
"gps_lon": row["longitude"] or 0.0,
"last_advert_timestamp": int(row["last_seen"] or 0),
"lastmod": int(row["last_seen"] or 0),
}
if self.companion_upsert_contact(companion_hash, contact):
count += 1
return count
except Exception as e:
logger.error(f"Failed to import repeater contacts: {e}")
return 0
```
### 2. New endpoint on `CompanionAPIEndpoints`
```
POST /api/companion/import_repeater_contacts
Body: { "companion_name": "MyCompanion" }
Response: { "success": true, "data": { "imported": 47 } }
```
After the SQL import, call `bridge.contacts.load_from_dicts()` to hot-reload into memory.
### Why this approach
- Reuses `companion_upsert_contact()` — handles TEXT→BLOB pubkey conversion, string→int `adv_type` mapping, and ON CONFLICT dedup
- Follows the existing dict-based pattern used by `companion_save_contacts` and `companion_load_contacts`
- Idempotent — safe to call multiple times (upsert skips or updates existing contacts)
- No new tables or schema changes
### Usage
Intended as a one-time seed when setting up a new companion identity, triggered from the dashboard UI.
<!-- github-sync-id:4062800682 -->
@agessaman maybe we have this as a options when creating the companion, (sometimes 500+ contacts might not be that useful) ;-)
@agessaman maybe we have this as a options when creating the companion, (sometimes 500+ contacts might not be that useful) ;-)
<!-- github-comment-id:4046749128 -->
100%, I think it would be awesome for legacy pyMC users as an option... for new users, not so helpful.
with a a feature like this, users may even choose to seed one ID with all prior contacts from their Repeater and/or create a pure ID with none.
100%, I think it would be awesome for legacy pyMC users as an option... for new users, not so helpful.
with a a feature like this, users may even choose to seed one ID with all prior contacts from their Repeater and/or create a pure ID with none.
<!-- github-comment-id:4047696004 -->
@rightup Agreed. I like it. I'd like to propose allowing users to have some additional control over what is imported. At a minimum, to select the types of nodes to be imported and the recency of the advert.
Update to add: As I starting thinking about implementation a bit more, I actually think we need:
Import maximum (only give me 500 entries)
Import type(s) (only give me repeaters + companions, or only room servers, etc.)
Import recency (adverted in the last x days)
@rightup Agreed. I like it. I'd like to propose allowing users to have some additional control over what is imported. At a minimum, to select the types of nodes to be imported and the recency of the advert.
Update to add: As I starting thinking about implementation a bit more, I actually think we need:
- Import maximum (only give me 500 entries)
- Import type(s) (only give me repeaters + companions, or only room servers, etc.)
- Import recency (adverted in the last x days)
<!-- github-comment-id:4048398038 -->
Okay, I have it working. Going to test a bit more and then I'll send it over.
<img width="537" height="479" alt="Image" src="https://github.com/user-attachments/assets/35f42932-23b3-41f4-96e6-175b6be5f695" />
Okay, I have it working. Going to test a bit more and then I'll send it over.
<!-- github-comment-id:4048816172 -->
would 2500 be a bridge too far? Also arbitrary — without pushing the limits I'm not sure what the frontend (or back) can reasonably handle. So far lazy loading and careful management of dom elements has worked well enough
would 2500 be a bridge too far? Also arbitrary — without pushing the limits I'm not sure what the frontend (or back) can reasonably handle. So far lazy loading and careful management of dom elements has worked well enough
<!-- github-comment-id:4048849698 -->
We can try it. It is set by a constant in pyMC_core, but I can make a small modification to expose it through an optional config option in pyMC_repeater.
We can try it. It is set by a constant in pyMC_core, but I can make a small modification to expose it through an optional config option in pyMC_repeater.
<!-- github-comment-id:4049094724 -->
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Problem
When a new companion identity is created, its contact store starts empty. The repeater already knows about dozens (or hundreds) of mesh nodes via its
advertstable, but the companion has to rediscover them all from scratch.Proposed solution
A one-time "seed" operation that imports the repeater's known adverts into a companion's
companion_contactstable.1. New method on
sqlite_handlerReuses the existing
companion_upsert_contact()pattern (ON CONFLICT upsert, dict-based, BLOB pubkeys):2. New endpoint on
CompanionAPIEndpointsAfter the SQL import, call
bridge.contacts.load_from_dicts()to hot-reload into memory.Why this approach
companion_upsert_contact()— handles TEXT→BLOB pubkey conversion, string→intadv_typemapping, and ON CONFLICT dedupcompanion_save_contactsandcompanion_load_contactsUsage
Intended as a one-time seed when setting up a new companion identity, triggered from the dashboard UI.
@agessaman maybe we have this as a options when creating the companion, (sometimes 500+ contacts might not be that useful) ;-)
100%, I think it would be awesome for legacy pyMC users as an option... for new users, not so helpful.
with a a feature like this, users may even choose to seed one ID with all prior contacts from their Repeater and/or create a pure ID with none.
@rightup Agreed. I like it. I'd like to propose allowing users to have some additional control over what is imported. At a minimum, to select the types of nodes to be imported and the recency of the advert.
Update to add: As I starting thinking about implementation a bit more, I actually think we need:
Okay, I have it working. Going to test a bit more and then I'll send it over.
what's the companion max? @agessaman
"where we're going, there are no caps" ⚡️🚙🔥🔥🔥
Currently 1000, but that's really arbitrary. I didn't want to grow it until it choked the app.
would 2500 be a bridge too far? Also arbitrary — without pushing the limits I'm not sure what the frontend (or back) can reasonably handle. So far lazy loading and careful management of dom elements has worked well enough
We can try it. It is set by a constant in pyMC_core, but I can make a small modification to expose it through an optional config option in pyMC_repeater.
merged