getLine() . ': ' . $e->getMessage()); die(); }); $rawPost = NULL; if (!$hookSecret || $hookSecret == '08ajh0qj93209qj90jfq932j32r') throw new \Exception("Hook secret is required and can not be default"); if (!isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) { throw new \Exception("HTTP header 'X-Hub-Signature' is missing."); } elseif (!extension_loaded('hash')) { throw new \Exception("Missing 'hash' extension to check the secret code validity."); } list($algo, $hash) = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'], 2) + array('', ''); if (!in_array($algo, hash_algos(), TRUE)) { throw new \Exception("Hash algorithm '$algo' is not supported."); } $rawPost = file_get_contents('php://input'); if ($hash !== hash_hmac($algo, $rawPost, $hookSecret)) { throw new \Exception('Hook secret does not match.'); } $contenttype = null; //apache and nginx/fastcgi/phpfpm call this two different things. if (!isset($_SERVER['HTTP_CONTENT_TYPE'])) { if (!isset($_SERVER['CONTENT_TYPE'])) throw new \Exception("Missing HTTP 'Content-Type' header."); else $contenttype = $_SERVER['CONTENT_TYPE']; } else { $contenttype = $_SERVER['HTTP_CONTENT_TYPE']; } if (!isset($_SERVER['HTTP_X_GITHUB_EVENT'])) { throw new \Exception("Missing HTTP 'X-Github-Event' header."); } switch ($contenttype) { case 'application/json': $json = $rawPost ?: file_get_contents('php://input'); break; case 'application/x-www-form-urlencoded': $json = $_POST['payload']; break; default: throw new \Exception("Unsupported content type: $contenttype"); } # Payload structure depends on triggered event # https://developer.github.com/v3/activity/events/types/ $payload = json_decode($json, true); switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) { case 'ping': echo 'pong'; break; case 'pull_request': handle_pr($payload); break; case 'pull_request_review': if($payload['action'] == 'submitted'){ $lower_state = strtolower($payload['review']['state']); if(($lower_state == 'approved' || $lower_state == 'changes_requested') && is_maintainer($payload, $payload['review']['user']['login'])) remove_ready_for_review($payload); } break; default: header('HTTP/1.0 404 Not Found'); echo "Event:$_SERVER[HTTP_X_GITHUB_EVENT] Payload:\n"; print_r($payload); # For debug only. Can be found in GitHub hook log. die(); } function apisend($url, $method = 'GET', $content = null, $authorization = null) { if (is_array($content)) $content = json_encode($content); $headers = array(); $headers[] = 'Content-type: application/json'; if ($authorization) $headers[] = 'Authorization: ' . $authorization; $scontext = array('http' => array( 'method' => $method, 'header' => implode("\r\n", $headers), 'ignore_errors' => true, 'user_agent' => 'tgstation13.org-Github-Automation-Tools' )); if ($content) $scontext['http']['content'] = $content; return file_get_contents($url, false, stream_context_create($scontext)); } function github_apisend($url, $method = 'GET', $content = NULL) { global $apiKey; return apisend($url, $method, $content, 'token ' . $apiKey); } function discord_webhook_send($webhook, $content) { return apisend($webhook, 'POST', $content); } function validate_user($payload) { global $validation, $validation_count; $query = array(); if (empty($validation)) $validation = 'org'; switch (strtolower($validation)) { case 'disable': return TRUE; case 'repo': $query['repo'] = $payload['pull_request']['base']['repo']['full_name']; break; default: $query['user'] = $payload['pull_request']['base']['repo']['owner']['login']; break; } $query['author'] = $payload['pull_request']['user']['login']; $query['is'] = 'merged'; $querystring = ''; foreach($query as $key => $value) $querystring .= ($querystring == '' ? '' : '+') . urlencode($key) . ':' . urlencode($value); $res = github_apisend('https://api.github.com/search/issues?q='.$querystring); $res = json_decode($res, TRUE); return $res['total_count'] >= (int)$validation_count; } function get_labels($payload){ $url = $payload['pull_request']['issue_url'] . '/labels'; $existing_labels = json_decode(github_apisend($url), true); $existing = array(); foreach((array) $existing_labels as $label) $existing[] = $label['name']; return $existing; } function check_tag_and_replace($payload, $title_tag, $label, &$array_to_add_label_to){ $title = $payload['pull_request']['title']; if(stripos($title, $title_tag) !== FALSE){ $array_to_add_label_to[] = $label; return true; } return false; } function set_labels($payload, $labels, $remove) { $existing = get_labels($payload); $tags = array(); $tags = array_merge($labels, $existing); $tags = array_unique($tags); if($remove) { $tags = array_diff($tags, $remove); } $final = array(); foreach($tags as $t) $final[] = $t; $url = $payload['pull_request']['issue_url'] . '/labels'; echo github_apisend($url, 'PUT', $final); } //rip bs-12 function tag_pr($payload, $opened) { //get the mergeable state $url = $payload['pull_request']['url']; $payload['pull_request'] = json_decode(github_apisend($url), TRUE); if($payload['pull_request']['mergeable'] == null) { //STILL not ready. Give it a bit, then try one more time sleep(10); $payload['pull_request'] = json_decode(github_apisend($url), TRUE); } $tags = array(); $title = $payload['pull_request']['title']; if($opened) { //you only have one shot on these ones so as to not annoy maintainers $tags = checkchangelog($payload, false); if(strpos(strtolower($title), 'refactor') !== FALSE) $tags[] = 'Refactor'; if(strpos(strtolower($title), 'revert') !== FALSE) $tags[] = 'Revert'; if(strpos(strtolower($title), 'removes') !== FALSE) $tags[] = 'Removal'; } $remove = array('Test Merge Candidate'); $mergeable = $payload['pull_request']['mergeable']; if($mergeable === TRUE) //only look for the false value $remove[] = 'Merge Conflict'; else if ($mergeable === FALSE) $tags[] = 'Merge Conflict'; $treetags = array('_maps' => 'Map Edit', 'tools' => 'Tools', 'SQL' => 'SQL', '.github' => 'GitHub'); $addonlytags = array('icons' => 'Sprites', 'sound' => 'Sound', 'config' => 'Config Update', 'code/controllers/configuration/entries' => 'Config Update', 'tgui' => 'UI'); foreach($treetags as $tree => $tag) if(has_tree_been_edited($payload, $tree)) $tags[] = $tag; else $remove[] = $tag; foreach($addonlytags as $tree => $tag) if(has_tree_been_edited($payload, $tree)) $tags[] = $tag; check_tag_and_replace($payload, '[dnm]', 'Do Not Merge', $tags); if(!check_tag_and_replace($payload, '[wip]', 'Work In Progress', $tags) && check_tag_and_replace($payload, '[ready]', 'Work In Progress', $remove)) $tags[] = 'Needs Review'; return array($tags, $remove); } function remove_ready_for_review($payload, $labels = null){ if($labels == null) $labels = get_labels($payload); $index = array_search('Needs Review', $labels); if($index !== FALSE) unset($labels[$index]); $url = $payload['pull_request']['issue_url'] . '/labels'; github_apisend($url, 'PUT', $labels); } function dismiss_review($payload, $id, $reason){ $content = array('message' => $reason); github_apisend($payload['pull_request']['url'] . '/reviews/' . $id . '/dismissals', 'PUT', $content); } function get_reviews($payload){ return json_decode(github_apisend($payload['pull_request']['url'] . '/reviews'), true); } function check_ready_for_review($payload, $labels = null, $remove = array()){ $r4rlabel = 'Needs Review'; $labels_which_should_not_be_ready = array('Do Not Merge', 'Work In Progress', 'Merge Conflict'); $has_label_already = false; $should_not_have_label = false; if($labels == null) $labels = get_labels($payload); $returned = array($labels, $remove); //if the label is already there we may need to remove it foreach($labels as $L){ if(in_array($L, $labels_which_should_not_be_ready)) $should_not_have_label = true; if($L == $r4rlabel) $has_label_already = true; } if($has_label_already && $should_not_have_label){ $remove[] = $r4rlabel; return $returned; } //find all reviews to see if changes were requested at some point $reviews = get_reviews($payload); $reviews_ids_with_changes_requested = array(); $dismissed_an_approved_review = false; foreach($reviews as $R) if(is_maintainer($payload, $R['user']['login'])){ $lower_state = strtolower($R['state']); if($lower_state == 'changes_requested') $reviews_ids_with_changes_requested[] = $R['id']; else if ($lower_state == 'approved'){ dismiss_review($payload, $R['id'], 'Out of date review'); $dismissed_an_approved_review = true; } } if(!$dismissed_an_approved_review && count($reviews_ids_with_changes_requested) == 0){ if($has_label_already) $remove[] = $r4rlabel; return $returned; //no need to be here } if(count($reviews_ids_with_changes_requested) > 0){ //now get the review comments for the offending reviews $review_comments = json_decode(github_apisend($payload['pull_request']['review_comments_url']), true); foreach($review_comments as $C){ //make sure they are part of an offending review if(!in_array($C['pull_request_review_id'], $reviews_ids_with_changes_requested)) continue; //review comments which are outdated have a null position if($C['position'] !== null){ if($has_label_already) $remove[] = $r4rlabel; return $returned; //no need to tag } } } //finally, add it if necessary if(!$has_label_already){ $labels[] = $r4rlabel; } return $returned; } function check_dismiss_changelog_review($payload){ global $require_changelog; global $no_changelog; if(!$require_changelog) return; if(!$no_changelog) checkchangelog($payload, false); $review_message = 'Your changelog for this PR is either malformed or non-existent. Please create one to document your changes.'; $reviews = get_reviews($payload); if($no_changelog){ //check and see if we've already have this review foreach($reviews as $R) if($R['body'] == $review_message && strtolower($R['state']) == 'changes_requested') return; //otherwise make it ourself github_apisend($payload['pull_request']['url'] . '/reviews', 'POST', array('body' => $review_message, 'event' => 'REQUEST_CHANGES')); } else //kill previous reviews foreach($reviews as $R) if($R['body'] == $review_message && strtolower($R['state']) == 'changes_requested') dismiss_review($payload, $R['id'], 'Changelog added/fixed.'); } function handle_pr($payload) { global $no_changelog; $action = 'opened'; $validated = validate_user($payload); switch ($payload["action"]) { case 'opened': list($labels, $remove) = tag_pr($payload, true); set_labels($payload, $labels, $remove); if($no_changelog) check_dismiss_changelog_review($payload); if(get_pr_code_friendliness($payload) <= 0){ $balances = pr_balances(); $author = $payload['pull_request']['user']['login']; if(isset($balances[$author]) && $balances[$author] < 0 && !is_maintainer($payload, $author)) create_comment($payload, 'You currently have a negative Fix/Feature pull request delta of ' . $balances[$author] . '. Maintainers may close this PR at will. Fixing issues or improving the codebase will improve this score.'); } break; case 'edited': check_dismiss_changelog_review($payload); case 'synchronize': list($labels, $remove) = tag_pr($payload, false); if($payload['action'] == 'synchronize') list($labels, $remove) = check_ready_for_review($payload, $labels, $remove); set_labels($payload, $labels, $remove); return; case 'reopened': $action = $payload['action']; break; case 'closed': if (!$payload['pull_request']['merged']) { $action = 'closed'; } else { $action = 'merged'; auto_update($payload); checkchangelog($payload, true); update_pr_balance($payload); $validated = TRUE; //pr merged events always get announced. } break; default: return; } $pr_flags = 0; if (strpos(strtolower($payload['pull_request']['title']), '[s]') !== false) { $pr_flags |= F_SECRET_PR; } if (!$validated) { $pr_flags |= F_UNVALIDATED_USER; } discord_announce($action, $payload, $pr_flags); game_announce($action, $payload, $pr_flags); } function filter_announce_targets($targets, $owner, $repo, $action, $pr_flags) { foreach ($targets as $i=>$target) { if (isset($target['exclude_events']) && in_array($action, array_map('strtolower', $target['exclude_events']))) { unset($targets[$i]); continue; } if (isset($target['announce_secret']) && $target['announce_secret']) { if (!($pr_flags & F_SECRET_PR) && $target['announce_secret'] === 'only') { unset($targets[$i]); continue; } } else if ($pr_flags & F_SECRET_PR) { unset($targets[$i]); continue; } if (isset($target['announce_unvalidated']) && $target['announce_unvalidated']) { if (!($pr_flags & F_UNVALIDATED_USER) && $target['announce_unvalidated'] === 'only') { unset($targets[$i]); continue; } } else if ($pr_flags & F_UNVALIDATED_USER) { unset($targets[$i]); continue; } $wildcard = false; if (isset($target['include_repos'])) { foreach ($target['include_repos'] as $match_string) { $owner_repo_pair = explode('/', strtolower($match_string)); if (count($owner_repo_pair) != 2) { log_error('Bad include repo: `'. $match_string.'`'); continue; } if (strtolower($owner) == $owner_repo_pair[0]) { if (strtolower($repo) == $owner_repo_pair[1]) continue 2; //don't parse excludes when we have an exact include match if ($owner_repo_pair[1] == '*') { $wildcard = true; continue; //do parse excludes when we have a wildcard match (but check the other entries for exact matches first) } } } if (!$wildcard) { unset($targets[$i]); continue; } } if (isset($target['exclude_repos'])) foreach ($target['exclude_repos'] as $match_string) { $owner_repo_pair = explode('/', strtolower($match_string)); if (count($owner_repo_pair) != 2) { log_error('Bad exclude repo: `'. $match_string.'`'); continue; } if (strtolower($owner) == $owner_repo_pair[0]) { if (strtolower($repo) == $owner_repo_pair[1]) { unset($targets[$i]); continue 2; } if ($owner_repo_pair[1] == '*') { if ($wildcard) log_error('Identical wildcard include and exclude: `'.$match_string.'`. Excluding.'); unset($targets[$i]); continue 2; } } } } return $targets; } function game_announce($action, $payload, $pr_flags) { global $servers; $msg = '['.$payload['pull_request']['base']['repo']['full_name'].'] Pull Request '.$action.' by '.htmlSpecialChars($payload['sender']['login']).': '.htmlSpecialChars('#'.$payload['pull_request']['number'].' '.$payload['pull_request']['user']['login'].' - '.$payload['pull_request']['title']).''; $game_servers = filter_announce_targets($servers, $payload['pull_request']['base']['repo']['owner']['login'], $payload['pull_request']['base']['repo']['name'], $action, $pr_flags); $msg = '?announce='.urlencode($msg).'&payload='.urlencode(json_encode($payload)); foreach ($game_servers as $serverid => $server) { $server_message = $msg; if (isset($server['comskey'])) $server_message .= '&key='.urlencode($server['comskey']); game_server_send($server['address'], $server['port'], $server_message); } } function discord_announce($action, $payload, $pr_flags) { global $discordWebHooks; $color; switch ($action) { case 'reopened': case 'opened': $color = 0x2cbe4e; break; case 'closed': $color = 0xcb2431; break; case 'merged': $color = 0x6f42c1; break; default: return; } $data = array( 'username' => 'GitHub', 'avatar_url' => $payload['pull_request']['base']['user']['avatar_url'], ); $content = 'Pull Request #'.$payload['pull_request']['number'].' *'.$action.'* by '.discord_sanitize($payload['sender']['login'])."\n".discord_sanitize($payload['pull_request']['user']['login']).' - __**'.discord_sanitize($payload['pull_request']['title']).'**__'."\n".'<'.$payload['pull_request']['html_url'].'>'; $embeds = array( array( 'title' => '__**'.discord_sanitize($payload['pull_request']['title'], S_MARKDOWN).'**__', 'description' => discord_sanitize(str_replace(array("\r\n", "\n"), array(' ', ' '), substr($payload['pull_request']['body'], 0, 320)), S_HTML_COMMENTS), 'url' => $payload['pull_request']['html_url'], 'color' => $color, 'author' => array( 'name' => discord_sanitize($payload['pull_request']['user']['login'], S_MARKDOWN), 'url' => $payload['pull_request']['user']['html_url'], 'icon_url' => $payload['pull_request']['user']['avatar_url'] ), 'footer' => array( 'text' => '#'.$payload['pull_request']['number'].' '.discord_sanitize($payload['pull_request']['base']['repo']['full_name'], S_MARKDOWN).' '.discord_sanitize($payload['pull_request']['head']['ref'], S_MARKDOWN).' -> '.discord_sanitize($payload['pull_request']['base']['ref'], S_MARKDOWN), 'icon_url' => $payload['pull_request']['base']['user']['avatar_url'] ) ) ); $discordWebHook_targets = filter_announce_targets($discordWebHooks, $payload['pull_request']['base']['repo']['owner']['login'], $payload['pull_request']['base']['repo']['name'], $action, $pr_flags); foreach ($discordWebHook_targets as $discordWebHook) { $sending_data = $data; if (isset($discordWebHook['embed']) && $discordWebHook['embed']) { $sending_data['embeds'] = $embeds; if (!isset($discordWebHook['no_text']) || !$discordWebHook['no_text']) $sending_data['content'] = $content; } else { $sending_data['content'] = $content; } discord_webhook_send($discordWebHook['url'], $sending_data); } } function discord_sanitize($text, $flags = S_MENTIONS|S_LINK_EMBED|S_MARKDOWN) { if ($flags & S_MARKDOWN) $text = str_ireplace(array('\\', '*', '_', '~', '`', '|'), (array('\\\\', '\\*', '\\_', '\\~', '\\`', '\\|')), $text); if ($flags & S_HTML_COMMENTS) $text = preg_replace('//Uis', '', $text); if ($flags & S_MENTIONS) $text = str_ireplace(array('@everyone', '@here', '<@'), array('`@everyone`', '`@here`', '@<'), $text); if ($flags & S_LINK_EMBED) $text = preg_replace("/((https?|ftp|byond)\:\/\/)([a-z0-9-.]*)\.([a-z]{2,3})(\:[0-9]{2,5})?(\/(?:[a-z0-9+\$_-]\.?)+)*\/?(\?[a-z+&\$_.-][a-z0-9;:@&%=+\/\$_.-]*)?(#[a-z_.-][a-z0-9+\$_.-]*)?/mi", '<$0>', $text); return $text; } //creates a comment on the payload issue function create_comment($payload, $comment){ github_apisend($payload['pull_request']['comments_url'], 'POST', json_encode(array('body' => $comment))); } //returns the payload issue's labels as a flat array function get_pr_labels_array($payload){ $url = $payload['pull_request']['issue_url'] . '/labels'; $issue = json_decode(github_apisend($url), true); $result = array(); foreach($issue as $l) $result[] = $l['name']; return $result; } //helper for getting the path the the balance json file function pr_balance_json_path(){ global $prBalanceJson; return $prBalanceJson != '' ? $prBalanceJson : 'pr_balances.json'; } //return the assoc array of login -> balance for prs function pr_balances(){ $path = pr_balance_json_path(); if(file_exists($path)) return json_decode(file_get_contents($path), true); else return array(); } //returns the difference in PR balance a pull request would cause function get_pr_code_friendliness($payload, $oldbalance = null){ global $startingPRBalance; if($oldbalance == null) $oldbalance = $startingPRBalance; $labels = get_pr_labels_array($payload); //anything not in this list defaults to 0 $label_values = array( 'Fix' => 3, 'Refactor' => 10, 'Code Improvement' => 2, 'Grammar and Formatting' => 1, 'Priority: High' => 15, 'Priority: CRITICAL' => 20, 'Unit Tests' => 6, 'Logging' => 1, 'Feedback' => 2, 'Performance' => 12, 'Feature' => -10, 'Balance/Rebalance' => -8, 'Tweak' => -2, 'Sound' => 1, 'Sprites' => 1, 'GBP: Reset' => $startingPRBalance - $oldbalance, ); $maxNegative = 0; $maxPositive = 0; foreach($labels as $l){ if($l == 'GBP: No Update') { //no effect on balance return 0; } else if(isset($label_values[$l])) { $friendliness = $label_values[$l]; if($friendliness > 0) $maxPositive = max($friendliness, $maxPositive); else $maxNegative = min($friendliness, $maxNegative); } } $affecting = abs($maxNegative) >= $maxPositive ? $maxNegative : $maxPositive; return $affecting; } function is_maintainer($payload, $author){ global $maintainer_team_id; $repo_is_org = $payload['pull_request']['base']['repo']['owner']['type'] == 'Organization'; if($maintainer_team_id == null || !$repo_is_org) { $collaburl = str_replace('{/collaborator}', '/' . $author, $payload['pull_request']['base']['repo']['collaborators_url']) . '/permission'; $perms = json_decode(github_apisend($collaburl), true); $permlevel = $perms['permission']; return $permlevel == 'admin' || $permlevel == 'write'; } else { $check_url = 'https://api.github.com/teams/' . $maintainer_team_id . '/memberships/' . $author; $result = json_decode(github_apisend($check_url), true); return isset($result['state']) && $result['state'] == 'active'; } } //payload is a merged pull request, updates the pr balances file with the correct positive or negative balance based on comments function update_pr_balance($payload) { global $startingPRBalance; global $trackPRBalance; if(!$trackPRBalance) return; $author = $payload['pull_request']['user']['login']; $balances = pr_balances(); if(!isset($balances[$author])) $balances[$author] = $startingPRBalance; $friendliness = get_pr_code_friendliness($payload, $balances[$author]); $balances[$author] += $friendliness; if(!is_maintainer($payload, $author)){ //immune if($balances[$author] < 0 && $friendliness < 0) create_comment($payload, 'Your Fix/Feature pull request delta is currently below zero (' . $balances[$author] . '). Maintainers may close future Feature/Tweak/Balance PRs. Fixing issues or helping to improve the codebase will raise this score.'); else if($balances[$author] >= 0 && ($balances[$author] - $friendliness) < 0) create_comment($payload, 'Your Fix/Feature pull request delta is now above zero (' . $balances[$author] . '). Feel free to make Feature/Tweak/Balance PRs.'); } $balances_file = fopen(pr_balance_json_path(), 'w'); fwrite($balances_file, json_encode($balances)); fclose($balances_file); } $github_diff = null; function get_diff($payload) { global $github_diff; if ($github_diff === null && $payload['pull_request']['diff_url']) { //go to the diff url $url = $payload['pull_request']['diff_url']; $github_diff = file_get_contents($url); } return $github_diff; } function auto_update($payload){ global $enable_live_tracking; global $path_to_script; global $repoOwnerAndName; global $tracked_branch; global $github_diff; if(!$enable_live_tracking || !has_tree_been_edited($payload, $path_to_script) || $payload['pull_request']['base']['ref'] != $tracked_branch) return; get_diff($payload); $content = file_get_contents('https://raw.githubusercontent.com/' . $repoOwnerAndName . '/' . $tracked_branch . '/'. $path_to_script); $content_diff = "### Diff not available. :slightly_frowning_face:"; if($github_diff && preg_match('/(diff --git a\/' . preg_quote($path_to_script, '/') . '.+?)(?:\Rdiff|$)/s', $github_diff, $matches)) { $script_diff = $matches[1]; if($script_diff) { $content_diff = "``" . "`DIFF\n" . $script_diff ."\n``" . "`"; } } create_comment($payload, "Edit detected. Self updating... \n
Here are my changes:\n\n" . $content_diff . "\n
\n
Here is my new code:\n\n``" . "`HTML+PHP\n" . $content . "\n``" . '`\n
'); $code_file = fopen(basename($path_to_script), 'w'); fwrite($code_file, $content); fclose($code_file); } function has_tree_been_edited($payload, $tree){ global $github_diff; get_diff($payload); //find things in the _maps/map_files tree //e.g. diff --git a/_maps/map_files/Cerestation/cerestation.dmm b/_maps/map_files/Cerestation/cerestation.dmm return ($github_diff !== FALSE) && (preg_match('/^diff --git a\/' . preg_quote($tree, '/') . '/m', $github_diff) !== 0); } $no_changelog = false; function checkchangelog($payload, $compile = true) { global $no_changelog; if (!isset($payload['pull_request']) || !isset($payload['pull_request']['body'])) { return; } if (!isset($payload['pull_request']['user']) || !isset($payload['pull_request']['user']['login'])) { return; } $body = $payload['pull_request']['body']; $tags = array(); if(preg_match('/(?i)(fix|fixes|fixed|resolve|resolves|resolved)\s*#[0-9]+/',$body)) //github autoclose syntax $tags[] = 'Fix'; $body = str_replace("\r\n", "\n", $body); $body = explode("\n", $body); $username = $payload['pull_request']['user']['login']; $incltag = false; $changelogbody = array(); $currentchangelogblock = array(); $foundcltag = false; foreach ($body as $line) { $line = trim($line); if (substr($line,0,4) == ':cl:' || substr($line,0,1) == '??') { $incltag = true; $foundcltag = true; $pos = strpos($line, " "); if ($pos) { $tmp = substr($line, $pos+1); if (trim($tmp) != 'optional name here') $username = $tmp; } continue; } else if (substr($line,0,5) == '/:cl:' || substr($line,0,6) == '/ :cl:' || substr($line,0,5) == ':/cl:' || substr($line,0,5) == '/??' || substr($line,0,6) == '/ ??' ) { $incltag = false; $changelogbody = array_merge($changelogbody, $currentchangelogblock); continue; } if (!$incltag) continue; $firstword = explode(' ', $line)[0]; $pos = strpos($line, " "); $item = ''; if ($pos) { $firstword = trim(substr($line, 0, $pos)); $item = trim(substr($line, $pos+1)); } else { $firstword = $line; } if (!strlen($firstword)) { $currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n"; continue; } //not a prefix line. //so we add it to the last changelog entry as a separate line if (!strlen($firstword) || $firstword[strlen($firstword)-1] != ':') { if (count($currentchangelogblock) <= 0) continue; $currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n".$line; continue; } $cltype = strtolower(substr($firstword, 0, -1)); switch ($cltype) { case 'fix': case 'fixes': case 'bugfix': if($item != 'fixed a few things') { $tags[] = 'Fix'; $currentchangelogblock[] = array('type' => 'bugfix', 'body' => $item); } break; case 'rsctweak': case 'tweaks': case 'tweak': if($item != 'tweaked a few things') { $tags[] = 'Tweak'; $currentchangelogblock[] = array('type' => 'tweak', 'body' => $item); } break; case 'soundadd': if($item != 'added a new sound thingy') { $tags[] = 'Sound'; $currentchangelogblock[] = array('type' => 'soundadd', 'body' => $item); } break; case 'sounddel': if($item != 'removed an old sound thingy') { $tags[] = 'Sound'; $tags[] = 'Removal'; $currentchangelogblock[] = array('type' => 'sounddel', 'body' => $item); } break; case 'add': case 'adds': case 'rscadd': if($item != 'Added new things' && $item != 'Added more things') { $tags[] = 'Feature'; $currentchangelogblock[] = array('type' => 'rscadd', 'body' => $item); } break; case 'del': case 'dels': case 'rscdel': if($item != 'Removed old things') { $tags[] = 'Removal'; $currentchangelogblock[] = array('type' => 'rscdel', 'body' => $item); } break; case 'imageadd': if($item != 'added some icons and images') { $tags[] = 'Sprites'; $currentchangelogblock[] = array('type' => 'imageadd', 'body' => $item); } break; case 'imagedel': if($item != 'deleted some icons and images') { $tags[] = 'Sprites'; $tags[] = 'Removal'; $currentchangelogblock[] = array('type' => 'imagedel', 'body' => $item); } break; case 'typo': case 'spellcheck': if($item != 'fixed a few typos') { $tags[] = 'Grammar and Formatting'; $currentchangelogblock[] = array('type' => 'spellcheck', 'body' => $item); } break; case 'balance': case 'rebalance': if($item != 'rebalanced something'){ $tags[] = 'Balance/Rebalance'; $currentchangelogblock[] = array('type' => 'balance', 'body' => $item); } break; case 'tgs': $currentchangelogblock[] = array('type' => 'tgs', 'body' => $item); break; case 'code_imp': case 'code': if($item != 'changed some code'){ $tags[] = 'Code Improvement'; $currentchangelogblock[] = array('type' => 'code_imp', 'body' => $item); } break; case 'refactor': if($item != 'refactored some code'){ $tags[] = 'Refactor'; $currentchangelogblock[] = array('type' => 'refactor', 'body' => $item); } break; case 'config': if($item != 'changed some config setting'){ $tags[] = 'Config Update'; $currentchangelogblock[] = array('type' => 'config', 'body' => $item); } break; case 'admin': if($item != 'messed with admin stuff'){ $tags[] = 'Administration'; $currentchangelogblock[] = array('type' => 'admin', 'body' => $item); } break; case 'server': if($item != 'something server ops should know') $currentchangelogblock[] = array('type' => 'server', 'body' => $item); break; default: //we add it to the last changelog entry as a separate line if (count($currentchangelogblock) > 0) $currentchangelogblock[count($currentchangelogblock)-1]['body'] .= "\n".$line; break; } } if(!count($changelogbody)) $no_changelog = true; if ($no_changelog || !$compile) return $tags; $file = 'author: "'.trim(str_replace(array("\\", '"'), array("\\\\", "\\\""), $username)).'"'."\n"; $file .= "delete-after: True\n"; $file .= "changes: \n"; foreach ($changelogbody as $changelogitem) { $type = $changelogitem['type']; $body = trim(str_replace(array("\\", '"'), array("\\\\", "\\\""), $changelogitem['body'])); $file .= ' - '.$type.': "'.$body.'"'; $file .= "\n"; } $content = array ( 'branch' => $payload['pull_request']['base']['ref'], 'message' => 'Automatic changelog generation for PR #'.$payload['pull_request']['number'].' [ci skip]', 'content' => base64_encode($file) ); $filename = '/html/changelogs/AutoChangeLog-pr-'.$payload['pull_request']['number'].'.yml'; echo github_apisend($payload['pull_request']['base']['repo']['url'].'/contents'.$filename, 'PUT', $content); } function game_server_send($addr, $port, $str) { // All queries must begin with a question mark (ie "?players") if($str[0] != '?') $str = ('?' . $str); /* --- Prepare a packet to send to the server (based on a reverse-engineered packet structure) --- */ $query = "\x00\x83" . pack('n', strlen($str) + 6) . "\x00\x00\x00\x00\x00" . $str . "\x00"; /* --- Create a socket and connect it to the server --- */ $server = socket_create(AF_INET,SOCK_STREAM,SOL_TCP) or exit("ERROR"); socket_set_option($server, SOL_SOCKET, SO_SNDTIMEO, array('sec' => 2, 'usec' => 0)); //sets connect and send timeout to 2 seconds if(!socket_connect($server,$addr,$port)) { return "ERROR: Connection failed"; } /* --- Send bytes to the server. Loop until all bytes have been sent --- */ $bytestosend = strlen($query); $bytessent = 0; while ($bytessent < $bytestosend) { //echo $bytessent.'
'; $result = socket_write($server,substr($query,$bytessent),$bytestosend-$bytessent); //echo 'Sent '.$result.' bytes
'; if ($result===FALSE) return "ERROR: " . socket_strerror(socket_last_error()); $bytessent += $result; } /* --- Idle for a while until recieved bytes from game server --- */ $result = socket_read($server, 10000, PHP_BINARY_READ); socket_close($server); // we don't need this anymore if($result != "") { if($result[0] == "\x00" || $result[1] == "\x83") { // make sure it's the right packet format // Actually begin reading the output: $sizebytes = unpack('n', $result[2] . $result[3]); // array size of the type identifier and content $size = $sizebytes[1] - 1; // size of the string/floating-point (minus the size of the identifier byte) if($result[4] == "\x2a") { // 4-byte big-endian floating-point $unpackint = unpack('f', $result[5] . $result[6] . $result[7] . $result[8]); // 4 possible bytes: add them up together, unpack them as a floating-point return $unpackint[1]; } else if($result[4] == "\x06") { // ASCII string $unpackstr = ""; // result string $index = 5; // string index while($size > 0) { // loop through the entire ASCII string $size--; $unpackstr .= $result[$index]; // add the string position to return string $index++; } return $unpackstr; } } } return ""; } ?>