2019-01-25 17:04:26 +01:00
|
|
|
package net.osmand.telegram.utils
|
|
|
|
|
|
|
|
import android.os.AsyncTask
|
|
|
|
import net.osmand.Location
|
2019-02-01 12:34:22 +01:00
|
|
|
import net.osmand.data.LatLon
|
2019-01-25 17:04:26 +01:00
|
|
|
import net.osmand.telegram.TelegramApplication
|
2019-01-28 17:09:35 +01:00
|
|
|
import net.osmand.telegram.helpers.LocationMessages
|
|
|
|
import net.osmand.telegram.helpers.LocationMessages.BufferMessage
|
2019-01-29 18:03:44 +01:00
|
|
|
import net.osmand.telegram.helpers.LocationMessages.LocationMessage
|
2019-01-25 17:04:26 +01:00
|
|
|
import net.osmand.telegram.helpers.TelegramHelper
|
|
|
|
import net.osmand.telegram.helpers.TelegramUiHelper
|
|
|
|
import net.osmand.util.GeoPointParserUtil
|
2019-01-30 18:14:05 +01:00
|
|
|
import net.osmand.util.MapUtils
|
2019-01-25 17:04:26 +01:00
|
|
|
import org.drinkless.td.libcore.telegram.TdApi
|
|
|
|
import java.io.File
|
|
|
|
import java.text.SimpleDateFormat
|
|
|
|
import java.util.*
|
|
|
|
|
2019-02-01 12:34:22 +01:00
|
|
|
const val TRACKS_DIR = "tracker/"
|
2019-01-25 17:04:26 +01:00
|
|
|
|
|
|
|
object OsmandLocationUtils {
|
|
|
|
|
|
|
|
const val DEVICE_PREFIX = "Device: "
|
|
|
|
const val LOCATION_PREFIX = "Location: "
|
|
|
|
const val LAST_LOCATION_PREFIX = "Last location: "
|
|
|
|
const val UPDATED_PREFIX = "Updated: "
|
|
|
|
const val USER_TEXT_LOCATION_TITLE = "\uD83D\uDDFA OsmAnd sharing:"
|
|
|
|
|
|
|
|
const val SHARING_LINK = "https://play.google.com/store/apps/details?id=net.osmand.telegram"
|
|
|
|
|
|
|
|
const val ALTITUDE_PREFIX = "Altitude: "
|
|
|
|
const val SPEED_PREFIX = "Speed: "
|
|
|
|
const val HDOP_PREFIX = "Horizontal precision: "
|
|
|
|
|
|
|
|
const val NOW = "now"
|
|
|
|
const val FEW_SECONDS_AGO = "few seconds ago"
|
|
|
|
const val SECONDS_AGO_SUFFIX = " seconds ago"
|
|
|
|
const val MINUTES_AGO_SUFFIX = " minutes ago"
|
|
|
|
const val HOURS_AGO_SUFFIX = " hours ago"
|
|
|
|
const val UTC_FORMAT_SUFFIX = " UTC"
|
|
|
|
|
|
|
|
val UTC_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd", Locale.US).apply {
|
|
|
|
timeZone = TimeZone.getTimeZone("UTC")
|
|
|
|
}
|
|
|
|
|
|
|
|
val UTC_TIME_FORMAT = SimpleDateFormat("HH:mm:ss", Locale.US).apply {
|
|
|
|
timeZone = TimeZone.getTimeZone("UTC")
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getLastUpdatedTime(message: TdApi.Message): Int {
|
|
|
|
val content = message.content
|
|
|
|
return when (content) {
|
|
|
|
is MessageOsmAndBotLocation -> content.lastUpdated
|
2019-01-30 18:14:05 +01:00
|
|
|
is MessageUserLocation -> content.lastUpdated
|
2019-01-25 17:04:26 +01:00
|
|
|
else -> Math.max(message.editDate, message.date)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getOsmAndBotDeviceName(message: TdApi.Message): String {
|
|
|
|
var deviceName = ""
|
|
|
|
if (message.replyMarkup is TdApi.ReplyMarkupInlineKeyboard) {
|
|
|
|
val replyMarkup = message.replyMarkup as TdApi.ReplyMarkupInlineKeyboard
|
|
|
|
try {
|
2019-02-11 18:05:49 +01:00
|
|
|
val content = message.content
|
|
|
|
when {
|
|
|
|
replyMarkup.rows[0].size > 1 -> deviceName = replyMarkup.rows[0][1].text.split("\\s".toRegex())[1]
|
|
|
|
content is TdApi.MessageText -> deviceName = content.text.text.lines().firstOrNull()?.removePrefix(DEVICE_PREFIX) ?: ""
|
|
|
|
content is MessageOsmAndBotLocation -> deviceName = content.deviceName
|
2019-02-08 18:12:17 +01:00
|
|
|
}
|
2019-01-25 17:04:26 +01:00
|
|
|
} catch (e: Exception) {
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return deviceName
|
|
|
|
}
|
|
|
|
|
2019-02-08 18:12:17 +01:00
|
|
|
fun parseMapLocation(message: TdApi.Message, botLocation: Boolean): MessageLocation {
|
|
|
|
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation()
|
2019-01-25 17:04:26 +01:00
|
|
|
val messageLocation = message.content as TdApi.MessageLocation
|
2019-02-08 18:12:17 +01:00
|
|
|
res.apply {
|
2019-01-25 17:04:26 +01:00
|
|
|
lat = messageLocation.location.latitude
|
|
|
|
lon = messageLocation.location.longitude
|
|
|
|
lastUpdated = getLastUpdatedTime(message)
|
2019-02-08 18:12:17 +01:00
|
|
|
type = LocationMessages.TYPE_MAP
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
2019-02-08 18:12:17 +01:00
|
|
|
if (res is MessageOsmAndBotLocation) {
|
|
|
|
res.deviceName = getOsmAndBotDeviceName(message)
|
2019-01-30 18:14:05 +01:00
|
|
|
}
|
2019-02-08 18:12:17 +01:00
|
|
|
|
|
|
|
return res
|
2019-01-30 18:14:05 +01:00
|
|
|
}
|
|
|
|
|
2019-02-11 18:05:49 +01:00
|
|
|
fun getSenderMessageId(message: TdApi.Message): Int {
|
|
|
|
val forwardInfo = message.forwardInfo
|
|
|
|
return if (forwardInfo != null && forwardInfo is TdApi.MessageForwardedFromUser) {
|
|
|
|
forwardInfo.senderUserId
|
|
|
|
} else {
|
|
|
|
message.senderUserId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-01 12:34:22 +01:00
|
|
|
fun parseMessage(message: TdApi.Message, helper: TelegramHelper, previousMessageLatLon: LatLon?): LocationMessage? {
|
2019-02-08 18:12:17 +01:00
|
|
|
val parsedContent = parseMessageContent(message, helper)
|
2019-02-11 18:05:49 +01:00
|
|
|
return createLocationMessage(message, parsedContent, previousMessageLatLon)
|
2019-02-08 18:12:17 +01:00
|
|
|
}
|
2019-01-30 18:14:05 +01:00
|
|
|
|
2019-02-08 18:12:17 +01:00
|
|
|
fun parseMessageContent(message: TdApi.Message, helper: TelegramHelper): MessageLocation? {
|
2019-02-11 18:05:49 +01:00
|
|
|
val senderUserId = getSenderMessageId(message)
|
2019-02-08 18:12:17 +01:00
|
|
|
val fromBot = helper.isOsmAndBot(senderUserId)
|
|
|
|
val viaBot = helper.isOsmAndBot(message.viaBotUserId)
|
|
|
|
return when (message.content) {
|
|
|
|
is TdApi.MessageText -> parseTextLocation((message.content as TdApi.MessageText).text, (fromBot || viaBot))
|
|
|
|
is TdApi.MessageLocation -> parseMapLocation(message, (fromBot || viaBot))
|
|
|
|
is MessageLocation -> message.content as MessageLocation
|
|
|
|
else -> null
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
2019-02-08 18:12:17 +01:00
|
|
|
}
|
2019-01-30 18:14:05 +01:00
|
|
|
|
2019-02-11 18:05:49 +01:00
|
|
|
fun createLocationMessage(message: TdApi.Message, content:MessageLocation?, previousMessageLatLon: LatLon?):LocationMessage?{
|
2019-02-08 18:12:17 +01:00
|
|
|
if (content == null) {
|
|
|
|
return null
|
|
|
|
}
|
2019-02-11 18:05:49 +01:00
|
|
|
val senderUserId = getSenderMessageId(message)
|
2019-02-08 18:12:17 +01:00
|
|
|
val messageType = getMessageType(message)
|
|
|
|
val distanceFromPrev = if (previousMessageLatLon != null) MapUtils.getDistance(previousMessageLatLon, content.lat, content.lon) else 0.0
|
|
|
|
val deviceName = if (content is MessageOsmAndBotLocation) content.deviceName else ""
|
|
|
|
|
|
|
|
return LocationMessage(senderUserId, message.chatId, content.lat, content.lon,
|
|
|
|
content.altitude, content.speed, content.hdop, content.bearing,
|
|
|
|
content.lastUpdated * 1000L, messageType, message.id, distanceFromPrev, deviceName
|
|
|
|
)
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
|
2019-02-08 18:12:17 +01:00
|
|
|
fun getMessageType(message: TdApi.Message): Int {
|
2019-01-30 18:14:05 +01:00
|
|
|
val oldContent = message.content
|
2019-02-08 18:12:17 +01:00
|
|
|
return when (oldContent) {
|
|
|
|
is TdApi.MessageText -> LocationMessages.TYPE_TEXT
|
|
|
|
is TdApi.MessageLocation -> LocationMessages.TYPE_MAP
|
|
|
|
is MessageLocation -> oldContent.type
|
|
|
|
else -> -1
|
2019-01-30 18:14:05 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-25 17:04:26 +01:00
|
|
|
fun formatLocation(sig: Location): String {
|
|
|
|
return String.format(Locale.US, "%.5f, %.5f", sig.latitude, sig.longitude)
|
|
|
|
}
|
|
|
|
|
2019-01-27 19:59:07 +01:00
|
|
|
fun formatLocation(sig: LocationMessage): String {
|
2019-01-25 17:04:26 +01:00
|
|
|
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
|
|
|
|
}
|
|
|
|
|
2019-01-28 17:09:35 +01:00
|
|
|
fun formatLocation(sig: BufferMessage): String {
|
|
|
|
return String.format(Locale.US, "%.5f, %.5f", sig.lat, sig.lon)
|
|
|
|
}
|
|
|
|
|
2019-01-25 17:04:26 +01:00
|
|
|
fun formatFullTime(ti: Long): String {
|
|
|
|
val dt = Date(ti)
|
|
|
|
return UTC_DATE_FORMAT.format(dt) + " " + UTC_TIME_FORMAT.format(dt) + " UTC"
|
|
|
|
}
|
|
|
|
|
|
|
|
fun parseOsmAndBotLocationContent(oldContent: MessageOsmAndBotLocation, content: TdApi.MessageContent): MessageOsmAndBotLocation {
|
|
|
|
val messageLocation = content as TdApi.MessageLocation
|
|
|
|
return MessageOsmAndBotLocation().apply {
|
2019-02-08 18:12:17 +01:00
|
|
|
deviceName = oldContent.deviceName
|
2019-01-25 17:04:26 +01:00
|
|
|
lat = messageLocation.location.latitude
|
|
|
|
lon = messageLocation.location.longitude
|
|
|
|
lastUpdated = (System.currentTimeMillis() / 1000).toInt()
|
2019-02-08 18:12:17 +01:00
|
|
|
type = LocationMessages.TYPE_MAP
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-10 17:00:45 +02:00
|
|
|
fun parseTextLocation(text: TdApi.FormattedText, botLocation: Boolean): MessageLocation? {
|
|
|
|
if (botLocation && !text.text.startsWith(DEVICE_PREFIX) || !botLocation && !text.text.startsWith(USER_TEXT_LOCATION_TITLE)) {
|
|
|
|
return null
|
|
|
|
}
|
2019-01-30 18:14:05 +01:00
|
|
|
val res = if (botLocation) MessageOsmAndBotLocation() else MessageUserLocation()
|
2019-02-08 18:12:17 +01:00
|
|
|
res.type = LocationMessages.TYPE_TEXT
|
2019-01-25 17:04:26 +01:00
|
|
|
var locationNA = false
|
|
|
|
for (s in text.text.lines()) {
|
|
|
|
when {
|
|
|
|
s.startsWith(DEVICE_PREFIX) -> {
|
|
|
|
if (res is MessageOsmAndBotLocation) {
|
2019-02-08 18:12:17 +01:00
|
|
|
res.deviceName = s.removePrefix(DEVICE_PREFIX)
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
s.startsWith(LOCATION_PREFIX) || s.startsWith(LAST_LOCATION_PREFIX) -> {
|
|
|
|
var locStr: String
|
|
|
|
var parse = true
|
|
|
|
if (s.startsWith(LAST_LOCATION_PREFIX)) {
|
|
|
|
locStr = s.removePrefix(LAST_LOCATION_PREFIX)
|
|
|
|
if (!locationNA) {
|
|
|
|
parse = false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
locStr = s.removePrefix(LOCATION_PREFIX)
|
|
|
|
if (locStr.trim() == "n/a") {
|
|
|
|
locationNA = true
|
|
|
|
parse = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (parse) {
|
|
|
|
try {
|
|
|
|
val urlTextEntity =
|
|
|
|
text.entities.firstOrNull { it.type is TdApi.TextEntityTypeTextUrl }
|
|
|
|
if (urlTextEntity != null && urlTextEntity.offset == text.text.indexOf(
|
|
|
|
locStr
|
|
|
|
)
|
|
|
|
) {
|
|
|
|
val url = (urlTextEntity.type as TdApi.TextEntityTypeTextUrl).url
|
|
|
|
val point: GeoPointParserUtil.GeoParsedPoint? =
|
|
|
|
GeoPointParserUtil.parse(url)
|
|
|
|
if (point != null) {
|
|
|
|
res.lat = point.latitude
|
|
|
|
res.lon = point.longitude
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
val (latS, lonS) = locStr.split(" ")
|
|
|
|
res.lat = latS.dropLast(1).toDouble()
|
|
|
|
res.lon = lonS.toDouble()
|
|
|
|
|
|
|
|
val timeIndex = locStr.indexOf("(")
|
|
|
|
if (timeIndex != -1) {
|
|
|
|
val updatedS = locStr.substring(timeIndex, locStr.length)
|
|
|
|
res.lastUpdated =
|
2019-02-08 18:12:17 +01:00
|
|
|
(parseTime(updatedS.removePrefix("(").removeSuffix(")")) / 1000).toInt()
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.startsWith(ALTITUDE_PREFIX) -> {
|
|
|
|
val altStr = s.removePrefix(ALTITUDE_PREFIX)
|
|
|
|
try {
|
|
|
|
val alt = altStr.split(" ").first()
|
|
|
|
res.altitude = alt.toDouble()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.startsWith(SPEED_PREFIX) -> {
|
|
|
|
val altStr = s.removePrefix(SPEED_PREFIX)
|
|
|
|
try {
|
|
|
|
val alt = altStr.split(" ").first()
|
|
|
|
res.speed = alt.toDouble()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.startsWith(HDOP_PREFIX) -> {
|
|
|
|
val altStr = s.removePrefix(HDOP_PREFIX)
|
|
|
|
try {
|
|
|
|
val alt = altStr.split(" ").first()
|
|
|
|
res.hdop = alt.toDouble()
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s.startsWith(UPDATED_PREFIX) -> {
|
|
|
|
if (res.lastUpdated == 0) {
|
|
|
|
val updatedStr = s.removePrefix(UPDATED_PREFIX)
|
|
|
|
val endIndex = updatedStr.indexOf("(")
|
|
|
|
val updatedS = updatedStr.substring(
|
|
|
|
0,
|
|
|
|
if (endIndex != -1) endIndex else updatedStr.length
|
|
|
|
)
|
|
|
|
val parsedTime = (parseTime(updatedS.trim()) / 1000).toInt()
|
|
|
|
val currentTime = (System.currentTimeMillis() / 1000) - 1
|
|
|
|
res.lastUpdated =
|
2019-02-08 18:12:17 +01:00
|
|
|
if (parsedTime < currentTime) parsedTime else currentTime.toInt()
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
|
|
|
fun parseTime(timeS: String): Long {
|
|
|
|
try {
|
|
|
|
when {
|
|
|
|
timeS.endsWith(FEW_SECONDS_AGO) -> return System.currentTimeMillis() - 5000
|
|
|
|
|
|
|
|
timeS.endsWith(SECONDS_AGO_SUFFIX) -> {
|
|
|
|
val locStr = timeS.removeSuffix(SECONDS_AGO_SUFFIX)
|
|
|
|
return System.currentTimeMillis() - locStr.toLong() * 1000
|
|
|
|
}
|
|
|
|
timeS.endsWith(MINUTES_AGO_SUFFIX) -> {
|
|
|
|
val locStr = timeS.removeSuffix(MINUTES_AGO_SUFFIX)
|
|
|
|
val minutes = locStr.toLong()
|
|
|
|
return System.currentTimeMillis() - minutes * 60 * 1000
|
|
|
|
}
|
|
|
|
timeS.endsWith(HOURS_AGO_SUFFIX) -> {
|
|
|
|
val locStr = timeS.removeSuffix(HOURS_AGO_SUFFIX)
|
|
|
|
val hours = locStr.toLong()
|
|
|
|
return (System.currentTimeMillis() - hours * 60 * 60 * 1000)
|
|
|
|
}
|
|
|
|
timeS.endsWith(UTC_FORMAT_SUFFIX) -> {
|
|
|
|
val locStr = timeS.removeSuffix(UTC_FORMAT_SUFFIX)
|
|
|
|
val (latS, lonS) = locStr.split(" ")
|
|
|
|
val date = UTC_DATE_FORMAT.parse(latS)
|
|
|
|
val time = UTC_TIME_FORMAT.parse(lonS)
|
|
|
|
val res = date.time + time.time
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (e: Exception) {
|
|
|
|
e.printStackTrace()
|
|
|
|
}
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2019-01-27 19:59:07 +01:00
|
|
|
fun getTextMessageContent(updateId: Int, location: LocationMessage): TdApi.InputMessageText {
|
2019-01-28 17:09:35 +01:00
|
|
|
val entities = mutableListOf<TdApi.TextEntity>()
|
|
|
|
val builder = StringBuilder()
|
|
|
|
val locationMessage = formatLocation(location)
|
|
|
|
|
|
|
|
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
|
|
|
|
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
|
|
|
|
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
|
|
|
|
builder.append("$USER_TEXT_LOCATION_TITLE\n")
|
|
|
|
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(LOCATION_PREFIX)
|
|
|
|
|
|
|
|
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
|
|
|
|
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
|
|
|
|
builder.append("$locationMessage\n")
|
|
|
|
|
|
|
|
if (location.altitude != 0.0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
|
|
|
|
}
|
|
|
|
if (location.speed > 0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
|
|
|
|
}
|
|
|
|
if (location.hdop != 0.0 && location.speed == 0.0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
|
|
|
|
}
|
|
|
|
if (updateId == 0) {
|
2019-01-29 18:03:44 +01:00
|
|
|
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
|
2019-01-28 17:09:35 +01:00
|
|
|
} else {
|
2019-01-29 18:03:44 +01:00
|
|
|
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
|
2019-01-28 17:09:35 +01:00
|
|
|
}
|
|
|
|
val textMessage = builder.toString().trim()
|
|
|
|
|
|
|
|
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
fun getTextMessageContent(updateId: Int, location: BufferMessage): TdApi.InputMessageText {
|
2019-01-25 17:04:26 +01:00
|
|
|
val entities = mutableListOf<TdApi.TextEntity>()
|
|
|
|
val builder = StringBuilder()
|
|
|
|
val locationMessage = formatLocation(location)
|
|
|
|
|
|
|
|
val firstSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ')
|
|
|
|
val secondSpace = USER_TEXT_LOCATION_TITLE.indexOf(' ', firstSpace + 1)
|
|
|
|
entities.add(TdApi.TextEntity(builder.length + firstSpace + 1, secondSpace - firstSpace, TdApi.TextEntityTypeTextUrl(SHARING_LINK)))
|
|
|
|
builder.append("$USER_TEXT_LOCATION_TITLE\n")
|
|
|
|
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, LOCATION_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(LOCATION_PREFIX)
|
|
|
|
|
|
|
|
entities.add(TdApi.TextEntity(builder.length, locationMessage.length,
|
|
|
|
TdApi.TextEntityTypeTextUrl("$BASE_SHARING_URL?lat=${location.lat}&lon=${location.lon}")))
|
|
|
|
builder.append("$locationMessage\n")
|
|
|
|
|
|
|
|
if (location.altitude != 0.0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, ALTITUDE_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$ALTITUDE_PREFIX%.1f m\n", location.altitude))
|
|
|
|
}
|
|
|
|
if (location.speed > 0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, SPEED_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$SPEED_PREFIX%.1f m/s\n", location.speed))
|
|
|
|
}
|
|
|
|
if (location.hdop != 0.0 && location.speed == 0.0) {
|
|
|
|
entities.add(TdApi.TextEntity(builder.lastIndex, HDOP_PREFIX.length, TdApi.TextEntityTypeBold()))
|
|
|
|
builder.append(String.format(Locale.US, "$HDOP_PREFIX%d m\n", location.hdop.toInt()))
|
|
|
|
}
|
|
|
|
if (updateId == 0) {
|
2019-01-29 18:03:44 +01:00
|
|
|
builder.append(String.format("$UPDATED_PREFIX%s\n", formatFullTime(location.time)))
|
2019-01-25 17:04:26 +01:00
|
|
|
} else {
|
2019-01-29 18:03:44 +01:00
|
|
|
builder.append(String.format("$UPDATED_PREFIX%s (%d)\n", formatFullTime(location.time), updateId))
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
val textMessage = builder.toString().trim()
|
|
|
|
|
|
|
|
return TdApi.InputMessageText(TdApi.FormattedText(textMessage, entities.toTypedArray()), true, true)
|
|
|
|
}
|
|
|
|
|
2019-01-27 19:59:07 +01:00
|
|
|
fun convertLocationMessagesToGpxFiles(items: List<LocationMessage>, newGpxPerChat: Boolean = true): List<GPXUtilities.GPXFile> {
|
2019-01-25 17:04:26 +01:00
|
|
|
val dataTracks = ArrayList<GPXUtilities.GPXFile>()
|
|
|
|
|
|
|
|
var previousTime: Long = -1
|
|
|
|
var previousChatId: Long = -1
|
|
|
|
var previousUserId = -1
|
|
|
|
var segment: GPXUtilities.TrkSegment? = null
|
|
|
|
var track: GPXUtilities.Track? = null
|
|
|
|
var gpx: GPXUtilities.GPXFile? = null
|
2019-02-01 12:34:22 +01:00
|
|
|
var countedLocations = 0
|
2019-01-25 17:04:26 +01:00
|
|
|
|
|
|
|
items.forEach {
|
|
|
|
val userId = it.userId
|
|
|
|
val chatId = it.chatId
|
2019-01-29 18:03:44 +01:00
|
|
|
val time = it.time
|
2019-02-01 12:34:22 +01:00
|
|
|
if (previousTime >= time) {
|
|
|
|
return@forEach
|
|
|
|
}
|
|
|
|
countedLocations++
|
2019-01-25 17:04:26 +01:00
|
|
|
if (previousUserId != userId || (newGpxPerChat && previousChatId != chatId)) {
|
|
|
|
gpx = GPXUtilities.GPXFile()
|
|
|
|
gpx!!.chatId = chatId
|
|
|
|
gpx!!.userId = userId
|
|
|
|
previousTime = 0
|
|
|
|
track = null
|
|
|
|
segment = null
|
|
|
|
dataTracks.add(gpx!!)
|
|
|
|
}
|
|
|
|
val pt = GPXUtilities.WptPt()
|
|
|
|
pt.userId = userId
|
|
|
|
pt.chatId = chatId
|
|
|
|
pt.lat = it.lat
|
|
|
|
pt.lon = it.lon
|
|
|
|
pt.ele = it.altitude
|
|
|
|
pt.speed = it.speed
|
|
|
|
pt.hdop = it.hdop
|
|
|
|
pt.time = time
|
|
|
|
val currentInterval = Math.abs(time - previousTime)
|
|
|
|
if (track != null) {
|
|
|
|
if (currentInterval < 30 * 60 * 1000) {
|
|
|
|
// 30 minute - same segment
|
|
|
|
segment!!.points.add(pt)
|
|
|
|
} else {
|
|
|
|
segment = GPXUtilities.TrkSegment()
|
|
|
|
segment!!.points.add(pt)
|
|
|
|
track!!.segments.add(segment)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
track = GPXUtilities.Track()
|
|
|
|
segment = GPXUtilities.TrkSegment()
|
|
|
|
track!!.segments.add(segment)
|
|
|
|
segment!!.points.add(pt)
|
|
|
|
|
|
|
|
gpx!!.tracks.add(track)
|
|
|
|
}
|
|
|
|
previousTime = time
|
|
|
|
previousUserId = userId
|
|
|
|
previousChatId = chatId
|
|
|
|
}
|
|
|
|
|
|
|
|
return dataTracks
|
|
|
|
}
|
|
|
|
|
|
|
|
fun saveGpx(app: TelegramApplication, listener: SaveGpxListener, dir: File, gpxFile: GPXUtilities.GPXFile) {
|
|
|
|
if (!gpxFile.isEmpty) {
|
|
|
|
val task = SaveGPXTrackToFileTask(app, listener, gpxFile, dir, 0)
|
|
|
|
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
abstract class MessageLocation : TdApi.MessageContent() {
|
|
|
|
|
|
|
|
var lat: Double = Double.NaN
|
|
|
|
internal set
|
|
|
|
var lon: Double = Double.NaN
|
|
|
|
internal set
|
|
|
|
var lastUpdated: Int = 0
|
|
|
|
internal set
|
|
|
|
var speed: Double = 0.0
|
|
|
|
internal set
|
|
|
|
var altitude: Double = 0.0
|
|
|
|
internal set
|
|
|
|
var hdop: Double = 0.0
|
|
|
|
internal set
|
|
|
|
var bearing: Double = 0.0
|
|
|
|
internal set
|
2019-01-29 18:03:44 +01:00
|
|
|
var type: Int = -1
|
|
|
|
internal set
|
2019-01-25 17:04:26 +01:00
|
|
|
|
|
|
|
override fun getConstructor() = -1
|
|
|
|
|
|
|
|
abstract fun isValid(): Boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
class MessageOsmAndBotLocation : MessageLocation() {
|
|
|
|
|
2019-02-08 18:12:17 +01:00
|
|
|
var deviceName: String = ""
|
2019-01-25 17:04:26 +01:00
|
|
|
internal set
|
|
|
|
|
2019-02-08 18:12:17 +01:00
|
|
|
override fun isValid() = deviceName != "" && lat != Double.NaN && lon != Double.NaN
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
|
2019-01-30 18:14:05 +01:00
|
|
|
class MessageUserLocation : MessageLocation() {
|
2019-01-25 17:04:26 +01:00
|
|
|
|
|
|
|
override fun isValid() = lat != Double.NaN && lon != Double.NaN
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
private class SaveGPXTrackToFileTask internal constructor(
|
|
|
|
private val app: TelegramApplication, private val listener: SaveGpxListener?,
|
|
|
|
private val gpxFile: GPXUtilities.GPXFile, private val dir: File, private val userId: Int
|
|
|
|
) :
|
|
|
|
AsyncTask<Void, Void, List<String>>() {
|
|
|
|
|
|
|
|
override fun doInBackground(vararg params: Void): List<String> {
|
|
|
|
val warnings = ArrayList<String>()
|
|
|
|
dir.mkdirs()
|
|
|
|
if (dir.parentFile.canWrite()) {
|
|
|
|
if (dir.exists()) {
|
|
|
|
// save file
|
|
|
|
var fout = File(dir, "$userId.gpx")
|
|
|
|
if (!gpxFile.isEmpty) {
|
|
|
|
val pt = gpxFile.findPointToShow()
|
|
|
|
|
|
|
|
val user = app.telegramHelper.getUser(pt!!.userId)
|
|
|
|
val fileName: String
|
|
|
|
fileName = if (user != null) {
|
2019-02-01 16:13:21 +01:00
|
|
|
(TelegramUiHelper.getUserName(user) + "_" + SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(pt.time)))
|
2019-01-25 17:04:26 +01:00
|
|
|
} else {
|
2019-02-01 16:13:21 +01:00
|
|
|
userId.toString() + "_" + SimpleDateFormat("yyyy-MM-dd", Locale.US).format(Date(pt.time))
|
2019-01-25 17:04:26 +01:00
|
|
|
}
|
|
|
|
fout = File(dir, "$fileName.gpx")
|
|
|
|
}
|
|
|
|
val warn = GPXUtilities.writeGpxFile(fout, gpxFile, app)
|
|
|
|
if (warn != null) {
|
|
|
|
warnings.add(warn)
|
|
|
|
return warnings
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return warnings
|
|
|
|
}
|
|
|
|
|
|
|
|
override fun onPostExecute(warnings: List<String>?) {
|
|
|
|
if (listener != null) {
|
|
|
|
if (warnings != null && warnings.isEmpty()) {
|
|
|
|
listener.onSavingGpxFinish(gpxFile.path)
|
|
|
|
} else {
|
|
|
|
listener.onSavingGpxError(warnings)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
interface SaveGpxListener {
|
|
|
|
|
|
|
|
fun onSavingGpxFinish(path: String)
|
|
|
|
|
|
|
|
fun onSavingGpxError(warnings: List<String>?)
|
|
|
|
}
|
|
|
|
}
|