From 992f01e0c17972c32de36c4eaf7a6a8ad33d6d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Mon, 16 Dec 2024 10:24:51 +0000 Subject: [PATCH 01/14] Store account rate limits --- includes/core.php | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/includes/core.php b/includes/core.php index bbf95d77..cd41eda2 100644 --- a/includes/core.php +++ b/includes/core.php @@ -51,6 +51,7 @@ function setup() { add_filter( 'autoshare_for_twitter_attached_image', __NAMESPACE__ . '\maybe_disable_upload_image', 10, 2 ); add_action( 'admin_init', __NAMESPACE__ . '\handle_notice_dismiss' ); add_action( 'admin_notices', __NAMESPACE__ . '\migrate_to_twitter_v2_api' ); + add_action( 'autoshare_for_twitter_after_status_update', __NAMESPACE__ . '\update_account_rate_limits', 10, 5 ); } /** @@ -222,3 +223,53 @@ function handle_notice_dismiss() { update_option( 'autoshare_migrate_to_v2_api_notice_dismissed', true ); } } + +/** + * Update the account rate limits from the last Twitter API request. + * + * @param object $response The response from the Twitter endpoint. + * @param array $update_data Data to send to the Twitter endpoint. + * @param \WP_Post $post The post associated with the tweet. + * @param string $account_id The account ID associated with the tweet. + * @param array|null $last_headers The headers from the last request. + * @return void + */ +function update_account_rate_limits( $response, $update_data, $post, $account_id, $last_headers ) { + + if ( empty( $account_id ) ) { + return; + } + + $accounts = get_option( 'autoshare_for_twitter_accounts', array() ); + + if ( empty( $accounts[ $account_id ] ) ) { + return; + } + + $accounts_rates = get_option( 'autopost_for_x_accounts_rates', array() ); + + $map = array( + 'rate_limit_limit' => 'x_rate_limit_limit', + 'rate_limit_reset' => 'x_rate_limit_reset', + 'rate_limit_remaining' => 'x_rate_limit_remaining', + 'app_limit_24hour_limit' => 'x_app_limit_24hour_limit', + 'app_limit_24hour_reset' => 'x_app_limit_24hour_reset', + 'app_limit_24hour_remaining' => 'x_app_limit_24hour_remaining', + 'user_limit_24hour_limit' => 'x_user_limit_24hour_limit', + 'user_limit_24hour_reset' => 'x_user_limit_24hour_reset', + 'user_limit_24hour_remaining' => 'x_user_limit_24hour_remaining', + ); + + $accounts_rates[ $account_id ] = array(); + + foreach ( $map as $key => $header ) { + + if ( ! isset( $last_headers[ $header ] ) ) { + continue; + } + + $accounts_rates[ $account_id ][ $key ] = sanitize_text_field( $last_headers[ $header ] ); + } + + update_option( 'autopost_for_x_accounts_rates', $accounts_rates ); +} From 67d9bdb746146652ea3f0d775e2b07caa4f0c1f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Mon, 16 Dec 2024 13:23:25 +0000 Subject: [PATCH 02/14] Display X/Twitter API rate limits per account on a dashboard widget --- assets/css/admin-autoshare-for-twitter.css | 57 +++++++ includes/core.php | 176 +++++++++++++++++++++ 2 files changed, 233 insertions(+) diff --git a/assets/css/admin-autoshare-for-twitter.css b/assets/css/admin-autoshare-for-twitter.css index a5a4ab1f..d6ccc4c5 100644 --- a/assets/css/admin-autoshare-for-twitter.css +++ b/assets/css/admin-autoshare-for-twitter.css @@ -265,3 +265,60 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { margin-top: 12px; display: block; } + +#autopost_for_x_rate_monitor_dashboard_widget .inside { + padding: 0; +} + +.autoshare-for-twitter-rate-monitor__account { + align-items: center; + display: flex; + padding: 0 12px 12px; +} + +.autoshare-for-twitter-rate-monitor__account img { + border-radius: 50%; + margin-right: 10px; + max-width: 48px; +} + +#autopost_for_x_rate_monitor_dashboard_widget .autoshare-for-twitter-rate-monitor__account h3 { + font-weight: bold; +} + +.autoshare-for-twitter-rate-monitor__rates { + border-bottom: 1px solid #f0f0f1; + font-size: 13px; + padding: 0 12px 12px; +} + +.autoshare-for-twitter-rate-monitor__rate { + margin-bottom: 8px; +} + +.autoshare-for-twitter-rate-monitor__rate p { + margin-bottom: 0; + margin-top: 0; +} + +.autoshare-for-twitter-rate-monitor__reset { + font-size: 12px; + font-style: italic; +} + +.autoshare-for-twitter-rate-monitor__footer { + color: #50575e; + background: #f6f7f7; + border-top: 1px solid #f0f0f1; + padding: 12px; +} + +.autoshare-for-twitter-rate-monitor__footer ul { + margin-bottom: 0; + margin-top: 0; +} + +.autoshare-for-twitter-rate-monitor__footer p { + margin-bottom: 8px; + margin-top: 0; +} \ No newline at end of file diff --git a/includes/core.php b/includes/core.php index cd41eda2..dc85aceb 100644 --- a/includes/core.php +++ b/includes/core.php @@ -52,6 +52,7 @@ function setup() { add_action( 'admin_init', __NAMESPACE__ . '\handle_notice_dismiss' ); add_action( 'admin_notices', __NAMESPACE__ . '\migrate_to_twitter_v2_api' ); add_action( 'autoshare_for_twitter_after_status_update', __NAMESPACE__ . '\update_account_rate_limits', 10, 5 ); + add_action( 'wp_dashboard_setup', __NAMESPACE__ . '\register_rate_monitor_dashboard_widget' ); } /** @@ -248,6 +249,9 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id $accounts_rates = get_option( 'autopost_for_x_accounts_rates', array() ); + /** + * Map the headers from the last request to internal keys. + */ $map = array( 'rate_limit_limit' => 'x_rate_limit_limit', 'rate_limit_reset' => 'x_rate_limit_reset', @@ -273,3 +277,175 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id update_option( 'autopost_for_x_accounts_rates', $accounts_rates ); } + +/** + * Register the Rate Monitor dashboard widget. + * + * @return void + */ +function register_rate_monitor_dashboard_widget() { + + wp_add_dashboard_widget( + 'autopost_for_x_rate_monitor_dashboard_widget', + esc_html__( 'Autopost for X — Rate Monitor', 'autoshare-for-twitter' ), + __NAMESPACE__ . '\display_rate_monitor_dashboard_widget' + ); +} + +/** + * Display the Rate Monitor dashboard widget. + * + * @return void + */ +function display_rate_monitor_dashboard_widget() { + $accounts = get_option( 'autoshare_for_twitter_accounts', array() ); + + if ( empty( $accounts ) ) { + printf( + '

