by Jay2k1 • 1 year ago • PHP
  1. <?php
  2. /*
  3.  
  4. (in this version, I added support for more message types and offer both plaintext and HTML message format)
  5.  
  6. This is a function that transforms the JSON you get from Google Takeout when you export your Hangouts history
  7. into a PHP array which can be used to further manipulate the data.
  8.  
  9. A use case is my hangouts parser at http://hangoutparser.jay2k1.com/ -- a description can be seen at
  10. http://blog.jay2k1.com/2014/11/10/how-to-export-and-backup-your-google-hangouts-chat-history/
  11.  
  12. You feed the function with the JSON, and in return you get a nice array holding all the conversations.
  13.    
  14. As a parameter, it expects JSON text (you can get this for example by using file_get_contents('yourfile.json'))
  15. It returns an array in this format:
  16.  
  17. $array[0..N]                                array of conversations
  18. $array[0..N][name]                          conversation name. afaik only group chats can have one
  19. $array[0..N][type]                          conversation type. can be either STICKY_ONE_TO_ONE or GROUP
  20. $array[0..N][msg_count]                     message count for that conversation
  21. $array[0..N][members]                       array of conversation members where key = sender ID and value = sender name
  22. $array[0..N][messages]                      array of messages
  23. $array[0..N][messages][0..N]                array with message details
  24. $array[0..N][messages][0..N][timestamp]     timestamp of message in unixtime (actually, unixtime plus six more digits)
  25. $array[0..N][messages][0..N][datetime]      timestamp of message in YYYY-MM-DD HH:MM:SS format
  26. $array[0..N][messages][0..N][sender_id]     google's chat ID of the message's sender
  27. $array[0..N][messages][0..N][sender]        name of the message's sender (the "from")
  28. $array[0..N][messages][0..N][event_type]    type of message/event. can be RENAME_CONVERSATION, HANGOUT_EVENT, REGULAR_CHAT_MESSAGE, ADD_USER, REMOVE_USER, SMS, OTR_MODIFICATION, VOICEMAIL and maybe more...
  29. $array[0..N][messages][0..N][message]       the actual message text
  30. $array[0..N][messages][0..N][message_html]  HTML version of message text, if applicable (links are clickable, images are embedded etc)
  31.  
  32. So you could call it like this: $my_conversations = hangoutsToArray(file_get_contents('/tmp/hangouts.json'));
  33.  
  34. */
  35.  
  36.  
  37. function replaceSmileys($string) {
  38.     // replaces UTF-8 graphical emoticons by their ASCII equivalents
  39.     // list of emoji codes taken from https://aprescott.com/posts/hangouts-emoji
  40.     $patterns = array(
  41.         '/\x{1F41D}/u',         // -<@% ?       honeybee
  42.         '/\x{1F435}/u',         // :(|) ?       monkey face
  43.         '/\x{1F437}/u',         // :(:) ?       pig face
  44.         '/\x{1F473}/u',         // (]:{ ?       man with turban
  45.         '/\x{1F494}/u',         // <\3 </3      ?       broken heart
  46.         '/\x{1F49C}/u',         // <3   ?       purple heart
  47.         '/\x{1F4A9}/u',         // ~@~  ?       pile of poo
  48.         '/\x{1F600}/u',         // :D :-D       ?       grinning face
  49.         '/\x{1F601}/u',         // ^_^  ?       grinning face with smiling eyes
  50.         '/\x{1F602}/u',         // XD
  51.         '/\x{1F603}/u',         // :) :-) =)    ?       smiling face with open mouth
  52.         '/\x{1F604}/u',         // =D   ?       smiling face with open mouth and smiling eyes
  53.         '/\x{1F605}/u',         // ^_^;;        ?       smiling face with open mouth and cold sweat
  54.         '/\x{1F607}/u',         // O:) O:-) O=) ?       smiling face with halo
  55.         '/\x{1F608}/u',         // }:) }:-) }=) ?       smiling face with horns
  56.         '/\x{1F609}/u',         // ;) ;-)       ?       winking face
  57.         '/\x{1F60E}/u',         // B) B-)       ?       smiling face with sunglasses
  58.         '/\x{1F610}/u',         // :-| :| =|    ?       neutral face
  59.         '/\x{1F611}/u',         // -_-  ?       expressionless face
  60.         '/\x{1F613}/u',         // o_o; ?       face with cold sweat
  61.         '/\x{1F614}/u',         // u_u  ?       pensive face
  62.         '/\x{1F615}/u',         // :\ :/ :-\ :-/ =\ =/  ?       confused face
  63.         '/\x{1F616}/u',         // :S :-S :s :-s        ?       confounded face
  64.         '/\x{1F617}/u',         // :* :-*       ?       kissing face
  65.         '/\x{1F618}/u',         // ;* ;-*       ?       face throwing a kiss
  66.         '/\x{1F61B}/u',         // :P :-P =P :p :-p =p  ?       face with stuck-out tongue
  67.         '/\x{1F61C}/u',         // ;P ;-P ;p ;-p        ?       face with stuck-out tongue and winking eye
  68.         '/\x{1F61E}/u',         // :( :-( =(    ?       disappointed face
  69.         '/\x{1F621}/u',         // >.< >:( >:-( >=(     ?       pouting face
  70.         '/\x{1F622}/u',         // T_T :'( ;_; ='(      ?       crying face
  71.         '/\x{1F623}/u',         // >_<  ?       persevering face
  72.         '/\x{1F626}/u',         // D:   ?       frowning face with open mouth
  73.         '/\x{1F62E}/u',         // o.o :o :-o =o        ?       face with open mouth
  74.         '/\x{1F632}/u',         // O.O :O :-O =O        ?       astonished face
  75.         '/\x{1F634}/u',         // O.O :O :-O =O        ?       astonished face
  76.         '/\x{1F635}/u',         // x_x X-O X-o X( X-(   ?       dizzy face
  77.         '/\x{1F638}/u'          // :X) :3 (=^..^=) (=^.^=) =^_^=        ?       grinning cat face with smiling eyes
  78.     );
  79.     $replacements = array(
  80.         '-<@%',
  81.         ':(|)',
  82.         ':(:)',
  83.         '(]:{',
  84.         '</3',
  85.         '<3',
  86.         '~@~',
  87.         ':D',
  88.         '^_^',
  89.         'XD',
  90.         ':)',
  91.         '=D',
  92.         '^_^;;',
  93.         'O:)',
  94.         '}:)',
  95.         ';)',
  96.         'B-)',
  97.         ':|',
  98.         '-_-',
  99.         'o_o;',
  100.         'u_u',
  101.         ':/',
  102.         ':S',
  103.         ':*',
  104.         ';*',
  105.         ':P',
  106.         ';P',
  107.         ':(',
  108.         '>.<',
  109.         ":'(",
  110.         '>_<',
  111.         'D:',
  112.         ':o',
  113.         ':O',
  114.         '-_-Zzz',
  115.         'x_x',
  116.         ':3'
  117.     );
  118.  
  119.     return preg_replace($patterns, $replacements, $string);
  120. }
  121.  
  122. function hangoutsToArray($json) {
  123.     // set the desired timestamp format here
  124.     // the default is 'Y-m-d H:i:s' which is YYYY-MM-DD HH:mm:ss.
  125.     $timestamp_format = 'Y-m-d H:i:s';
  126.     ////////////////////////////////////////////////////////////
  127.     // decode JSON
  128.     $decoded = json_decode($json,true);
  129.     // extract useful part
  130.     $rawconvos = $decoded['conversation_state'];
  131.     $return = array();    
  132.     // loop through conversations
  133.     for ($i = 0; $i < sizeof($rawconvos); $i++) {
  134.         // first, get metadata
  135.         $convo = $rawconvos[$i];
  136.         $in_conv = $rawconvos[$i]['conversation_state']['conversation'];
  137.         $in_event = $rawconvos[$i]['conversation_state']['event'];
  138.         $pdata = $in_conv['participant_data'];
  139.         $return[$i]['type'] = $in_conv['type'];
  140.         $return[$i]['msgcount'] = sizeof($in_event);
  141.         $return[$i]['name'] = (isset($in_conv['name']) ? $in_conv['name'] : "");
  142.         // conversation participants
  143.         for ($j = 0; $j < sizeof($pdata); $j++) {
  144.             $id = $pdata[$j]['id']['chat_id'];
  145.             // use "unknown_<chat_id>" as name if they don't have a fallback_name
  146.             $name = (isset($pdata[$j]['fallback_name']) ? $pdata[$j]['fallback_name'] : 'unknown_'.$id);
  147.             $return[$i]['members'][$id] = $name;
  148.         }
  149.        
  150.         // loop through messages/events
  151.         $messages = array();
  152.         for ($k = 0; $k < sizeof($in_event); $k++) {
  153.             $messages[$k]['timestamp'] = $in_event[$k]['timestamp'];
  154.             $messages[$k]['datetime'] = date($timestamp_format,substr($messages[$k]['timestamp'], 0, 10));
  155.             $messages[$k]['sender_id'] = $in_event[$k]['sender_id']['chat_id'];
  156.             $messages[$k]['sender'] = (isset($return[$i]['members'][$messages[$k]['sender_id']]) ? $return[$i]['members'][$messages[$k]['sender_id']] : 'unknown_'.$id);
  157.             $messages[$k]['event_type'] = $in_event[$k]['event_type'];
  158.            
  159.             switch ($messages[$k]['event_type']) {
  160.                 case 'RENAME_CONVERSATION':
  161.                     $newname = $in_event[$k]['conversation_rename']['new_name'];
  162.                     $oldname = $in_event[$k]['conversation_rename']['old_name'];
  163.                     $messages[$k]['message'] = 'changed conversation name '.($oldname != '' ? 'from \''.$oldname.'\' ' : '').'to \''.$newname.'\'';
  164.                     break;
  165.                 case 'HANGOUT_EVENT':
  166.                     switch ($in_event[$k]['hangout_event']['event_type']) {
  167.                         case 'START_HANGOUT':
  168.                             $messages[$k]['message'] = 'started a video chat';
  169.                             break;
  170.                         case 'END_HANGOUT':
  171.                             $messages[$k]['message'] = 'ended a video chat';
  172.                             break;
  173.                         default:
  174.                             $messages[$k]['message'] = $in_event[$k]['hangout_event']['event_type'];
  175.                            
  176.                     }
  177.                     break;
  178.                 case 'REGULAR_CHAT_MESSAGE':
  179.                     $messages[$k]['message'] = "";
  180.                     $msg = "";
  181.                     $msghtml = "";
  182.                     // join message segments together
  183.                     if (isset($in_event[$k]['chat_message']['message_content']['segment'])) {
  184.                         foreach ($in_event[$k]['chat_message']['message_content']['segment'] as $num=>$event) {
  185.                             if (!isset($event['text'])) continue;
  186.                             if ($event['type'] == 'TEXT') {
  187.                                 $msg .= $event['text'];
  188.                                 $msghtml .= preg_replace('/\n/','<br>',$event['text']);
  189.                             } else if ($event['type'] == 'LINK') {
  190.                                 $msg .= $event['text'];
  191.                                 $msghtml .= '<a href="'.$event['link_data']['link_target'].'" target="_blank">'.$event['text'].'</a>';
  192.                             } else if ($event['type'] == 'LINE_BREAK') {
  193.                                 $msg .= $event['text'];
  194.                                 $msghtml .= preg_replace('/\n/','<br>',$event['text']);
  195.                             }
  196.                         }
  197.                     }
  198.                     // handle attachments
  199.                     else if (isset($in_event[$k]['chat_message']['message_content']['attachment'])) {
  200.                         // loop through attachments
  201.                         foreach ($in_event[$k]['chat_message']['message_content']['attachment'] as $att) {
  202.                             //echo "<pre>";print_r($att);echo "</pre>";
  203.                             if ($att['embed_item']['type'][0] == 'PLUS_PHOTO') {
  204.                                 $imgurl = $att['embed_item']['embeds.PlusPhoto.plus_photo']['url'];
  205.                                 $msg .= $imgurl;
  206.                                 $msghtml .= '<a href="'.$imgurl.'" target="_blank"><img src="'.$imgurl.'" alt="attached image" style="max-width:100%"></a>';
  207.                             }
  208.                         }
  209.                     }
  210.                     // replace unicode emoticon characters by smileys
  211.                     $messages[$k]['message'] = replaceSmileys($msg);
  212.                     if ($msg != $msghtml) { $messages[$k]['message_html'] = replaceSmileys($msghtml); }
  213.                     break;
  214.                 case 'ADD_USER':
  215.                     $newuserid = $in_event[$k]['membership_change']['participant_id'][0]['chat_id'];
  216.                     $newusername = (isset($return[$i]['members'][$newuserid]) ? $return[$i]['members'][$newuserid] : 'unknown_'.$newuserid);
  217.                     $messages[$k]['message'] = 'added user \''.$newusername.'\' to conversation';
  218.                     break;
  219.                 case 'REMOVE_USER':
  220.                     $newuserid = $in_event[$k]['membership_change']['participant_id'][0]['chat_id'];
  221.                     $newusername = (isset($return[$i]['members'][$newuserid]) ? $return[$i]['members'][$newuserid] : 'unknown_'.$newuserid);
  222.                     $messages[$k]['message'] = 'removed user \''.$newusername.'\' from conversation';
  223.                     break;
  224.                 case 'SMS':
  225.                     $messages[$k]['message'] = "";
  226.                     // join message segments together
  227.                     if (isset($in_event[$k]['chat_message']['message_content']['segment'])) {
  228.                         for ($l = 0; $l < sizeof($in_event[$k]['chat_message']['message_content']['segment']); $l++) {
  229.                             if (!isset($in_event[$k]['chat_message']['message_content']['segment'][$l]['text'])) continue;
  230.                             $messages[$k]['message'] .= $in_event[$k]['chat_message']['message_content']['segment'][$l]['text'];
  231.                         }
  232.                     }
  233.                     // replace unicode emoticon characters by smileys
  234.                     $messages[$k]['message'] = replaceSmileys($messages[$k]['message']);
  235.                     break;
  236.                 case 'OTR_MODIFICATION':
  237.                     $messages[$k]['message'] = 'unknown OTR_MODIFICATION';
  238.                     break;
  239.                 case 'VOICEMAIL':
  240.                     $messages[$k]['message'] = "new voicemail:\n";
  241.                     // join message segments together
  242.                     if (isset($in_event[$k]['chat_message']['message_content']['segment'])) {
  243.                         for ($l = 0; $l < sizeof($in_event[$k]['chat_message']['message_content']['segment']); $l++) {
  244.                             if (!isset($in_event[$k]['chat_message']['message_content']['segment'][$l]['text'])) continue;
  245.                             $messages[$k]['message'] .= $in_event[$k]['chat_message']['message_content']['segment'][$l]['text'];
  246.                         }
  247.                     }
  248.                     // replace unicode emoticon characters by smileys
  249.                     $messages[$k]['message'] = replaceSmileys($messages[$k]['message']);
  250.                     break;
  251.             }
  252.         }
  253.         // sort messages by timestamp because for some reason they're cluttered
  254.         usort($messages, function($a, $b) { return $a['timestamp'] - $b['timestamp']; });
  255.         // add the messages array to the conversation array
  256.         $return[$i]['messages'] = $messages;
  257.     }
  258.     return $return;
  259. }
  260. ?>

Replies to Hangout Parser function rss

Title Name Language When
Re: Hangout Parser function Small Meerkat php 3 weeks ago.