antenna-core 1.3.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +247 -0
  2. package/package.json +11 -0
package/index.js ADDED
@@ -0,0 +1,247 @@
1
+ // antenna-core — shared logic for CLI, MCP, and Plugin
2
+ // All three import this instead of duplicating Supabase calls.
3
+
4
+ import { createClient } from "@supabase/supabase-js";
5
+
6
+ const DEFAULT_URL = "https://bcudjloikmpcqwcptuyd.supabase.co";
7
+ const DEFAULT_KEY =
8
+ "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImJjdWRqbG9pa21wY3F3Y3B0dXlkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzQ0MTg1NDgsImV4cCI6MjA4OTk5NDU0OH0.FaoC3QfpfHP1npNGjRchJAoAp2PdZtQe_WhP-t-GN1o";
9
+
10
+ let _client = null;
11
+ let _url = null;
12
+
13
+ export function getClient(url, key) {
14
+ const u = url || process.env.ANTENNA_SUPABASE_URL || process.env.ANTENNA_URL || DEFAULT_URL;
15
+ const k = key || process.env.ANTENNA_SUPABASE_KEY || process.env.ANTENNA_KEY || DEFAULT_KEY;
16
+ if (!_client || _url !== u) {
17
+ _client = createClient(u, k);
18
+ _url = u;
19
+ }
20
+ return _client;
21
+ }
22
+
23
+ export function deriveDeviceId(senderId, channel) {
24
+ return `${channel}:${senderId}`;
25
+ }
26
+
27
+ export function fuzzyCoord(lat, lng) {
28
+ return {
29
+ lat: Math.round(lat * 1000) / 1000,
30
+ lng: Math.round(lng * 1000) / 1000,
31
+ };
32
+ }
33
+
34
+ // ─── scan ────────────────────────────────────────────────────────────
35
+
36
+ export async function scan({ lat, lng, radius_m = 500, device_id, supabaseUrl, supabaseKey }) {
37
+ const sb = getClient(supabaseUrl, supabaseKey);
38
+ const fuzzy = fuzzyCoord(lat, lng);
39
+
40
+ if (device_id) {
41
+ await sb.rpc("upsert_profile_location", {
42
+ p_device_id: device_id,
43
+ p_lng: fuzzy.lng,
44
+ p_lat: fuzzy.lat,
45
+ });
46
+ }
47
+
48
+ const { data, error } = await sb.rpc("nearby_profiles", {
49
+ p_lat: fuzzy.lat,
50
+ p_lng: fuzzy.lng,
51
+ p_radius_m: radius_m,
52
+ });
53
+
54
+ if (error) throw new Error(error.message);
55
+
56
+ const others = device_id
57
+ ? (data || []).filter((p) => p.device_id !== device_id)
58
+ : data || [];
59
+
60
+ return {
61
+ count: others.length,
62
+ radius_m,
63
+ profiles: others.map((p) => ({
64
+ device_id: p.device_id,
65
+ name: p.display_name || "匿名",
66
+ emoji: p.emoji || "👤",
67
+ line1: p.line1,
68
+ line2: p.line2,
69
+ line3: p.line3,
70
+ distance_m: p.distance_m ?? p.dist_meters ?? null,
71
+ })),
72
+ };
73
+ }
74
+
75
+ // ─── getProfile ──────────────────────────────────────────────────────
76
+
77
+ export async function getProfile({ device_id, supabaseUrl, supabaseKey }) {
78
+ const sb = getClient(supabaseUrl, supabaseKey);
79
+ const { data, error } = await sb.rpc("get_profile", { p_device_id: device_id });
80
+ if (error) throw new Error(error.message);
81
+ return data || null;
82
+ }
83
+
84
+ // ─── setProfile ──────────────────────────────────────────────────────
85
+
86
+ export async function setProfile({
87
+ device_id,
88
+ display_name,
89
+ emoji = "👤",
90
+ line1,
91
+ line2,
92
+ line3,
93
+ visible = true,
94
+ supabaseUrl,
95
+ supabaseKey,
96
+ }) {
97
+ const sb = getClient(supabaseUrl, supabaseKey);
98
+ const { data, error } = await sb.rpc("upsert_profile", {
99
+ p_device_id: device_id,
100
+ p_display_name: display_name || null,
101
+ p_emoji: emoji,
102
+ p_line1: line1 || null,
103
+ p_line2: line2 || null,
104
+ p_line3: line3 || null,
105
+ p_visible: visible,
106
+ });
107
+ if (error) throw new Error(error.message);
108
+ return data;
109
+ }
110
+
111
+ // ─── accept ──────────────────────────────────────────────────────────
112
+
113
+ export async function accept({
114
+ device_id,
115
+ target_device_id,
116
+ contact_info,
117
+ supabaseUrl,
118
+ supabaseKey,
119
+ }) {
120
+ const sb = getClient(supabaseUrl, supabaseKey);
121
+
122
+ const { error } = await sb.rpc("upsert_match", {
123
+ p_device_id_a: device_id,
124
+ p_device_id_b: target_device_id,
125
+ p_reason: "",
126
+ p_score: 0,
127
+ p_status: "accepted",
128
+ p_contact_info: contact_info || null,
129
+ p_expires_hours: 24,
130
+ });
131
+ if (error) throw new Error(error.message);
132
+
133
+ // Check mutual
134
+ const { data: reverse } = await sb
135
+ .from("matches")
136
+ .select("status, contact_info_a")
137
+ .eq("device_id_a", target_device_id)
138
+ .eq("device_id_b", device_id)
139
+ .eq("status", "accepted")
140
+ .single();
141
+
142
+ const mutual = !!reverse;
143
+
144
+ return {
145
+ accepted: true,
146
+ mutual,
147
+ their_contact: mutual ? reverse?.contact_info_a || null : null,
148
+ message: mutual
149
+ ? "双向匹配成功!🎉"
150
+ : "已接受。等对方也接受后,你们就可以交换联系方式了。",
151
+ };
152
+ }
153
+
154
+ // ─── checkin ─────────────────────────────────────────────────────────
155
+
156
+ export async function checkin({ lat, lng, device_id, supabaseUrl, supabaseKey }) {
157
+ const sb = getClient(supabaseUrl, supabaseKey);
158
+ const fuzzy = fuzzyCoord(lat, lng);
159
+
160
+ // Check profile exists
161
+ const profile = await getProfile({ device_id, supabaseUrl, supabaseKey });
162
+ if (!profile) {
163
+ return {
164
+ checked_in: false,
165
+ message: "你还没有名片,先创建一个吧。",
166
+ };
167
+ }
168
+
169
+ const { error } = await sb.rpc("upsert_profile_location", {
170
+ p_device_id: device_id,
171
+ p_lng: fuzzy.lng,
172
+ p_lat: fuzzy.lat,
173
+ });
174
+ if (error) throw new Error(error.message);
175
+
176
+ return {
177
+ checked_in: true,
178
+ message: "已签到 📍 现在附近的人扫描就能看到你了。",
179
+ };
180
+ }
181
+
182
+ // ─── checkMatches ────────────────────────────────────────────────────
183
+
184
+ export async function checkMatches({ device_id, supabaseUrl, supabaseKey }) {
185
+ const sb = getClient(supabaseUrl, supabaseKey);
186
+
187
+ const { data: allMatches, error } = await sb.rpc("get_my_matches", { p_device_id: device_id });
188
+ if (error) throw new Error(error.message);
189
+
190
+ if (!allMatches?.length) {
191
+ return {
192
+ mutual_matches: [],
193
+ incoming_accepts: [],
194
+ message: "目前没有进行中的匹配。",
195
+ };
196
+ }
197
+
198
+ const myMatches = allMatches.filter((m) => m.device_id_a === device_id);
199
+ const incomingMatches = allMatches.filter((m) => m.device_id_b === device_id);
200
+
201
+ // Mutual
202
+ const mutualMatches = [];
203
+ for (const match of myMatches) {
204
+ const reverse = incomingMatches.find((m) => m.device_id_a === match.device_id_b);
205
+ if (reverse) {
206
+ const profile = await getProfile({ device_id: match.device_id_b, supabaseUrl, supabaseKey });
207
+ mutualMatches.push({
208
+ device_id: match.device_id_b,
209
+ name: profile?.display_name || "匿名",
210
+ emoji: profile?.emoji || "👤",
211
+ line1: profile?.line1,
212
+ line2: profile?.line2,
213
+ line3: profile?.line3,
214
+ their_contact: reverse.contact_info_a || null,
215
+ you_shared: match.contact_info_a || null,
216
+ });
217
+ }
218
+ }
219
+
220
+ // Incoming only
221
+ const incomingAccepts = [];
222
+ for (const match of incomingMatches) {
223
+ const iAccepted = myMatches.find((m) => m.device_id_b === match.device_id_a);
224
+ if (!iAccepted) {
225
+ const profile = await getProfile({ device_id: match.device_id_a, supabaseUrl, supabaseKey });
226
+ incomingAccepts.push({
227
+ device_id: match.device_id_a,
228
+ name: profile?.display_name || "匿名",
229
+ emoji: profile?.emoji || "👤",
230
+ line1: profile?.line1,
231
+ line2: profile?.line2,
232
+ line3: profile?.line3,
233
+ });
234
+ }
235
+ }
236
+
237
+ const messages = [];
238
+ if (mutualMatches.length > 0) messages.push(`${mutualMatches.length} 个双向匹配!可以交换联系方式了`);
239
+ if (incomingAccepts.length > 0) messages.push(`${incomingAccepts.length} 个人想认识你,等你回应`);
240
+ if (messages.length === 0) messages.push("你接受了一些匹配,但对方还没有回应。耐心等等 ⏳");
241
+
242
+ return {
243
+ mutual_matches: mutualMatches,
244
+ incoming_accepts: incomingAccepts,
245
+ message: messages.join(";"),
246
+ };
247
+ }
package/package.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "antenna-core",
3
+ "version": "1.3.29",
4
+ "description": "Shared core logic for Antenna (CLI, MCP, Plugin)",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "dependencies": {
8
+ "@supabase/supabase-js": "^2.49.4"
9
+ },
10
+ "license": "MIT"
11
+ }