Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,7 @@ API_KEY=your_api_key
API_SECRET_KEY=your_api_secret_key
ACCESS_TOKEN=your_access_token
ACCESS_TOKEN_SECRET=your_access_token_secret
TWITTER_BACKEND=twitter
XQUIK_API_KEY=your_xquik_api_key
XQUIK_ACCOUNT=your_x_account_username
XQUIK_API_BASE=https://xquik.com/api/v1
38 changes: 31 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,15 @@ composer install
```

### Configuration
#### Option 1: Using Environment Variables
#### Option 1: Using Shell Environment Variables

Rename the file .env.example to .env and update it with your Twitter API credentials:
Export your credentials before running the script:

```dotenv
API_KEY=your_api_key
API_SECRET_KEY=your_api_secret_key
ACCESS_TOKEN=your_access_token
ACCESS_TOKEN_SECRET=your_access_token_secret
```bash
export API_KEY=your_api_key
export API_SECRET_KEY=your_api_secret_key
export ACCESS_TOKEN=your_access_token
export ACCESS_TOKEN_SECRET=your_access_token_secret
```

#### Option 2: Using config.php
Expand All @@ -47,11 +47,35 @@ define('API_KEY', 'your_api_key');
define('API_SECRET_KEY', 'your_api_secret_key');
define('ACCESS_TOKEN', 'your_access_token');
define('ACCESS_TOKEN_SECRET', 'your_access_token_secret');
define('TWITTER_BACKEND', 'twitter');
define('XQUIK_API_KEY', 'your_xquik_api_key');
define('XQUIK_ACCOUNT', 'your_x_account_username');
define('XQUIK_API_BASE', 'https://xquik.com/api/v1');
?>
```

Environment variables override values from `config.php`.

Note: For security reasons, ensure that config.php and .env are added to your .gitignore file and not committed to version control.

### Optional: Posting with Xquik

Set `TWITTER_BACKEND` to `xquik` to post through Xquik:

```dotenv
TWITTER_BACKEND=xquik
XQUIK_API_KEY=your_xquik_api_key
XQUIK_ACCOUNT=your_x_account_username
XQUIK_API_BASE=https://xquik.com/api/v1
```

The script sends `account` and `text` to `POST /api/v1/x/tweets` with the
`x-api-key` header. The default `twitter` backend continues to use X API v2
with OAuth 1.0a.

Xquik is an independent third-party service. Not affiliated with X Corp.
"Twitter" and "X" are trademarks of X Corp.

### Customize Your Tweet

Edit the post_tweet.php script to customize the tweet content:
Expand Down
4 changes: 4 additions & 0 deletions config.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@
define('API_SECRET_KEY', 'YOUR_API_SECRET_KEY');
define('ACCESS_TOKEN', 'YOUR_ACCESS_TOKEN');
define('ACCESS_TOKEN_SECRET', 'YOUR_ACCESS_TOKEN_SECRET');
define('TWITTER_BACKEND', 'twitter');
define('XQUIK_API_KEY', 'YOUR_XQUIK_API_KEY');
define('XQUIK_ACCOUNT', 'YOUR_X_ACCOUNT_USERNAME');
define('XQUIK_API_BASE', 'https://xquik.com/api/v1');
?>
160 changes: 138 additions & 22 deletions post_tweet.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,24 @@
error_reporting(E_ALL);
ini_set('display_errors', getenv('APP_DEBUG') ? '1' : '0');

// Twitter API credentials
if (!file_exists(__DIR__ . '/config.php')) {
fwrite(STDERR, "Error: config.php not found. Copy .env.example values into config.php.\n");
// Optional file-based credentials. Environment variables override these values.
if (file_exists(__DIR__ . '/config.php')) {
require_once __DIR__ . '/config.php';
}

$backend = strtolower(trim(getConfigValue('TWITTER_BACKEND', 'twitter')));
if ($backend !== 'twitter' && $backend !== 'xquik') {
fwrite(STDERR, "Error: TWITTER_BACKEND must be twitter or xquik.\n");
exit(1);
}
require_once __DIR__ . '/config.php';

// Validate that credentials have been configured
$credentials = [
'API_KEY' => API_KEY,
'API_SECRET_KEY' => API_SECRET_KEY,
'ACCESS_TOKEN' => ACCESS_TOKEN,
'ACCESS_TOKEN_SECRET' => ACCESS_TOKEN_SECRET,
];
foreach ($credentials as $name => $value) {
if (empty($value) || strpos($value, 'YOUR_') === 0) {
echo "Error: $name is not configured. Please update config.php with your actual credentials.\n";
exit(1);
}

if ($backend === 'twitter') {
validateCredentials([
'API_KEY' => getConfigValue('API_KEY'),
'API_SECRET_KEY' => getConfigValue('API_SECRET_KEY'),
'ACCESS_TOKEN' => getConfigValue('ACCESS_TOKEN'),
'ACCESS_TOKEN_SECRET' => getConfigValue('ACCESS_TOKEN_SECRET'),
]);
}

// The tweet content variables
Expand Down Expand Up @@ -54,16 +53,21 @@
*/
}

if ($backend === 'xquik') {
postTweetWithXquik($fullTweet);
exit(0);
}

// Twitter API endpoint for creating a tweet (API v2)
$url = 'https://api.twitter.com/2/tweets';

// OAuth parameters
$oauth = [
'oauth_consumer_key' => API_KEY,
'oauth_consumer_key' => getConfigValue('API_KEY'),
'oauth_nonce' => bin2hex(random_bytes(16)),
'oauth_signature_method' => 'HMAC-SHA1',
'oauth_timestamp' => time(),
'oauth_token' => ACCESS_TOKEN,
'oauth_token' => getConfigValue('ACCESS_TOKEN'),
'oauth_version' => '1.0',
];

Expand All @@ -79,7 +83,10 @@
$base_info = buildBaseString($url, 'POST', $signature_params);

// Generate the composite signing key
$composite_key = rawurlencode(API_SECRET_KEY) . '&' . rawurlencode(ACCESS_TOKEN_SECRET);
$composite_key =
rawurlencode(getConfigValue('API_SECRET_KEY')) .
'&' .
rawurlencode(getConfigValue('ACCESS_TOKEN_SECRET'));

// Generate the OAuth signature and add it to the OAuth parameters
$oauth_signature = base64_encode(hash_hmac('sha1', $base_info, $composite_key, true));
Expand Down Expand Up @@ -107,8 +114,10 @@
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_err = $response === false ? curl_error($ch) : null;

// Close cURL
curl_close($ch);
// PHP 8 releases cURL handles automatically. PHP 8.5 deprecates curl_close().
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}

if ($response === false) {
$error_message = "Failed to post tweet. cURL error: $curl_err\n";
Expand Down Expand Up @@ -148,6 +157,113 @@

echo $error_message;
logTwitterResponse($http_status, $error_message);
exit(1);
}

/**
* Reads a config value from the environment or config.php.
*
* @param string $name
* @param string $default
* @return string
*/
function getConfigValue($name, $default = '')
{
$environmentValue = getenv($name);
if ($environmentValue !== false && $environmentValue !== '') {
return $environmentValue;
}

return defined($name) ? (string) constant($name) : $default;
}

/**
* Validates required credentials.
*
* @param array $credentials
* @return void
*/
function validateCredentials($credentials)
{
foreach ($credentials as $name => $value) {
if (empty($value) || stripos($value, 'your_') === 0) {
fwrite(STDERR, "Error: $name is not configured.\n");
exit(1);
}
}
}

/**
* Posts a tweet through Xquik's REST API.
*
* @param string $fullTweet
* @return void
*/
function postTweetWithXquik($fullTweet)
{
$apiKey = getConfigValue('XQUIK_API_KEY');
$account = getConfigValue('XQUIK_ACCOUNT');
validateCredentials([
'XQUIK_API_KEY' => $apiKey,
'XQUIK_ACCOUNT' => $account,
]);

$apiBase = rtrim(getConfigValue('XQUIK_API_BASE', 'https://xquik.com/api/v1'), '/');
$postfields = json_encode([
'account' => $account,
'text' => $fullTweet,
], JSON_UNESCAPED_UNICODE);
if ($postfields === false) {
fwrite(STDERR, "Error: Could not encode the Xquik request.\n");
exit(1);
}

$ch = curl_init();
if ($ch === false) {
fwrite(STDERR, "Error: Could not initialize cURL.\n");
exit(1);
}

curl_setopt_array($ch, [
CURLOPT_URL => $apiBase . '/x/tweets',
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Content-Type: application/json',
'x-api-key: ' . $apiKey,
],
CURLOPT_POSTFIELDS => $postfields,
CURLOPT_RETURNTRANSFER => true,
]);

$response = curl_exec($ch);
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_err = $response === false ? curl_error($ch) : null;
if (PHP_VERSION_ID < 80000) {
curl_close($ch);
}

if ($response === false) {
$error_message = "Failed to post tweet with Xquik. cURL error: $curl_err\n";
fwrite(STDERR, $error_message);
logTwitterResponse($http_status, $error_message);
exit(1);
}

$response_data = json_decode($response, true);
if ($http_status >= 200 && $http_status < 300 && isset($response_data['tweetId'])) {
$success_message = "Tweet posted successfully with Xquik! Tweet ID: " . $response_data['tweetId'];
echo $success_message;
logTwitterResponse($http_status, $success_message);
return;
}

$error_message = "Failed to post tweet with Xquik. HTTP Status Code: $http_status\n";
if (is_array($response_data) && isset($response_data['message'])) {
$error_message .= "Message: " . $response_data['message'] . "\n";
}
fwrite(STDERR, $error_message);
logTwitterResponse($http_status, $error_message);
exit(1);
}

/**
Expand Down