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