diff --git a/public/webhook-phabricator/index.php b/public/webhook-phabricator/index.php index 2770f1c..7a94e01 100644 --- a/public/webhook-phabricator/index.php +++ b/public/webhook-phabricator/index.php @@ -1,239 +1,244 @@ 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}" ); } // array of stuff to be set in the ClickUp Task $clickup_changes = []; // ClickUp List in which we will save the Task $clickup_list_id = null; // 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 ); } // check if we already know the related ClickUp Task if( $clickup_task ) { // update an existing 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 } } else { // create a fresh 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 < 3000 ) { // set ClickUp basic fields $clickup_changes['name'] = $phab_task['fields']['name']; - $clickup_changes['description'] = $phab_task['fields']['description']['raw']; $clickup_changes['status'] = PhabricatorAPI::status2clickup( $phab_task_status ); + // import the description (adding the self Phabricator URL) + $clickup_changes['description'] = $phab_task['fields']['description']['raw']; + $clickup_changes['description'] .= "\n\n"; + $clickup_changes['description'] .= PHABRICATOR_URL . "T{$phab_task_id}"; + $clickup_changes['description'] = trim( $clickup_changes['description'] ); + // try to assign this ClickUp Task to somebody $clickup_assignee = $cache->findClickupUserbyPHID( $phab_owner_phid ); if( $clickup_assignee ) { $clickup_changes['assignees'] = [ $clickup_assignee->id ]; } 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" ); } // check if this Phabricator Tag has some parents (and find a ClickUp parent) $phab_task_parent_phids = PhabricatorAPI::queryParentTaskPHIDsFromTaskPHID( $phab_task_phid ); $clickup_task_parents = $cache->getClickupTasksFromPhabricatorTaskPHIDs( $phab_task_parent_phids ); $clickup_task_parent = array_pop( $clickup_task_parents ); // just the first one asd // we have a ClickUp Task parent! so we have a 100% sure List ID if( $clickup_task_parent ) { // let's save this ClickUp Task on the same List of the parent ClickUp Task // and also let's connect the parent ClickUp Task to the current one $clickup_list_id = $clickup_task_parent->list->id; $clickup_changes['parent'] = $clickup_task_parent->id; } else { // try to find the most relevant ClickUp List from the Phabricator Task (from its Tags etc.) $clickup_list = PhabricatorAPI::findClickupListFromTaskObject( $phab_task ); if( $clickup_list ) { $clickup_list_id = $clickup_list->id; } else { error_log( "cannot determine ClickUp List for Phabricator Task T{$phab_task_id} - please run ./cli/clickup-folders.php" ); } } // if we know the ClickUp List ID, let's create this new ClickUp Task if( $clickup_list_id && $clickup_changes ) { // create the ClickUp task $clickup_task = ClickUpAPI::createTaskOnList( $clickup_list_id, $clickup_changes ); // after creation, reset changes, to avoid to run them again later $clickup_changes = []; } } else { error_log( "Phabricator Task T{$phab_task_id} has not a related ClickUp Task and it's too old ($time_diff seconds)" ); } } 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 ); } } // check if we have missed something to be integrated unset( $transaction_result['comments'] ); if( $transaction_result ) { error_log( "received unsupported Phabricator transaction: " . json_encode( $transaction_result ) ); } } } // 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 );