public function process_comment_headers($headers, $group_hash, $save = true) { /* * We iterate over the provided headers (generated by * $this->_get_headers() to a structure that is at the very * minimum looking like this: * * array ( * [0] => array ( * 'Number': <int> * 'Subject': <string> * 'From': <string> * 'Date': <string> * 'Message-ID': <string> * 'Bytes': <int> * 'Lines': <int> * 'Epoch': <int> * ), * ... * ) * From the structure above, we process our group hash and retrieve * all the binary data we need on valid content. * * A group_hash() record looks like this: * array( * array( * 'id': <int>, * 'key': <string>, * 'user': <string>, * 'email': <string>, * 'ref': <int>, * ), * array( * 'id': <int>, * 'key': <string>, * 'user': <string>, * 'email': <string>, * 'ref': <int>, * ), * ) */ if (!count($group_hash)) { // Nothing to process return []; } // // Prepare some general SQL Commands for saving later if all goes well // $db = new Settings(); $rc = new ReleaseComments(); // Comments $sql_new_cmt = "INSERT INTO release_comments (" . "id, sourceid, username, userid, gid, cid, isvisible, " . "releaseid, `text`, createddate, issynced, nzb_guid) VALUES (" . "NULL, %d, %s, 0, %s, %s, %d, 0, %s, %s, 1, %s)"; $sql_upd_cmt = "UPDATE release_comments SET " . "isvisible = %d, `text` = %s" . "WHERE sourceid = %d AND gid = %s AND cid = %s AND nzb_guid = %s"; $sql_fnd_cmt = "SELECT count(id) as cnt FROM release_comments " . "WHERE sourceid = %d AND gid = %s AND cid = %s"; // Sync Times $sql_sync = "UPDATE spotnabsources SET lastupdate = %s " . "WHERE id = %d"; $matches = Null; $processed = 0; $updates = 0; $inserts = 0; foreach ($headers as $header) { // Preform some general scanning the header to determine // if it could possibly be a valid post. if (!preg_match(SpotNab::FETCH_MSGID_REGEX, $header['Message-ID'], $matches)) { continue; } if ($matches['domain'] != SpotNab::SEGID_DOMAIN) { continue; } if ($matches['type'] != SpotNab::FETCH_COMMENT_TYPE) { continue; } // Now we check the subject line; it provides the first part of // the key to determining if we should handle the message or not if (!preg_match(SpotNab::FETCH_COMMENT_SUBJECT_REGEX, $header['Subject'], $matches)) { continue; } // We have a match; So populate potential variables $checksum = $matches['checksum']; $refdate = $matches['utcref']; $refdate_epoch = @strtotime($matches['utcref'] . " UTC"); if ($refdate_epoch === false || $refdate_epoch < 0) { // Bad time specified continue; } // PreKey is used to attempt to run the decode algorithm // a head of time.. if we can decrypt this we can probably // assume the body will decode too (and won't be a waste of // time to download it) foreach ($group_hash as $hash) { // Track how many records we handled $processed++; // First check the ref date... if it's newer then what we've // already processed, then we'll just keep on chugging along. if ($refdate_epoch <= $hash['ref']) { continue; } // Scan header information for supported matches if (!preg_match('/^(?P<user>[^<]+)<(?P<email>[^>]+)>$/', $header['From'], $matches)) { continue; } // Match against our sources posts if (trim($matches['user']) != $hash['user']) { continue; } if (trim($matches['email']) != $hash['email']) { continue; } // If we reach here, we've found a header we can process // The next step is to download the header's body // We'll do some final verifications on it such as detect // if the checksum is okay, and verify that the timestamp // within the body matches that of the header... then we // can start processing the guts of the body. if ($save) { // Download Body $body = $this->_get_body($header['Group'], $header['Message-ID']); if ($body === false) { continue; } //echo "DEBUG Close Match:\n"; //print_r($header); // Decode Body $body = $this->decodePost($body, $hash['key']); if ($body === false) { continue; } // Decode failed // Verify Body if (!is_array($body)) { continue; } // not any array if (!(bool) count(array_filter(array_keys($body), 'is_string'))) { continue; } // not an associative array if (!array_key_exists('server', $body) || !array_key_exists('postdate_utc', $body)) { continue; } // base structure missing // Compare postdate_utc and ensure it matches header // timestamp if (preg_replace('/[^0-9]/', '', $body['postdate_utc']) != $refdate) { continue; } // Comment Handling if (array_key_exists('comments', $body) && is_array($body['comments'])) { $rc = new ReleaseComments(); foreach ($body['comments'] as $comment) { // Verify Comment is parseable if (!is_array($comment)) { continue; } // not an array if (!count(array_filter(array_keys($comment)))) { continue; } // not an associative array // Store isvisible flag $is_visible = 1; if (array_key_exists('is_visible', $comment)) { $is_visible = intval($comment['is_visible']) > 0 ? 1 : 0; } // Check that comment doesn't already exist $res = $db->queryOneRow(sprintf($sql_fnd_cmt, $hash['id'], $db->escapeString($comment['gid']), $db->escapeString($comment['cid']))); // Store Results in DB if ($res && intval($res['cnt']) > 0) { // Make some noise echo '.'; $updates += $db->queryExec(sprintf($sql_upd_cmt, $is_visible, $db->escapeString($comment['comment']), $hash['id'], $db->escapeString($comment['gid']), $db->escapeString($comment['cid']), $db->escapeString($comment['gid']))) > 0 ? 1 : 0; } else { // Make some noise echo '+'; // Perform Insert $res = $db->queryInsert(sprintf($sql_new_cmt, $hash['id'], $db->escapeString($comment['username']), $db->escapeString($comment['gid']), $db->escapeString($comment['cid']), $is_visible, $db->escapeString($comment['comment']), $db->escapeString($this->utc2local($comment['postdate_utc'])), $db->escapeString($comment['gid']))); $inserts += 1; } $rc->updateReleaseCommentCount($comment['gid']); } } // Update spotnabsources table, set lastupdate to the // timestamp parsed from the header. $db->queryExec(sprintf($sql_sync, $db->escapeString($this->utc2local($body['postdate_utc'])), $hash['id'])); } else { // Debug non/save mode; mark update $updates += 1; } // always break if we made it this far... no mater how many // other groups are being processed, we've already matched // for this article, so we don't need to process it for // other sources. break; } } return [$inserts, $updates]; }