Skip to content

Commit

Permalink
+ Signal support (beta)
Browse files Browse the repository at this point in the history
  • Loading branch information
emi420 committed Jan 12, 2025
1 parent 4039d34 commit f14ae2c
Show file tree
Hide file tree
Showing 14 changed files with 135 additions and 12 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ChatMap

Export a chat from the app (WhatsApp or Telegram) and visualize the locations shared in the conversation.
Export a chat from the app (WhatsApp, Telegram or Signal) and visualize the locations shared in the conversation.

It was developed to help emergency services and humanitarian organizations to get
locations from people in the field during disasters and emergencies, but it can
Expand Down
2 changes: 1 addition & 1 deletion docs/README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Pruébalo aquí! [chatmap.hotosm.org](https://chatmap.hotosm.org)

## Cómo se usa?

1. Exporta un chat de WhatsApp o Telegram con localizaciones compartidas
1. Exporta un chat de WhatsApp, Telegram o Signal con localizaciones compartidas
2. Sube el archivo .zip a esta página
3. Extraerá todas las localizaciones y las mostrará en un mapa, junto con el mensaje que le sigue a cada ubicación
4. Puedes también descargar las localizaciones + mensajes como un archivo GeoJSON
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Try it here! [chatmap.hotosm.org](https://chatmap.hotosm.org)

## How to use it?

1. Export a WhatsApp or Telegram chat with shared locations
1. Export a WhatsApp, Telegram or Signal chat with shared locations
2. Upload the .zip file to this page
3. It will extract all the locations and display them on a map, together with the message that follows each location.
4. You can also download the locations + messages as a GeoJSON file from there
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"dependencies": {
"jszip": "^3.10.1",
"maplibre-gl": "^4.3.2",
"moment": "^2.30.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-drag-drop-files": "^2.3.10",
Expand Down
2 changes: 1 addition & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ function App() {
<p className="supportedApps">
<FormattedMessage
id = "app.supportedApps"
defaultMessage="Now it works with WhatsApp and Telegram!"
defaultMessage="Now it works with WhatsApp, Telegram or Signal!"
/>
</p>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/FileUpload/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const getFileFormat = (filename) => {
return "chat";
} else if (fileName.endsWith(".zip")) {
return "zip";
} else if (fileName.endsWith(".jpg") || fileName.endsWith(".mp4")) {
} else if (fileName.endsWith(".jpg") || fileName.endsWith(".jpeg") || fileName.endsWith(".mp4")) {
return "media";
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Map/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import './map.css';

const getMessage = (properties, dataFiles) => {
if (properties.file && dataFiles && properties.file in dataFiles) {
if (properties.file.endsWith("jpg")) {
if (properties.file.endsWith("jpg") || properties.file.endsWith("jpeg")) {
return <img className="popupImage" alt="Message attached file" src={URL.createObjectURL(dataFiles[properties.file])} />
} else if (properties.file.endsWith("mp4")) {
return <video controls autoplay loop className="popupImage" alt="Message attached file" src={URL.createObjectURL(dataFiles[properties.file])} />
Expand Down
2 changes: 1 addition & 1 deletion src/int/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"app.options": "Options",
"app.uploadLabel": "Upload or drag a file right here",
"app.nolocations": "No locations found in this file",
"app.supportedApps": "Now it works with WhatsApp and Telegram!"
"app.supportedApps": "Now it works with WhatsApp, Telegram or Signal!"
}

2 changes: 1 addition & 1 deletion src/int/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@
"app.uploadLabel": "Sube o arrastra un archivo aquí mismo",
"app.nolocations": "No se han encontrado ubicaciones en este archivo",
"app.loading": "Cargando",
"app.supportedApps": "Ahora funciona con WhatsApp y Telegram!"
"app.supportedApps": "Ahora funciona con WhatsApp, Telegram o Signal!"
}

2 changes: 1 addition & 1 deletion src/int/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
"app.uploadLabel": "Carregue ou arraste um ficheiro aqui mesmo",
"app.nolocations": "Não foram encontrados locais neste arquivo.",
"app.loading": "Carregando",
"app.supportedApps": "Agora funciona com WhatsApp e Telegram!"
"app.supportedApps": "Agora funciona com WhatsApp, Telegram e Signal!"
}
6 changes: 4 additions & 2 deletions src/parsers/detectApp.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import whatsAppParser from './whatsapp';
import telegramParser from './telegram';
import signalParser from './signal';

export default function getAppParser (files) {
for (let filename in files) {
const text = files[filename];
if (!text) return;
if (text[0] === "{") {
return telegramParser;
} else {
return whatsAppParser;
} else if (text.indexOf("group-v2-change") > -1) {
return signalParser;
}
return whatsAppParser;
}
}
115 changes: 115 additions & 0 deletions src/parsers/signal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { getClosestMessage, getClosestNextMessage, getClosestPrevMessage } from "./chatmap";
import moment from 'moment';

// Regex to search for coordinates in the format <lat>%2C<lon> (ex: -31.006037,-64.262794)
const LOCATION_PATTERN = /[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?)%2C\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/;

// Search for a location
const searchLocation = (line) => {
const match = line.match(LOCATION_PATTERN);
if (match) {
return match[0].split("%2C").map(x => parseFloat(x))
}
return null;
}

// Parse time, username and message
const parseMessage = (line, msg) => {
const location = searchLocation(line);
if (location) {
msg._location = [parseFloat(location[1]), parseFloat(location[0])]
msg.file = null;
} else if (line.indexOf("From: ") === 0) {
msg.username = line.replace("From: ", "");
} else if (line.indexOf("Sent: ") === 0) {
msg.timeString = line.replace("Sent: ", "");
msg.time = parseTimeString(msg.timeString);
} else if (line.indexOf("Attachment: ") === 0 && line.indexOf(".jpeg") > -1) {
msg.file = line.substring(12, line.indexOf(".jpeg") + 5);
} else if (line.indexOf("Attachment: ") === 0 && line.indexOf(".jpg") > -1) {
msg.file = line.substring(12, line.indexOf(".jpg") + 4);
} else if (line.indexOf("Attachment: ") === 0 && line.indexOf("jpeg") > -1 && line.indexOf("no filename") > -1 ) {
const formattedTime = moment.parseZone(msg.timeString).format('YYYY-MM-DD-HH-mm-ss');
msg.file = `attachment-${formattedTime}.jpg`;
} else if (!msg.file && !msg.message && line.indexOf("Type: ") === -1 &&
line.indexOf("Received: ") === -1 && line.indexOf("Conversation: ") === -1) {
msg.message = line;
}
}

// Parse time strings
const parseTimeString = (dateStr) => {
return new Date(dateStr);
}

// Parse messages from lines and create an index
const parseAndIndex = (lines) => {
const result = {};
let msg = {};
let started = false;
let lastUsername;
let msgIndex = 0;
for (const [index, line] of lines.entries()) {
parseMessage(line, msg);
let isFrom = line.indexOf("From: ") === 0;
if (started) {
if (isFrom) {
result[msgIndex] = msg;
lastUsername = msg.username;
msg = {};
msgIndex++;
// Last line
} else if (index === lines.length - 1 && msg) {
msg.username = lastUsername;
result[msgIndex] = msg;
}
}
if (isFrom && !started) {
started = true;
}
}
return result;
}


export default function signalParser({ text, msgPosition }) {
if (!text) return;
const lines = text.split("\n");
const geoJSON = {
type: "FeatureCollection",
features: []
};
let featureObject = {}

const messages = parseAndIndex(lines);
const msgObjects = Object.values(messages);

msgObjects.forEach((msgObject, index) => {
if (msgObject._location) {
featureObject = {
type: "Feature",
properties: {},
geometry: {
type: "Point",
coordinates: msgObject._location
}
}
let message;
switch (msgPosition) {
case "before":
message = getClosestPrevMessage(messages, index);
break;
case "after":
message = getClosestNextMessage(messages, index);
break;
default:
message = getClosestMessage(messages, index);
break;
}
featureObject.properties = {...message};
geoJSON.features.push(featureObject);
}
});

return geoJSON;
}
2 changes: 1 addition & 1 deletion src/parsers/telegram.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ export default function telegramParser({ text, msgPosition }) {
type: "FeatureCollection",
features: []
};
// Creates an indexed dictionary for messages

// Creates an indexed dictionary for messages
const messages = parseAndIndex(json.messages);
const msgObjects = Object.values(messages);

Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,11 @@ minimist@^1.2.6, minimist@^1.2.8:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==

moment@^2.30.1:
version "2.30.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae"
integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==

ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
Expand Down

0 comments on commit f14ae2c

Please sign in to comment.