index.html 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Hangouts.json Reader</title>
  5. <meta charset="UTF-8" />
  6. <!-- Styles -->
  7. <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css">
  8. <style type="text/css">
  9. .btn-file {
  10. position: relative;
  11. overflow: hidden;
  12. }
  13. .btn-file input[type=file] {
  14. position: absolute;
  15. top: 0;
  16. right: 0;
  17. min-width: 100%;
  18. min-height: 100%;
  19. font-size: 100px;
  20. text-align: right;
  21. filter: alpha(opacity=0);
  22. opacity: 0;
  23. outline: none;
  24. background: white;
  25. cursor: inherit;
  26. display: block;
  27. }
  28. .main-row{
  29. margin-top: 40px;
  30. }
  31. .thumb{
  32. width:100px;
  33. height: auto;
  34. }
  35. .txt{
  36. white-space: pre-wrap;
  37. border: 1px solid #ddd;
  38. border-radius: 4px;
  39. padding: 14px;
  40. background: #F5F5F5;
  41. }
  42. .emoji{
  43. width: 15px;
  44. }
  45. h3{
  46. margin-top: 0;
  47. }
  48. </style>
  49. </head>
  50. <body>
  51. <div class="container-fluid main">
  52. <h1>Hangouts.json Reader</h1>
  53. <form class="form-inline">
  54. Select your Hangouts.json file:
  55. <div class="input-group">
  56. <span class="input-group-btn">
  57. <span class="btn btn-primary btn-file">
  58. Browse&hellip; <input id="file-upload" type="file" multiple>
  59. </span>
  60. </span>
  61. <input type="text" class="form-control file-name" readonly>
  62. </div>
  63. </form>
  64. <div class="row main-row">
  65. <div class="col-md-3">
  66. <ul class="list-group convo-list">
  67. </ul>
  68. </div>
  69. <div class="col-md-9">
  70. <div class="txt"><h3>How to use:</h3>Visit <a href="https://www.google.com/settings/takeout" target="_blank">Google Takeout</a> then click "Select none" and only check the box next to Hangouts.
  71. Within a few minutes or so you will recieve a zip file with Hangouts.json inside it.
  72. Extract the file and choose it above. if you have a very large chat history it can take a few minutes to load the file.
  73. Don't worry, everything here is done clientside so there's no risk of your chat history being sent anywhere.
  74. You can view the source code <a href="https://github.com/Jessecar96/hangouts-reader" target="_blank">here</a> if you want to make sure. You can even just download this HTML file and run it in your browser locally.</div>
  75. </div>
  76. </div>
  77. </div>
  78. </body>
  79. <script type="text/javascript" src="https://code.jquery.com/jquery-1.11.0.min.js"></script>
  80. <script type="text/javascript" src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js"></script>
  81. <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/twemoji/1.2.1/twemoji.min.js"></script>
  82. <script type="text/javascript">
  83. var Hangouts = {}; // Main object for raw hangouts data
  84. var Conversations = {};
  85. var all_participants = {};
  86. $(document).on('change', '.btn-file :file', function() {
  87. var input = $(this),
  88. numFiles = input.get(0).files ? input.get(0).files.length : 1,
  89. label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
  90. input.trigger('fileselect', [numFiles, label]);
  91. });
  92. $('.btn-file :file').on('fileselect', function(event, numFiles, label) {
  93. $('.file-name').val(label);
  94. // Process file
  95. var file = document.getElementById("file-upload").files[0];
  96. if (file) {
  97. var reader = new FileReader();
  98. reader.readAsText(file, "UTF-8");
  99. reader.onload = function (evt) {
  100. Hangouts = JSON.parse(evt.target.result);
  101. console.log("Loaded: " + evt.target.result.length);
  102. processData();
  103. }
  104. reader.onerror = function (evt) {
  105. alert("Error reading file");
  106. }
  107. }
  108. });
  109. function processData() {
  110. // First we want to get all participants, so we loop fully once
  111. for(key in Hangouts['conversation_state']) {
  112. var conversation = Hangouts['conversation_state'][key]['conversation_state']['conversation'];
  113. // Get all participants
  114. for(person_key in conversation['participant_data']){
  115. var person = conversation['participant_data'][person_key];
  116. var gaia_id = person['id']['gaia_id'];
  117. if(!person['fallback_name'] || person['fallback_name'] == null) continue;
  118. if(!all_participants[gaia_id])
  119. all_participants[gaia_id] = person['fallback_name'];
  120. }
  121. }
  122. for(key in Hangouts['conversation_state']) {
  123. var conversation_state = Hangouts['conversation_state'][key];
  124. var id = conversation_state['conversation_id']['id'];
  125. var conversation = conversation_state['conversation_state']['conversation'];
  126. // Find participants
  127. var participants = [], participants_obj = {};
  128. for(person_key in conversation['participant_data']){
  129. var person = conversation['participant_data'][person_key];
  130. var gaia_id = person['id']['gaia_id'];
  131. var name = "Unknown";
  132. if(person['fallback_name']){
  133. name = person['fallback_name'];
  134. }else{
  135. name = all_participants[gaia_id];
  136. }
  137. participants.push(name);
  138. participants_obj[gaia_id] = name;
  139. }
  140. var participants_string = participants.join(", ");
  141. // Add to list
  142. $(".convo-list").append("<a href=\"javascript:void(0);\" onclick=\"switchConvo('"+id+"')\" class=\"list-group-item\">" + participants_string + "</a>");
  143. // Parse events
  144. var events = [];
  145. for(event_key in conversation_state['conversation_state']['event']){
  146. var convo_event = conversation_state['conversation_state']['event'][event_key];
  147. var timestamp = convo_event['timestamp'];
  148. var msgtime = formatTimestamp(timestamp);
  149. var sender = convo_event['sender_id']['gaia_id'];
  150. var message = "";
  151. if(convo_event['chat_message']){
  152. // Get message
  153. for(msg_key in convo_event['chat_message']['message_content']['segment']){
  154. var segment = convo_event['chat_message']['message_content']['segment'][msg_key];
  155. if(segment['type'] == 'LINE_BREAK') message += "\n";
  156. if(!segment['text']) continue;
  157. message += twemoji.parse(segment['text']);
  158. }
  159. // Check for images on event
  160. if(convo_event['chat_message']['message_content']['attachment']){
  161. for(var attach_key in convo_event['chat_message']['message_content']['attachment']){
  162. var attachment = convo_event['chat_message']['message_content']['attachment'][attach_key];
  163. console.log(attachment);
  164. if(attachment['embed_item']['type'][0] == "PLUS_PHOTO"){
  165. message += "\n<a target='blank' href='" + attachment['embed_item']['embeds.PlusPhoto.plus_photo']['url'] + "'><img class='thumb' src='" + attachment['embed_item']['embeds.PlusPhoto.plus_photo']['thumbnail']['image_url'] + "' /></a>";
  166. }
  167. }
  168. }
  169. events.push({msgtime: msgtime, sender: participants_obj[sender], message: message, timestamp: timestamp});
  170. }
  171. }
  172. // Sort events by timestamp
  173. events.sort(function(a, b){
  174. var keyA = a.timestamp,
  175. keyB = b.timestamp;
  176. if(keyA < keyB) return -1;
  177. if(keyA > keyB) return 1;
  178. return 0;
  179. });
  180. // Add events
  181. Conversations[id] = events;
  182. }
  183. }
  184. function switchConvo(id){
  185. $('.txt').text('');
  186. for(var event_id in Conversations[id]){
  187. var convo_event = Conversations[id][event_id];
  188. $('.txt').append(convo_event.msgtime + ": " + convo_event.sender + ": " + convo_event.message + "\n");
  189. }
  190. }
  191. function zeroPad(string) {
  192. return (string < 10) ? "0" + string : string;
  193. }
  194. function formatTimestamp(timestamp) {
  195. var d = new Date(timestamp/1000);
  196. var formattedDate = d.getFullYear() + "-" +
  197. zeroPad(d.getMonth() + 1) + "-" +
  198. zeroPad(d.getDate());
  199. var hours = zeroPad(d.getHours());
  200. var minutes = zeroPad(d.getMinutes());
  201. var formattedTime = hours + ":" + minutes;
  202. return formattedDate + " " + formattedTime;
  203. }
  204. </script>
  205. </html>