Skip to content

Instantly share code, notes, and snippets.

@OmeGak
Created September 27, 2020 18:37
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save OmeGak/f193c5459bccdadb2e339e093668dbf5 to your computer and use it in GitHub Desktop.
Save OmeGak/f193c5459bccdadb2e339e093668dbf5 to your computer and use it in GitHub Desktop.
Wire chat export

Wire doesn't support exporting chat history (wireapp/wire#116). It is, however, possible to access the decrypted messages in the local storage of a browser. This guide will help you produce a text export once you are logged in https://app.wire.com. All from the browser console.

Open a connection to your browser local storage:

var db
var request = indexedDB.open("wire@production@4278eafb-b21c-4d7a-aad4-2be4fa3a9fa0@permanent")
request.onerror = function(event) {
  console.log("Why didn't you allow my web app to use IndexedDB?!")
}
request.onsuccess = function(event) {
  db = event.target.result
}

Fetch all conversation:

var conversations = []
var objectStore = db.transaction("conversations").objectStore("conversations")
objectStore.getAll().onsuccess = function(event) {
    conversations = event.target.result
}

List their name alongside their ID:

conversations.forEach((value, index, array) => console.log(value.id, value.name))

Once you find the conversation, store its ID:

var conversation_id = "..."

Fetch all its messages:

var messages = []
var objectStore = db.transaction("events").objectStore("events")
var index = objectStore.index("conversation")
var singleKeyRange = IDBKeyRange.only(conversation_id)
index.openCursor(singleKeyRange).onsuccess = function(event) {
  var cursor = event.target.result
  if (cursor) {
    messages.push(cursor.value)
    cursor.continue()
  }
}

Find all user IDs in those messages:

var user_ids = new Set()
messages.forEach((x) => user_ids.add(x.from))

Usernames need to be queried to the server as they are not kept in local storage, so request first an access token:

var token = ''
fetch("https://prod-nginz-https.wire.com/access", {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "accept-language": "en-US,en;q=0.9",
  },
  "method": "POST",
  "credentials": "include"
}).then(response => response.json()).then(data => token = data.access_token);

And build a map of user IDs to usernames from a server request:

var usernames = {}
fetch(`https://prod-nginz-https.wire.com/users?ids=${Array.from(user_ids).join()}`, {
  "headers": {
    "accept": "application/json, text/plain, */*",
    "authorization": `Bearer ${token}`,
  },
  "method": "GET",
}).then(response => response.json()).then(data => {
  data.forEach(item => usernames[item.id] = item.name)
})

Finally, define helper functions render the messages:

var getUsernames = (user_ids) => user_ids.map(id => usernames[id])
var renderHeader = (message, idx, debug) => {
  var header = `${getUsernames([message.from])} ${message.time}`
  var debug_info = `[#${idx} ${message.type}]`
  return !debug ? `${header}:` : `${header} ${debug_info}:`
}
var exportHistory = (messages, debug) => {
  var history_export = ''
  messages.every((m, idx) => {
    if (m.type == 'conversation.group-creation') {
      users = getUsernames(m.data.userIds)
      return `
${renderHeader(m, idx, debug)}
-->> started the conversation **${m.data.name}** <<--
-->> with ${users.join(', ')}<<--
`
    } if (m.type == 'conversation.rename'
          && m.data.name !== undefined) {
      entry = `
${renderHeader(m, idx, debug)}
-->> renamed the conversation **${m.data.name}**<<--
`
    } else if (m.type == 'conversation.member-join') {
      users = getUsernames(m.data.user_ids)
      entry = `
${renderHeader(m, idx, debug)}
-->> added ${users.join(', ')} to the conversation <<--
`
    } else if (m.type == 'conversation.member-leave') {
      users = getUsernames(m.data.user_ids)
      entry = `
${renderHeader(m, idx, debug)}
-->> removed ${users.join(', ')} <<--
`
  } else if (m.type == 'conversation.message-timer-update') {
    entry = `
${renderHeader(m, idx, debug)}
-->> set the message timer to ${m.data.message_timer}ms <<--
`
  } else if (m.type == 'conversation.asset-add') {
    entry = `
${renderHeader(m, idx, debug)}
-->> uploaded ${m.data.content_type} <<--
`
  } else if (m.type == 'conversation.location') {
    entry = `
${renderHeader(m, idx, debug)}
-->> shared location <<--
-->> ${m.data.location.name} (${m.data.location.latitude}, ${m.data.location.latitude}) <<--
`
  } else if (m.type == 'conversation.voice-channel-deactivate') {
    entry = `
${renderHeader(m, idx, debug)}
-->> called <<--
`
  } else if (m.type == 'conversation.delete-everywhere') {
    entry = `
${renderHeader(m, idx, debug)}
-->> deleted message <<--
`
    } else if (m.type == 'conversation.knock') {
      entry = `
${renderHeader(m, idx, debug)}
-->> pinged <<--
`
    } else if (m.type == 'conversation.message-add') {
      entry = `
${renderHeader(m, idx, debug)}
${m.data.content}
`
    } else if (m.type == 'conversation.missed-messages'
               || m.type == 'conversation.voice-channel-activate') {
      return true
    } else {
      debugger
    }
    if (m.time.slice(0, 4) == '1970') {
      history_export = entry + history_export
    } else {
      history_export += entry
    }
    return true
  })
  return history_export.trim()
}

And just print the whole history to the console:

console.log(exportHistory(messages, debug=false))

NOTE: This was tested on Brave browser.


Todo:

  • Find message reply information
  • Display likes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment