diff --git a/public/webhook-phabricator/index.php b/public/webhook-phabricator/index.php index 2d1c7c2..69fdf7b 100644 --- a/public/webhook-phabricator/index.php +++ b/public/webhook-phabricator/index.php @@ -1,208 +1,208 @@ ClickUp bot # Copyright (C) 2023 Valerio Bozzolan, contributors # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . /** * Receive Phabricator webhooks * * Documentation: * https://we.phorge.it/conduit/ * * See also Herald rule. */ // default response code http_response_code( 500 ); // load needed libraries require realpath( __DIR__ ) . '/../autoload.php'; // parse HTTP payload $response = file_get_contents('php://input'); // parse as an array since native Conduit also handles everything as an array $response_data = json_decode( $response, JSON_OBJECT_AS_ARRAY ); // no signature no party $signature_header = $_SERVER['HTTP_X_PHABRICATOR_WEBHOOK_SIGNATURE'] ?? null; if( !$signature_header ) { http_response_code( 400 ); echo "This is not a request from an official Phabricator webhook.\n"; exit; } // do the math $signature_calculated = hash_hmac( 'sha256', $response, PHABRICATOR_WEBHOOK_HMAC_KEY ); if( $signature_calculated !== $signature_header ) { http_response_code( 400 ); throw new Exception( "invalid signature, calculated $signature_calculated expected $signature_header" ); exit; } // no object no party $object = $response_data['object'] ?? null; if( !$object ) { http_response_code( 400 ); throw new Exception( "received empty Phabricator object" ); } $transactions = $response_data['transactions'] ?? []; $object_type = $object['type']; $object_phid = $object['phid']; $cache = ClickUpPhabricatorCache::instance(); switch( $object_type ) { case 'TASK': // query fresh Task data $phab_task = PhabricatorAPI::getTaskByPHID( $object_phid ); if( !$phab_task ) { throw new Exception( "missing Task: {$object_phid}" ); } $clickup_changes = []; // resolved / open / etc. $phab_task_id = $phab_task['id']; $phab_task_phid = $phab_task['phid']; $phab_task_status = $phab_task['fields']['status']['value']; $phab_owner_phid = $phab_task['fields']['ownerPHID'] ?? null; // check if the Phabricator Task is already known to be connected to ClickUp $clickup_task = null; $clickup_task_cache = $cache->getClickupTaskFromPhabricatorTaskID( $phab_task_id ); if( $clickup_task_cache ) { $clickup_task = ClickUpAPI::queryTaskData( $clickup_task_cache->id ); } // no related ClickUp Task? if( !$clickup_task ) { // check if this is recent $phab_task_data_created = $phab_task['fields']['dateCreated']; $time_diff = time() - $phab_task_data_created; if( $time_diff < 600 ) { // try to find the most relevant ClickUp List $clickup_list = PhabricatorAPI::findClickupListFromTaskObject( $phab_task ); if( $clickup_list ) { error_log( "creating ClickUp Task from Phabricator Task T{$phab_task_id} on ClickUp List '{$clickup_list->name}' ID {$clickup_list->id}" ); // try to assign this ClickUp Task to somebody $clickup_assignees = []; $clickup_assignee = $cache->findClickupUserbyPHID( $phab_owner_phid ); if( $clickup_assignee ) { $clickup_assignees[] = $clickup_assignee; } else { error_log( "cannot determine ClickUp assignee from Phabricator Task T{$phab_task_id} that has owner {$phab_owner_phid} - please run ./cli/clickup-users.php" ); } error_log( "to be implemented: find ClickUp parent Task from Conduit API edge.search with edge type = task.parent https://sviluppo.erinformatica.it/conduit/method/edge.search/" ); // create in ClickUp as well // $clickup_task = ClickUpAPI::createTaskOnList( $clickup_list->id, [ // 'name' => $phab_task['fields']['name'], // 'description' => $phab_task['fields']['description']['raw'], // 'assignees' => $clickup_assignees, // 'status' => PhabricatorAPI::status2clickup( $phab_task_status ), // 'parent' => '', - ] ); +// ] ); } else { error_log( "cannot determine ClickUp List for Phabricator Task T{$phab_task_id} - please run ./cli/clickup-folders.php" ); } } else { error_log( "Phabricator Task T{$phab_task_id} has not a related ClickUp Task and it's too old ($time_diff seconds)" ); } } if( $clickup_task ) { // check if Phabricator has a different Task status than ClickUp $phab_task_is_closed = PhabricatorAPI::isStatusClosed( $phab_task_status ); $clickup_task_is_closed = ClickupAPI::isStatusClosed( $clickup_task->status->type ); if( $clickup_task->status->type !== $clickup_task_is_closed ) { // update the Task status in ClickUp $clickup_changes[ 'status' ] = PhabricatorAPI::status2clickup( $phab_task_status ); } // check if we have to add a Task assignee in ClickUp if( $phab_owner_phid ) { $clickup_assignee_id = $cache->findClickupUserIDbyPHID( $phab_owner_phid ); if( $clickup_assignee_id ) { $clickup_changes['assignees.add'] = $clickup_assignee_id; } } else { // TODO: somehow remove the removed assignee from ClickUp } } if( $transactions ) { $transaction_results = PhabricatorAPI::searchObjectTransactionsFromTransactions( $phab_task_phid, $transactions ); foreach( $transaction_results as $transaction_result ) { // found some new Phabricator comments $phab_comments = $transaction_result['comments'] ?? []; foreach( $phab_comments as $phab_comment ) { // Phabricator comment content $comment_content_raw = $phab_comment['content']['raw']; // Phabricator author (NOTE: it MUST exists) $comment_author = PhabricatorAPI::searchSingleUserByPHID( $phab_comment['authorPHID'] ); $comment_author_name = $comment_author['fields']['realName']; // post the comment on ClickUp if( $clickup_task ) { // "From Phabricator: Mario Rossi:\n\nHello world!" // IMPORTANT: keep the COMMENT_PREFIX_FROM_PHABRICATOR at startup since now // the ClickUp webhook controls this token at startup. $clickup_comment = sprintf( "%s %s:\n\n%s", COMMENT_PREFIX_FROM_PHABRICATOR, $comment_author_name, $comment_content_raw ); ClickupAPI::addTaskComment( $clickup_task->id, $clickup_comment ); } } } } // eventally update ClickUp with some changes if( $clickup_task && $clickup_changes ) { ClickUpAPI::putTaskData( $clickup_task->id, [], $clickup_changes ); } break; case 'HWBH': error_log( "received Phabricator Webhook (test?)" ); break; default: error_log( "unknown Phabricator webhook: {$response}" ); } // success http_response_code( 200 );