%s

', + esc_html__( 'No X/Twitter accounts are connected. Please connect at least one X/Twitter account to continue using Autopost for X.', 'autoshare-for-twitter' ) + ); + return; + } + + $accounts_rates = get_option( 'autopost_for_x_accounts_rates', array() ); + + if ( empty( $accounts_rates ) ) { + printf( + '

%s

', + esc_html__( 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) + ); + return; + } + + $rows = array( + array( + 'label' => __( 'Rate Limit', 'autoshare-for-twitter' ), + 'remaining_key' => 'rate_limit_remaining', + 'limit_key' => 'rate_limit_limit', + 'reset_key' => 'rate_limit_reset', + ), + array( + 'label' => __( 'User 24-Hour Limit', 'autoshare-for-twitter' ), + 'remaining_key' => 'user_limit_24hour_remaining', + 'limit_key' => 'user_limit_24hour_limit', + 'reset_key' => 'user_limit_24hour_reset', + ), + array( + 'label' => __( 'App 24-Hour Limit', 'autoshare-for-twitter' ), + 'remaining_key' => 'app_limit_24hour_remaining', + 'limit_key' => 'app_limit_24hour_limit', + 'reset_key' => 'app_limit_24hour_reset', + ), + ); + + $accounts_data = ''; + + foreach ( $accounts_rates as $account_id => $rates ) { + + if ( empty( $accounts[ $account_id ] ) ) { + continue; + } + + $account_data = array_map( + function ( $row ) use ( $rates ) { + $remaining = isset( $rates[ $row['remaining_key'] ] ) ? (int) $rates[ $row['remaining_key'] ] : 0; + $limit = isset( $rates[ $row['limit_key'] ] ) ? (int) $rates[ $row['limit_key'] ] : 0; + $reset = isset( $rates[ $row['reset_key'] ] ) ? human_readable_time( $rates[ $row['reset_key'] ] ) : esc_html__( 'N/A', 'autoshare-for-twitter' ); + + return sprintf( + '
+

%1$s: %2$s

+

%3$s

