feat(telegram): parse location and venue messages
- Add TelegramLocation, TelegramVenue, and TelegramMessageWithLocation types - Add formatLocationMessage() to convert location/venue shares to text - Add extractLocationData() for structured location access in ctxPayload - Handle both raw location pins and venue shares (places with names) - Include location in reply-to context for quoted messages Location messages now appear as: - [Location: lat, lon ±accuracy] for raw pins - [Venue: Name - Address (lat, lon)] for places ctxPayload includes LocationLat, LocationLon, LocationAccuracy, VenueName, and VenueAddress fields for programmatic access.
This commit is contained in:
committed by
Peter Steinberger
parent
9ffea23f31
commit
255e77f530
@@ -48,6 +48,32 @@ type MediaGroupEntry = {
|
|||||||
timer: ReturnType<typeof setTimeout>;
|
timer: ReturnType<typeof setTimeout>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Telegram Location object */
|
||||||
|
interface TelegramLocation {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
horizontal_accuracy?: number;
|
||||||
|
live_period?: number;
|
||||||
|
heading?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Telegram Venue object */
|
||||||
|
interface TelegramVenue {
|
||||||
|
location: TelegramLocation;
|
||||||
|
title: string;
|
||||||
|
address: string;
|
||||||
|
foursquare_id?: string;
|
||||||
|
foursquare_type?: string;
|
||||||
|
google_place_id?: string;
|
||||||
|
google_place_type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extended message type that may include location/venue */
|
||||||
|
type TelegramMessageWithLocation = TelegramMessage & {
|
||||||
|
location?: TelegramLocation;
|
||||||
|
venue?: TelegramVenue;
|
||||||
|
};
|
||||||
|
|
||||||
type TelegramContext = {
|
type TelegramContext = {
|
||||||
message: TelegramMessage;
|
message: TelegramMessage;
|
||||||
me?: { username?: string };
|
me?: { username?: string };
|
||||||
@@ -226,7 +252,15 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
else if (msg.document) placeholder = "<media:document>";
|
else if (msg.document) placeholder = "<media:document>";
|
||||||
|
|
||||||
const replyTarget = describeReplyTarget(msg);
|
const replyTarget = describeReplyTarget(msg);
|
||||||
const rawBody = (msg.text ?? msg.caption ?? placeholder).trim();
|
const locationText = formatLocationMessage(
|
||||||
|
msg as TelegramMessageWithLocation,
|
||||||
|
);
|
||||||
|
const rawBody = (
|
||||||
|
msg.text ??
|
||||||
|
msg.caption ??
|
||||||
|
locationText ??
|
||||||
|
placeholder
|
||||||
|
).trim();
|
||||||
if (!rawBody && allMedia.length === 0) return;
|
if (!rawBody && allMedia.length === 0) return;
|
||||||
|
|
||||||
let bodyText = rawBody;
|
let bodyText = rawBody;
|
||||||
@@ -248,6 +282,8 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
body: `${bodyText}${replySuffix}`,
|
body: `${bodyText}${replySuffix}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const locationData = extractLocationData(msg);
|
||||||
|
|
||||||
const ctxPayload = {
|
const ctxPayload = {
|
||||||
Body: body,
|
Body: body,
|
||||||
From: isGroup ? `group:${chatId}` : `telegram:${chatId}`,
|
From: isGroup ? `group:${chatId}` : `telegram:${chatId}`,
|
||||||
@@ -273,6 +309,11 @@ export function createTelegramBot(opts: TelegramBotOptions) {
|
|||||||
allMedia.length > 0
|
allMedia.length > 0
|
||||||
? (allMedia.map((m) => m.contentType).filter(Boolean) as string[])
|
? (allMedia.map((m) => m.contentType).filter(Boolean) as string[])
|
||||||
: undefined,
|
: undefined,
|
||||||
|
LocationLat: locationData?.latitude,
|
||||||
|
LocationLon: locationData?.longitude,
|
||||||
|
LocationAccuracy: locationData?.accuracy,
|
||||||
|
VenueName: locationData?.venueName,
|
||||||
|
VenueAddress: locationData?.venueAddress,
|
||||||
CommandAuthorized: commandAuthorized,
|
CommandAuthorized: commandAuthorized,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -698,6 +739,10 @@ function describeReplyTarget(msg: TelegramMessage) {
|
|||||||
else if (reply.video) body = "<media:video>";
|
else if (reply.video) body = "<media:video>";
|
||||||
else if (reply.audio || reply.voice) body = "<media:audio>";
|
else if (reply.audio || reply.voice) body = "<media:audio>";
|
||||||
else if (reply.document) body = "<media:document>";
|
else if (reply.document) body = "<media:document>";
|
||||||
|
else if ((reply as TelegramMessageWithLocation).location)
|
||||||
|
body =
|
||||||
|
formatLocationMessage(reply as TelegramMessageWithLocation) ??
|
||||||
|
"<location>";
|
||||||
}
|
}
|
||||||
if (!body) return null;
|
if (!body) return null;
|
||||||
const sender = buildSenderName(reply);
|
const sender = buildSenderName(reply);
|
||||||
@@ -708,3 +753,61 @@ function describeReplyTarget(msg: TelegramMessage) {
|
|||||||
body,
|
body,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract structured location data from a message.
|
||||||
|
*/
|
||||||
|
function extractLocationData(msg: TelegramMessage): {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
accuracy?: number;
|
||||||
|
venueName?: string;
|
||||||
|
venueAddress?: string;
|
||||||
|
} | null {
|
||||||
|
const msgWithLocation = msg as TelegramMessageWithLocation;
|
||||||
|
const { venue, location } = msgWithLocation;
|
||||||
|
|
||||||
|
if (venue) {
|
||||||
|
return {
|
||||||
|
latitude: venue.location.latitude,
|
||||||
|
longitude: venue.location.longitude,
|
||||||
|
venueName: venue.title,
|
||||||
|
venueAddress: venue.address,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
return {
|
||||||
|
latitude: location.latitude,
|
||||||
|
longitude: location.longitude,
|
||||||
|
accuracy: location.horizontal_accuracy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format location or venue message into text.
|
||||||
|
* Handles both raw location shares and venue shares (places with names).
|
||||||
|
*/
|
||||||
|
function formatLocationMessage(
|
||||||
|
msg: TelegramMessageWithLocation,
|
||||||
|
): string | null {
|
||||||
|
const { venue, location } = msg;
|
||||||
|
|
||||||
|
if (venue) {
|
||||||
|
const { latitude, longitude } = venue.location;
|
||||||
|
return `[Venue: ${venue.title} - ${venue.address} (${latitude.toFixed(6)}, ${longitude.toFixed(6)})]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (location) {
|
||||||
|
const { latitude, longitude, horizontal_accuracy } = location;
|
||||||
|
const accuracy = horizontal_accuracy
|
||||||
|
? ` ±${Math.round(horizontal_accuracy)}m`
|
||||||
|
: "";
|
||||||
|
return `[Location: ${latitude.toFixed(6)}, ${longitude.toFixed(6)}${accuracy}]`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user