+
', + esc_html( $row['label'] ), + sprintf( + /* translators: %1$s: Remaining, %2$s: Limit */ + esc_html__( '%1$s of %2$s', 'autoshare-for-twitter' ), + esc_html( $remaining ), + esc_html( $limit ) + ), + sprintf( + /* translators: %1$s: Reset time */ + esc_html__( 'Resets on %1$s', 'autoshare-for-twitter' ), + esc_html( $reset ) + ) + ); + }, + $rows + ); + + $accounts_data .= sprintf( + ' +
+ %4$s +
', + esc_url( $accounts[ $account_id ]['profile_image_url'] ), + esc_attr( $accounts[ $account_id ]['name'] ), + esc_html( $accounts[ $account_id ]['username'] ), + implode( ' ', $account_data ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + ); + } + + $footnotes = array( + __( 'Rate Limit: The maximum number of API calls allowed within a 15-minute window for the current app.', 'autoshare-for-twitter' ), + __( 'User 24-Hour Limit: The maximum number of requests a single user can make across all API endpoints within a 24-hour period.', 'autoshare-for-twitter' ), + __( 'App 24-Hour Limit: The total number of API calls your app can make across all users within a 24-hour period.', 'autoshare-for-twitter' ), + ); + + $footnotes = array_map( + function ( $footnote ) { + return sprintf( + '
  • %1$s
  • ', + esc_html( $footnote ) + ); + }, + $footnotes + ); + + printf( + '
    + %1$s + +
    ', + $accounts_data, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + implode( ' ', $footnotes ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + esc_html__( 'Note:', 'autoshare-for-twitter' ), + esc_html__( 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', 'autoshare-for-twitter' ) + ); +} + +/** + * Get human readable time. + * + * @param int $timestamp Timestamp. + * @param string $date_format Date format. + * @return string + */ +function human_readable_time( $timestamp, $date_format = '' ) { + + $timestamp = (int) $timestamp; + + $timezone = wp_timezone(); + + $datetime = new \DateTime( '@' . $timestamp, new \DateTimeZone( 'UTC' ) ); + $datetime->setTimezone( $timezone ); + + if ( empty( $date_format ) ) { + $date_format = sprintf( + '%s %s (T)', + esc_html( get_option( 'date_format' ) ), + esc_html( get_option( 'time_format' ) ) + ); + } + + $human_readable_time = $datetime->format( $date_format ); + + return $human_readable_time; +} From bb2769f3b5f2e4c9122537a02cc09cbcf5231f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Mon, 16 Dec 2024 15:09:08 +0000 Subject: [PATCH 03/14] Move rate limits to account data --- includes/core.php | 107 +++++++++++++++++++++++----------------------- 1 file changed, 53 insertions(+), 54 deletions(-) diff --git a/includes/core.php b/includes/core.php index dc85aceb..22e28842 100644 --- a/includes/core.php +++ b/includes/core.php @@ -247,8 +247,6 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id return; } - $accounts_rates = get_option( 'autopost_for_x_accounts_rates', array() ); - /** * Map the headers from the last request to internal keys. */ @@ -264,7 +262,7 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id 'user_limit_24hour_remaining' => 'x_user_limit_24hour_remaining', ); - $accounts_rates[ $account_id ] = array(); + $account_rate_limits = array(); foreach ( $map as $key => $header ) { @@ -272,10 +270,12 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id continue; } - $accounts_rates[ $account_id ][ $key ] = sanitize_text_field( $last_headers[ $header ] ); + $account_rate_limits[ $key ] = sanitize_text_field( $last_headers[ $header ] ); } - update_option( 'autopost_for_x_accounts_rates', $accounts_rates ); + $accounts[ $account_id ]['rate_limits'] = $account_rate_limits; + + update_option( 'autoshare_for_twitter_accounts', $accounts ); } /** @@ -308,16 +308,6 @@ function display_rate_monitor_dashboard_widget() { return; } - $accounts_rates = get_option( 'autopost_for_x_accounts_rates', array() ); - - if ( empty( $accounts_rates ) ) { - printf( - '

    %s

    ', - esc_html__( 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) - ); - return; - } - $rows = array( array( 'label' => __( 'Rate Limit', 'autoshare-for-twitter' ), @@ -339,43 +329,52 @@ function display_rate_monitor_dashboard_widget() { ), ); - $accounts_data = ''; - - foreach ( $accounts_rates as $account_id => $rates ) { + $all_accounts_markup = ''; + + foreach ( $accounts as $account ) { + + $account_markup = ''; + + if ( ! empty( $account['rate_limits'] ) ) { + $rate_limits = $account['rate_limits']; + + $account_markup = array_map( + function ( $row ) use ( $rate_limits ) { + $remaining = isset( $rate_limits[ $row['remaining_key'] ] ) ? (int) $rate_limits[ $row['remaining_key'] ] : 0; + $limit = isset( $rate_limits[ $row['limit_key'] ] ) ? (int) $rate_limits[ $row['limit_key'] ] : 0; + $reset = isset( $rate_limits[ $row['reset_key'] ] ) ? human_readable_time( $rate_limits[ $row['reset_key'] ] ) : esc_html__( 'N/A', 'autoshare-for-twitter' ); + + return sprintf( + '
    +

    %1$s: %2$s

    +

    %3$s

    +
    ', + esc_html( $row['label'] ), + sprintf( + /* translators: %1$s: Remaining, %2$s: Limit */ + esc_html__( '%1$s of %2$s', 'autoshare-for-twitter' ), + esc_html( $remaining ), + esc_html( $limit ) + ), + sprintf( + /* translators: %1$s: Reset time */ + esc_html__( 'Resets on %1$s', 'autoshare-for-twitter' ), + esc_html( $reset ) + ) + ); + }, + $rows + ); - if ( empty( $accounts[ $account_id ] ) ) { - continue; + $account_markup = implode( ' ', $account_markup ); + } else { + $account_markup = sprintf( + '

    %s

    ', + esc_html__( 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) + ); } - $account_data = array_map( - function ( $row ) use ( $rates ) { - $remaining = isset( $rates[ $row['remaining_key'] ] ) ? (int) $rates[ $row['remaining_key'] ] : 0; - $limit = isset( $rates[ $row['limit_key'] ] ) ? (int) $rates[ $row['limit_key'] ] : 0; - $reset = isset( $rates[ $row['reset_key'] ] ) ? human_readable_time( $rates[ $row['reset_key'] ] ) : esc_html__( 'N/A', 'autoshare-for-twitter' ); - - return sprintf( - '
    -

    %1$s: %2$s

    -

    %3$s

    -
    ', - esc_html( $row['label'] ), - sprintf( - /* translators: %1$s: Remaining, %2$s: Limit */ - esc_html__( '%1$s of %2$s', 'autoshare-for-twitter' ), - esc_html( $remaining ), - esc_html( $limit ) - ), - sprintf( - /* translators: %1$s: Reset time */ - esc_html__( 'Resets on %1$s', 'autoshare-for-twitter' ), - esc_html( $reset ) - ) - ); - }, - $rows - ); - - $accounts_data .= sprintf( + $all_accounts_markup .= sprintf( ' ', - $accounts_data, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped + $all_accounts_markup, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped implode( ' ', $footnotes ), // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped esc_html__( 'Note:', 'autoshare-for-twitter' ), esc_html__( 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', 'autoshare-for-twitter' ) From 5fb62406feb2f968f783f16bd1edf1c95391c242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Mon, 16 Dec 2024 17:08:19 +0000 Subject: [PATCH 04/14] Add rate limits info to the block editor --- assets/css/admin-autoshare-for-twitter.css | 7 +- includes/core.php | 18 +-- src/js/components/TwitterAccounts.js | 162 +++++++++++++++++---- 3 files changed, 149 insertions(+), 38 deletions(-) diff --git a/assets/css/admin-autoshare-for-twitter.css b/assets/css/admin-autoshare-for-twitter.css index d6ccc4c5..5b97707d 100644 --- a/assets/css/admin-autoshare-for-twitter.css +++ b/assets/css/admin-autoshare-for-twitter.css @@ -246,7 +246,7 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { border-radius: 50%; } -.autoshare-for-twitter-accounts-wrapper .twitter-account-wrapper span.account-details { +.autoshare-for-twitter-accounts-wrapper .twitter-account-wrapper div.account-details { padding-left: 8px; padding-right: 8px; } @@ -321,4 +321,9 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { .autoshare-for-twitter-rate-monitor__footer p { margin-bottom: 8px; margin-top: 0; +} + +.autoshare-for-twitter-accounts-wrapper .autoshare-for-twitter-rate-monitor__rates { + border-bottom: 0; + padding: 0; } \ No newline at end of file diff --git a/includes/core.php b/includes/core.php index 22e28842..8d803d39 100644 --- a/includes/core.php +++ b/includes/core.php @@ -310,19 +310,19 @@ function display_rate_monitor_dashboard_widget() { $rows = array( array( - 'label' => __( 'Rate Limit', 'autoshare-for-twitter' ), + 'label' => __( 'Rate Limit:', 'autoshare-for-twitter' ), 'remaining_key' => 'rate_limit_remaining', 'limit_key' => 'rate_limit_limit', 'reset_key' => 'rate_limit_reset', ), array( - 'label' => __( 'User 24-Hour Limit', 'autoshare-for-twitter' ), + 'label' => __( 'User 24-Hour Limit:', 'autoshare-for-twitter' ), 'remaining_key' => 'user_limit_24hour_remaining', 'limit_key' => 'user_limit_24hour_limit', 'reset_key' => 'user_limit_24hour_reset', ), array( - 'label' => __( 'App 24-Hour Limit', 'autoshare-for-twitter' ), + 'label' => __( 'App 24-Hour Limit:', 'autoshare-for-twitter' ), 'remaining_key' => 'app_limit_24hour_remaining', 'limit_key' => 'app_limit_24hour_limit', 'reset_key' => 'app_limit_24hour_reset', @@ -340,13 +340,13 @@ function display_rate_monitor_dashboard_widget() { $account_markup = array_map( function ( $row ) use ( $rate_limits ) { - $remaining = isset( $rate_limits[ $row['remaining_key'] ] ) ? (int) $rate_limits[ $row['remaining_key'] ] : 0; - $limit = isset( $rate_limits[ $row['limit_key'] ] ) ? (int) $rate_limits[ $row['limit_key'] ] : 0; + $remaining = isset( $rate_limits[ $row['remaining_key'] ] ) ? (int) $rate_limits[ $row['remaining_key'] ] : __( 'N/A', 'autoshare-for-twitter' ); + $limit = isset( $rate_limits[ $row['limit_key'] ] ) ? (int) $rate_limits[ $row['limit_key'] ] : __( 'N/A', 'autoshare-for-twitter' ); $reset = isset( $rate_limits[ $row['reset_key'] ] ) ? human_readable_time( $rate_limits[ $row['reset_key'] ] ) : esc_html__( 'N/A', 'autoshare-for-twitter' ); return sprintf( '
    -

    %1$s: %2$s

    +

    %1$s %2$s

    %3$s

    ', esc_html( $row['label'] ), @@ -431,20 +431,18 @@ function human_readable_time( $timestamp, $date_format = '' ) { $timestamp = (int) $timestamp; - $timezone = wp_timezone(); - $datetime = new \DateTime( '@' . $timestamp, new \DateTimeZone( 'UTC' ) ); - $datetime->setTimezone( $timezone ); if ( empty( $date_format ) ) { $date_format = sprintf( - '%s %s (T)', + '%s %s', esc_html( get_option( 'date_format' ) ), esc_html( get_option( 'time_format' ) ) ); } $human_readable_time = $datetime->format( $date_format ); + $human_readable_time = sprintf( '%s (UTC)', $human_readable_time ); return $human_readable_time; } diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index 79add787..bd47ec56 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -1,8 +1,10 @@ import { ToggleControl, ExternalLink } from '@wordpress/components'; -import { __ } from '@wordpress/i18n'; +import { __, sprintf } from '@wordpress/i18n'; +import { dateI18n, getSettings } from '@wordpress/date'; import { useTweetAccounts } from '../hooks'; const { connectedAccounts, connectAccountUrl } = adminAutoshareForTwitter; +const settings = getSettings(); /** * Twitter accounts component. @@ -39,32 +41,138 @@ function TwitterAccount( props ) { const [ tweetAccounts, setTweetAccounts ] = useTweetAccounts(); const { id, name, username, profile_image_url: profileUrl } = props; return ( -
    - { - - @{ username } -
    - { name } -
    - { - if ( checked ) { - setTweetAccounts( [ ...tweetAccounts, id ] ); - } else { - setTweetAccounts( - tweetAccounts.filter( - ( account ) => account !== id - ) - ); - } - } } - className="autoshare-for-twitter-account-toggle" - /> + <> +
    + { +
    + @{ username } +
    + { name } +
    + + { + if ( checked ) { + setTweetAccounts( [ ...tweetAccounts, id ] ); + } else { + setTweetAccounts( + tweetAccounts.filter( + ( account ) => account !== id + ) + ); + } + } } + className="autoshare-for-twitter-account-toggle" + /> +
    + + + ); +} + +/** + * Main component to display Twitter account rate limits. + * + * @param {Object} props + * @param {Object} props.rate_limits - Rate limit data from the API. + * @return {JSX.Element} + */ +function TwitterAccountRateLimits( { rate_limits: rateLimits } ) { + return ( +
    +
    + { rateLimits ? ( + <> + + + + + ) : ( +

    + { __( + 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', + 'autoshare-for-twitter' + ) } +

    + ) } +
    +
    +

    + { __( 'Note:', 'autoshare-for-twitter' ) }{ ' ' } + { __( + 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', + 'autoshare-for-twitter' + ) } +

    +
    +
    + ); +} + +/** + * Reusable component to display rate limit details. + * + * @param {Object} props + * @param {string} props.title - The title of the rate limit (e.g., "Rate Limit"). + * @param {number} props.remaining - The remaining requests for this limit. + * @param {number} props.limit - The total limit for this type. + * @param {number} props.reset - The UNIX timestamp for when the limit resets. + * @return {JSX.Element} + */ +function TwitterAccountRateLimit( { title, remaining, limit, reset } ) { + let formattedResetTime = __( 'N/A', 'autoshare-for-twitter' ); + if ( reset && settings?.formats?.datetime ) { + formattedResetTime = dateI18n( + settings.formats.datetime, + reset * 1000, + 'UTC' + ); + formattedResetTime = sprintf( '%1$s (UTC)', formattedResetTime ); + } + + return ( +
    +

    + { title }{ ' ' } + { sprintf( + /* translators: %1$s: Remaining, %2$s: Limit */ + __( '%1$s of %2$s', 'autoshare-for-twitter' ), + remaining ?? __( 'N/A', 'autoshare-for-twitter' ), + limit ?? __( 'N/A', 'autoshare-for-twitter' ) + ) } +

    +

    + { formattedResetTime } +

    ); } From ed0c820c2fe72475d46efc6b0b89e71d885fbf65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 09:39:22 +0000 Subject: [PATCH 05/14] Revert change --- assets/css/admin-autoshare-for-twitter.css | 2 +- src/js/components/TwitterAccounts.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/css/admin-autoshare-for-twitter.css b/assets/css/admin-autoshare-for-twitter.css index 5b97707d..94b479db 100644 --- a/assets/css/admin-autoshare-for-twitter.css +++ b/assets/css/admin-autoshare-for-twitter.css @@ -246,7 +246,7 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { border-radius: 50%; } -.autoshare-for-twitter-accounts-wrapper .twitter-account-wrapper div.account-details { +.autoshare-for-twitter-accounts-wrapper .twitter-account-wrapper span.account-details { padding-left: 8px; padding-right: 8px; } diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index bd47ec56..9e1c947a 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -48,11 +48,11 @@ function TwitterAccount( props ) { alt={ name } className="twitter-account-profile-image" /> -
    + @{ username }
    { name } -
    + Date: Tue, 17 Dec 2024 09:49:40 +0000 Subject: [PATCH 06/14] Add tooltips for the different rates --- src/js/components/TwitterAccounts.js | 31 +++++++++++++++++++++------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index 9e1c947a..bf3228f7 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -1,4 +1,4 @@ -import { ToggleControl, ExternalLink } from '@wordpress/components'; +import { ToggleControl, ExternalLink, Tooltip } from '@wordpress/components'; import { __, sprintf } from '@wordpress/i18n'; import { dateI18n, getSettings } from '@wordpress/date'; @@ -80,7 +80,7 @@ function TwitterAccount( props ) { * * @param {Object} props * @param {Object} props.rate_limits - Rate limit data from the API. - * @return {JSX.Element} + * @return {JSX.Element} The account rate limits. */ function TwitterAccountRateLimits( { rate_limits: rateLimits } ) { return ( @@ -88,29 +88,41 @@ function TwitterAccountRateLimits( { rate_limits: rateLimits } ) {
    { rateLimits ? ( <> - - -

    - { title }{ ' ' } + + { title } + { ' ' } { sprintf( /* translators: %1$s: Remaining, %2$s: Limit */ __( '%1$s of %2$s', 'autoshare-for-twitter' ), From 9ce65a6f346c878f5834b45d06f6149bb479a564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 11:00:41 +0000 Subject: [PATCH 07/14] Remove "global" rate limit from dashboard --- includes/core.php | 6 ------ 1 file changed, 6 deletions(-) diff --git a/includes/core.php b/includes/core.php index 8d803d39..318548d3 100644 --- a/includes/core.php +++ b/includes/core.php @@ -309,12 +309,6 @@ function display_rate_monitor_dashboard_widget() { } $rows = array( - array( - 'label' => __( 'Rate Limit:', 'autoshare-for-twitter' ), - 'remaining_key' => 'rate_limit_remaining', - 'limit_key' => 'rate_limit_limit', - 'reset_key' => 'rate_limit_reset', - ), array( 'label' => __( 'User 24-Hour Limit:', 'autoshare-for-twitter' ), 'remaining_key' => 'user_limit_24hour_remaining', From 4efbf688b305886d9a2d419febaff440e0244d24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 11:01:16 +0000 Subject: [PATCH 08/14] Only show note once and remove "global" rate limit from the block editor panel --- src/js/components/TwitterAccounts.js | 103 ++++++++++++--------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index bf3228f7..41baceb3 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -70,7 +70,20 @@ function TwitterAccount( props ) { className="autoshare-for-twitter-account-toggle" />

    - +
    + +
    +

    + + { __( 'Note:', 'autoshare-for-twitter' ) } + { ' ' } + { __( + 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', + 'autoshare-for-twitter' + ) } +

    +
    +
    ); } @@ -84,68 +97,44 @@ function TwitterAccount( props ) { */ function TwitterAccountRateLimits( { rate_limits: rateLimits } ) { return ( -
    -
    - { rateLimits ? ( - <> - - - - - ) : ( -

    - { __( - 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', +

    + { rateLimits ? ( + <> + - ) } -
    -
    -

    - { __( 'Note:', 'autoshare-for-twitter' ) }{ ' ' } + tooltip={ __( + 'The maximum number of requests a single user can make across all API endpoints within a 24-hour period.', + 'autoshare-for-twitter' + ) } + remaining={ rateLimits.user_limit_24hour_remaining } + limit={ rateLimits.user_limit_24hour_limit } + reset={ rateLimits.user_limit_24hour_reset } + /> + + + ) : ( +

    { __( - 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', + 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) }

    -
    + ) }
    ); } From d96163dbce46231e1f55b4c28436268d7959aa6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 11:02:28 +0000 Subject: [PATCH 09/14] Update copy --- includes/core.php | 2 +- src/js/components/TwitterAccounts.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/includes/core.php b/includes/core.php index 318548d3..0a9d1585 100644 --- a/includes/core.php +++ b/includes/core.php @@ -364,7 +364,7 @@ function ( $row ) use ( $rate_limits ) { } else { $account_markup = sprintf( '

    %s

    ', - esc_html__( 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) + esc_html__( 'No X/Twitter rate limit available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) ); } diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index 41baceb3..c003d363 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -130,7 +130,7 @@ function TwitterAccountRateLimits( { rate_limits: rateLimits } ) { ) : (

    { __( - 'No X/Twitter rate data available yet. Make a post to X/Twitter first.', + 'No X/Twitter rate limit available yet. Make a post to X/Twitter first.', 'autoshare-for-twitter' ) }

    From b8e19787a9043bfccc13a25824e97cf519f90ee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 11:15:00 +0000 Subject: [PATCH 10/14] Only show note and rate limits when an account is toggled --- src/js/components/TwitterAccounts.js | 29 +++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/js/components/TwitterAccounts.js b/src/js/components/TwitterAccounts.js index c003d363..c3137c28 100644 --- a/src/js/components/TwitterAccounts.js +++ b/src/js/components/TwitterAccounts.js @@ -15,12 +15,26 @@ export function TwitterAccounts() { const accounts = connectedAccounts ? Object.values( connectedAccounts ) : []; + const [ tweetAccounts ] = useTweetAccounts(); return (
    { accounts.map( ( account ) => ( ) ) } + { tweetAccounts?.length > 0 && ( +
    +

    + + { __( 'Note:', 'autoshare-for-twitter' ) } + { ' ' } + { __( + 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', + 'autoshare-for-twitter' + ) } +

    +
    + ) } { __( 'Connect an account', 'autoshare-for-twitter' ) } @@ -70,20 +84,9 @@ function TwitterAccount( props ) { className="autoshare-for-twitter-account-toggle" />
    -
    + { tweetAccounts && tweetAccounts.includes( id ) && ( -
    -

    - - { __( 'Note:', 'autoshare-for-twitter' ) } - { ' ' } - { __( - 'The displayed API rate limits are updated only when a tweet is posted. Since there is no dedicated endpoint for real-time usage data, the information provided may not fully reflect the current API usage, especially if other tweets are made through the same app.', - 'autoshare-for-twitter' - ) } -

    -
    -
    + ) } ); } From 2f3f8315be9add064f37bf151355fb48f28979df Mon Sep 17 00:00:00 2001 From: Dharmesh Patel Date: Tue, 17 Dec 2024 18:01:26 +0530 Subject: [PATCH 11/14] Remove rate limit description from foot note. --- assets/css/admin-autoshare-for-twitter.css | 7 ++++++- includes/core.php | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/assets/css/admin-autoshare-for-twitter.css b/assets/css/admin-autoshare-for-twitter.css index 94b479db..f3b085e6 100644 --- a/assets/css/admin-autoshare-for-twitter.css +++ b/assets/css/admin-autoshare-for-twitter.css @@ -268,12 +268,13 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { #autopost_for_x_rate_monitor_dashboard_widget .inside { padding: 0; + margin-top: 0px; } .autoshare-for-twitter-rate-monitor__account { align-items: center; display: flex; - padding: 0 12px 12px; + padding: 12px 12px; } .autoshare-for-twitter-rate-monitor__account img { @@ -326,4 +327,8 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { .autoshare-for-twitter-accounts-wrapper .autoshare-for-twitter-rate-monitor__rates { border-bottom: 0; padding: 0; +} + +.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__footer { + margin-bottom: 8px; } \ No newline at end of file diff --git a/includes/core.php b/includes/core.php index 0a9d1585..defc843d 100644 --- a/includes/core.php +++ b/includes/core.php @@ -384,7 +384,6 @@ function ( $row ) use ( $rate_limits ) { } $footnotes = array( - __( 'Rate Limit: The maximum number of API calls allowed within a 15-minute window for the current app.', 'autoshare-for-twitter' ), __( 'User 24-Hour Limit: The maximum number of requests a single user can make across all API endpoints within a 24-hour period.', 'autoshare-for-twitter' ), __( 'App 24-Hour Limit: The total number of API calls your app can make across all users within a 24-hour period.', 'autoshare-for-twitter' ), ); From 65d8bb0411ace405507e168378635dac0d80a147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 17:23:50 +0000 Subject: [PATCH 12/14] Update global rate limits on all accounts --- includes/core.php | 75 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/includes/core.php b/includes/core.php index 0a9d1585..68ac31e7 100644 --- a/includes/core.php +++ b/includes/core.php @@ -247,34 +247,46 @@ function update_account_rate_limits( $response, $update_data, $post, $account_id return; } - /** - * Map the headers from the last request to internal keys. - */ - $map = array( - 'rate_limit_limit' => 'x_rate_limit_limit', - 'rate_limit_reset' => 'x_rate_limit_reset', - 'rate_limit_remaining' => 'x_rate_limit_remaining', - 'app_limit_24hour_limit' => 'x_app_limit_24hour_limit', - 'app_limit_24hour_reset' => 'x_app_limit_24hour_reset', - 'app_limit_24hour_remaining' => 'x_app_limit_24hour_remaining', - 'user_limit_24hour_limit' => 'x_user_limit_24hour_limit', - 'user_limit_24hour_reset' => 'x_user_limit_24hour_reset', - 'user_limit_24hour_remaining' => 'x_user_limit_24hour_remaining', + $rate_limits = parse_last_headers( + $last_headers, + array( + 'rate_limit_limit' => 'x_rate_limit_limit', + 'rate_limit_reset' => 'x_rate_limit_reset', + 'rate_limit_remaining' => 'x_rate_limit_remaining', + ) ); - $account_rate_limits = array(); + $app_rate_limits = parse_last_headers( + $last_headers, + array( + 'app_limit_24hour_limit' => 'x_app_limit_24hour_limit', + 'app_limit_24hour_reset' => 'x_app_limit_24hour_reset', + 'app_limit_24hour_remaining' => 'x_app_limit_24hour_remaining', + ) + ); - foreach ( $map as $key => $header ) { + $user_rate_limits = parse_last_headers( + $last_headers, + array( + 'user_limit_24hour_limit' => 'x_user_limit_24hour_limit', + 'user_limit_24hour_reset' => 'x_user_limit_24hour_reset', + 'user_limit_24hour_remaining' => 'x_user_limit_24hour_remaining', + ) + ); - if ( ! isset( $last_headers[ $header ] ) ) { - continue; + foreach ( $accounts as $key => $account ) { + + // Update the "global" and app rate limits on all accounts. + $account_rate_limits = array_merge( $rate_limits, $app_rate_limits ); + + // Update the user rate limits on the account that made the request. + if ( $account['id'] === $account_id ) { + $account_rate_limits = array_merge( $user_rate_limits, $account_rate_limits ); } - $account_rate_limits[ $key ] = sanitize_text_field( $last_headers[ $header ] ); + $accounts[ $key ]['rate_limits'] = $account_rate_limits; } - $accounts[ $account_id ]['rate_limits'] = $account_rate_limits; - update_option( 'autoshare_for_twitter_accounts', $accounts ); } @@ -440,3 +452,26 @@ function human_readable_time( $timestamp, $date_format = '' ) { return $human_readable_time; } + +/** + * Parse the last headers from the Twitter API response. + * + * @param array $last_headers The headers from the last request. + * @param array $map The map of headers to internal keys. + * @return array + */ +function parse_last_headers( $last_headers, $map ) { + + $parsed = array(); + + foreach ( $map as $key => $header ) { + + if ( ! isset( $last_headers[ $header ] ) ) { + continue; + } + + $parsed[ $key ] = sanitize_text_field( $last_headers[ $header ] ); + } + + return $parsed; +} From eddffdcf32354b8ba2c9c69b831c49e8379d612c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Se=CC=81rgio=20Santos?= Date: Tue, 17 Dec 2024 23:42:51 +0000 Subject: [PATCH 13/14] Display app limits outside the user limits --- assets/css/admin-autoshare-for-twitter.css | 58 ++++--- includes/core.php | 183 +++++++++++++-------- src/js/components/TwitterAccounts.js | 108 ++++++------ 3 files changed, 207 insertions(+), 142 deletions(-) diff --git a/assets/css/admin-autoshare-for-twitter.css b/assets/css/admin-autoshare-for-twitter.css index f3b085e6..00ae27e2 100644 --- a/assets/css/admin-autoshare-for-twitter.css +++ b/assets/css/admin-autoshare-for-twitter.css @@ -271,30 +271,27 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { margin-top: 0px; } -.autoshare-for-twitter-rate-monitor__account { +.autoshare-for-twitter-rate-monitor__users { + border-top: 1px solid #f0f0f1; +} + +.autoshare-for-twitter-rate-monitor__user { align-items: center; + border-bottom: 1px solid #f0f0f1; display: flex; - padding: 12px 12px; + padding: 12px; } -.autoshare-for-twitter-rate-monitor__account img { +.autoshare-for-twitter-rate-monitor__user img { border-radius: 50%; margin-right: 10px; max-width: 48px; } -#autopost_for_x_rate_monitor_dashboard_widget .autoshare-for-twitter-rate-monitor__account h3 { +#autopost_for_x_rate_monitor_dashboard_widget .autoshare-for-twitter-rate-monitor__user h3 { font-weight: bold; -} - -.autoshare-for-twitter-rate-monitor__rates { - border-bottom: 1px solid #f0f0f1; - font-size: 13px; - padding: 0 12px 12px; -} - -.autoshare-for-twitter-rate-monitor__rate { - margin-bottom: 8px; + margin-bottom: 0; + margin-right: 10px; } .autoshare-for-twitter-rate-monitor__rate p { @@ -302,33 +299,48 @@ tbody .autoshare-for-twitter-status-logo--disabled::before { margin-top: 0; } -.autoshare-for-twitter-rate-monitor__reset { +.autoshare-for-twitter-rate-monitor__rate-reset { font-size: 12px; font-style: italic; } -.autoshare-for-twitter-rate-monitor__footer { - color: #50575e; +.autoshare-for-twitter-rate-monitor__app { + align-items: center; + display: flex; + padding: 12px; +} + +.autoshare-for-twitter-rate-monitor__disclaimer { background: #f6f7f7; - border-top: 1px solid #f0f0f1; + color: #50575e; padding: 12px; } -.autoshare-for-twitter-rate-monitor__footer ul { +.autoshare-for-twitter-rate-monitor__disclaimer ul { margin-bottom: 0; margin-top: 0; } -.autoshare-for-twitter-rate-monitor__footer p { +.autoshare-for-twitter-rate-monitor__disclaimer p { margin-bottom: 8px; margin-top: 0; } -.autoshare-for-twitter-accounts-wrapper .autoshare-for-twitter-rate-monitor__rates { +.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__user { border-bottom: 0; + margin-bottom: 16px; padding: 0; } -.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__footer { - margin-bottom: 8px; +.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__app { + padding: 0; + margin-bottom: 16px; +} + +.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__disclaimer { + margin-bottom: 16px; +} + +.autoshare-for-twitter-editor-panel .autoshare-for-twitter-rate-monitor__disclaimer p { + margin-bottom: 0; } \ No newline at end of file diff --git a/includes/core.php b/includes/core.php index fd4fa6df..8f3703a9 100644 --- a/includes/core.php +++ b/includes/core.php @@ -226,10 +226,10 @@ function handle_notice_dismiss() { } /** - * Update the account rate limits from the last Twitter API request. + * Update the account rate limits from the last X/Twitter API request. * - * @param object $response The response from the Twitter endpoint. - * @param array $update_data Data to send to the Twitter endpoint. + * @param object $response The response from the X/Twitter endpoint. + * @param array $update_data Data to send to the X/Twitter endpoint. * @param \WP_Post $post The post associated with the tweet. * @param string $account_id The account ID associated with the tweet. * @param array|null $last_headers The headers from the last request. @@ -320,59 +320,19 @@ function display_rate_monitor_dashboard_widget() { return; } - $rows = array( - array( - 'label' => __( 'User 24-Hour Limit:', 'autoshare-for-twitter' ), - 'remaining_key' => 'user_limit_24hour_remaining', - 'limit_key' => 'user_limit_24hour_limit', - 'reset_key' => 'user_limit_24hour_reset', - ), - array( - 'label' => __( 'App 24-Hour Limit:', 'autoshare-for-twitter' ), - 'remaining_key' => 'app_limit_24hour_remaining', - 'limit_key' => 'app_limit_24hour_limit', - 'reset_key' => 'app_limit_24hour_reset', - ), - ); - - $all_accounts_markup = ''; + $app_rate_limits_markup = ''; + $users_rate_limits_markup = ''; foreach ( $accounts as $account ) { $account_markup = ''; if ( ! empty( $account['rate_limits'] ) ) { - $rate_limits = $account['rate_limits']; - - $account_markup = array_map( - function ( $row ) use ( $rate_limits ) { - $remaining = isset( $rate_limits[ $row['remaining_key'] ] ) ? (int) $rate_limits[ $row['remaining_key'] ] : __( 'N/A', 'autoshare-for-twitter' ); - $limit = isset( $rate_limits[ $row['limit_key'] ] ) ? (int) $rate_limits[ $row['limit_key'] ] : __( 'N/A', 'autoshare-for-twitter' ); - $reset = isset( $rate_limits[ $row['reset_key'] ] ) ? human_readable_time( $rate_limits[ $row['reset_key'] ] ) : esc_html__( 'N/A', 'autoshare-for-twitter' ); - - return sprintf( - '
    -

    %1$s %2$s

    -

    %3$s

    -
    ', - esc_html( $row['label'] ), - sprintf( - /* translators: %1$s: Remaining, %2$s: Limit */ - esc_html__( '%1$s of %2$s', 'autoshare-for-twitter' ), - esc_html( $remaining ), - esc_html( $limit ) - ), - sprintf( - /* translators: %1$s: Reset time */ - esc_html__( 'Resets on %1$s', 'autoshare-for-twitter' ), - esc_html( $reset ) - ) - ); - }, - $rows - ); + $account_markup = get_user_rate_limits_markup( $account['rate_limits'] ); - $account_markup = implode( ' ', $account_markup ); + if ( empty( $app_rate_limits_markup ) ) { // We only need to display the app rate limits once. + $app_rate_limits_markup = get_app_rate_limits_markup( $account['rate_limits'] ); + } } else { $account_markup = sprintf( '

    %s

    ', @@ -380,12 +340,10 @@ function ( $row ) use ( $rate_limits ) { ); } - $all_accounts_markup .= sprintf( - '