Skip to content

Commit e2de52c

Browse files
committed
Add optional Xquik posting backend
1 parent f449780 commit e2de52c

4 files changed

Lines changed: 177 additions & 29 deletions

File tree

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ API_KEY=your_api_key
22
API_SECRET_KEY=your_api_secret_key
33
ACCESS_TOKEN=your_access_token
44
ACCESS_TOKEN_SECRET=your_access_token_secret
5+
TWITTER_BACKEND=twitter
6+
XQUIK_API_KEY=your_xquik_api_key
7+
XQUIK_ACCOUNT=your_x_account_username
8+
XQUIK_API_BASE=https://xquik.com/api/v1

README.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,15 @@ composer install
2626
```
2727

2828
### Configuration
29-
#### Option 1: Using Environment Variables
29+
#### Option 1: Using Shell Environment Variables
3030

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

33-
```dotenv
34-
API_KEY=your_api_key
35-
API_SECRET_KEY=your_api_secret_key
36-
ACCESS_TOKEN=your_access_token
37-
ACCESS_TOKEN_SECRET=your_access_token_secret
33+
```bash
34+
export API_KEY=your_api_key
35+
export API_SECRET_KEY=your_api_secret_key
36+
export ACCESS_TOKEN=your_access_token
37+
export ACCESS_TOKEN_SECRET=your_access_token_secret
3838
```
3939

4040
#### Option 2: Using config.php
@@ -47,11 +47,35 @@ define('API_KEY', 'your_api_key');
4747
define('API_SECRET_KEY', 'your_api_secret_key');
4848
define('ACCESS_TOKEN', 'your_access_token');
4949
define('ACCESS_TOKEN_SECRET', 'your_access_token_secret');
50+
define('TWITTER_BACKEND', 'twitter');
51+
define('XQUIK_API_KEY', 'your_xquik_api_key');
52+
define('XQUIK_ACCOUNT', 'your_x_account_username');
53+
define('XQUIK_API_BASE', 'https://xquik.com/api/v1');
5054
?>
5155
```
5256

57+
Environment variables override values from `config.php`.
58+
5359
Note: For security reasons, ensure that config.php and .env are added to your .gitignore file and not committed to version control.
5460

61+
### Optional: Posting with Xquik
62+
63+
Set `TWITTER_BACKEND` to `xquik` to post through Xquik:
64+
65+
```dotenv
66+
TWITTER_BACKEND=xquik
67+
XQUIK_API_KEY=your_xquik_api_key
68+
XQUIK_ACCOUNT=your_x_account_username
69+
XQUIK_API_BASE=https://xquik.com/api/v1
70+
```
71+
72+
The script sends `account` and `text` to `POST /api/v1/x/tweets` with the
73+
`x-api-key` header. The default `twitter` backend continues to use X API v2
74+
with OAuth 1.0a.
75+
76+
Xquik is an independent third-party service. Not affiliated with X Corp.
77+
"Twitter" and "X" are trademarks of X Corp.
78+
5579
### Customize Your Tweet
5680

5781
Edit the post_tweet.php script to customize the tweet content:

config.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,8 @@
33
define('API_SECRET_KEY', 'YOUR_API_SECRET_KEY');
44
define('ACCESS_TOKEN', 'YOUR_ACCESS_TOKEN');
55
define('ACCESS_TOKEN_SECRET', 'YOUR_ACCESS_TOKEN_SECRET');
6+
define('TWITTER_BACKEND', 'twitter');
7+
define('XQUIK_API_KEY', 'YOUR_XQUIK_API_KEY');
8+
define('XQUIK_ACCOUNT', 'YOUR_X_ACCOUNT_USERNAME');
9+
define('XQUIK_API_BASE', 'https://xquik.com/api/v1');
610
?>

post_tweet.php

Lines changed: 138 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,24 @@
66
error_reporting(E_ALL);
77
ini_set('display_errors', getenv('APP_DEBUG') ? '1' : '0');
88

9-
// Twitter API credentials
10-
if (!file_exists(__DIR__ . '/config.php')) {
11-
fwrite(STDERR, "Error: config.php not found. Copy .env.example values into config.php.\n");
9+
// Optional file-based credentials. Environment variables override these values.
10+
if (file_exists(__DIR__ . '/config.php')) {
11+
require_once __DIR__ . '/config.php';
12+
}
13+
14+
$backend = strtolower(trim(getConfigValue('TWITTER_BACKEND', 'twitter')));
15+
if ($backend !== 'twitter' && $backend !== 'xquik') {
16+
fwrite(STDERR, "Error: TWITTER_BACKEND must be twitter or xquik.\n");
1217
exit(1);
1318
}
14-
require_once __DIR__ . '/config.php';
15-
16-
// Validate that credentials have been configured
17-
$credentials = [
18-
'API_KEY' => API_KEY,
19-
'API_SECRET_KEY' => API_SECRET_KEY,
20-
'ACCESS_TOKEN' => ACCESS_TOKEN,
21-
'ACCESS_TOKEN_SECRET' => ACCESS_TOKEN_SECRET,
22-
];
23-
foreach ($credentials as $name => $value) {
24-
if (empty($value) || strpos($value, 'YOUR_') === 0) {
25-
echo "Error: $name is not configured. Please update config.php with your actual credentials.\n";
26-
exit(1);
27-
}
19+
20+
if ($backend === 'twitter') {
21+
validateCredentials([
22+
'API_KEY' => getConfigValue('API_KEY'),
23+
'API_SECRET_KEY' => getConfigValue('API_SECRET_KEY'),
24+
'ACCESS_TOKEN' => getConfigValue('ACCESS_TOKEN'),
25+
'ACCESS_TOKEN_SECRET' => getConfigValue('ACCESS_TOKEN_SECRET'),
26+
]);
2827
}
2928

3029
// The tweet content variables
@@ -54,16 +53,21 @@
5453
*/
5554
}
5655

56+
if ($backend === 'xquik') {
57+
postTweetWithXquik($fullTweet);
58+
exit(0);
59+
}
60+
5761
// Twitter API endpoint for creating a tweet (API v2)
5862
$url = 'https://api.twitter.com/2/tweets';
5963

6064
// OAuth parameters
6165
$oauth = [
62-
'oauth_consumer_key' => API_KEY,
66+
'oauth_consumer_key' => getConfigValue('API_KEY'),
6367
'oauth_nonce' => bin2hex(random_bytes(16)),
6468
'oauth_signature_method' => 'HMAC-SHA1',
6569
'oauth_timestamp' => time(),
66-
'oauth_token' => ACCESS_TOKEN,
70+
'oauth_token' => getConfigValue('ACCESS_TOKEN'),
6771
'oauth_version' => '1.0',
6872
];
6973

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

8185
// Generate the composite signing key
82-
$composite_key = rawurlencode(API_SECRET_KEY) . '&' . rawurlencode(ACCESS_TOKEN_SECRET);
86+
$composite_key =
87+
rawurlencode(getConfigValue('API_SECRET_KEY')) .
88+
'&' .
89+
rawurlencode(getConfigValue('ACCESS_TOKEN_SECRET'));
8390

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

110-
// Close cURL
111-
curl_close($ch);
117+
// PHP 8 releases cURL handles automatically. PHP 8.5 deprecates curl_close().
118+
if (PHP_VERSION_ID < 80000) {
119+
curl_close($ch);
120+
}
112121

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

149158
echo $error_message;
150159
logTwitterResponse($http_status, $error_message);
160+
exit(1);
161+
}
162+
163+
/**
164+
* Reads a config value from the environment or config.php.
165+
*
166+
* @param string $name
167+
* @param string $default
168+
* @return string
169+
*/
170+
function getConfigValue($name, $default = '')
171+
{
172+
$environmentValue = getenv($name);
173+
if ($environmentValue !== false && $environmentValue !== '') {
174+
return $environmentValue;
175+
}
176+
177+
return defined($name) ? (string) constant($name) : $default;
178+
}
179+
180+
/**
181+
* Validates required credentials.
182+
*
183+
* @param array $credentials
184+
* @return void
185+
*/
186+
function validateCredentials($credentials)
187+
{
188+
foreach ($credentials as $name => $value) {
189+
if (empty($value) || stripos($value, 'your_') === 0) {
190+
fwrite(STDERR, "Error: $name is not configured.\n");
191+
exit(1);
192+
}
193+
}
194+
}
195+
196+
/**
197+
* Posts a tweet through Xquik's REST API.
198+
*
199+
* @param string $fullTweet
200+
* @return void
201+
*/
202+
function postTweetWithXquik($fullTweet)
203+
{
204+
$apiKey = getConfigValue('XQUIK_API_KEY');
205+
$account = getConfigValue('XQUIK_ACCOUNT');
206+
validateCredentials([
207+
'XQUIK_API_KEY' => $apiKey,
208+
'XQUIK_ACCOUNT' => $account,
209+
]);
210+
211+
$apiBase = rtrim(getConfigValue('XQUIK_API_BASE', 'https://xquik.com/api/v1'), '/');
212+
$postfields = json_encode([
213+
'account' => $account,
214+
'text' => $fullTweet,
215+
], JSON_UNESCAPED_UNICODE);
216+
if ($postfields === false) {
217+
fwrite(STDERR, "Error: Could not encode the Xquik request.\n");
218+
exit(1);
219+
}
220+
221+
$ch = curl_init();
222+
if ($ch === false) {
223+
fwrite(STDERR, "Error: Could not initialize cURL.\n");
224+
exit(1);
225+
}
226+
227+
curl_setopt_array($ch, [
228+
CURLOPT_URL => $apiBase . '/x/tweets',
229+
CURLOPT_POST => true,
230+
CURLOPT_HTTPHEADER => [
231+
'Content-Type: application/json',
232+
'x-api-key: ' . $apiKey,
233+
],
234+
CURLOPT_POSTFIELDS => $postfields,
235+
CURLOPT_RETURNTRANSFER => true,
236+
]);
237+
238+
$response = curl_exec($ch);
239+
$http_status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
240+
$curl_err = $response === false ? curl_error($ch) : null;
241+
if (PHP_VERSION_ID < 80000) {
242+
curl_close($ch);
243+
}
244+
245+
if ($response === false) {
246+
$error_message = "Failed to post tweet with Xquik. cURL error: $curl_err\n";
247+
fwrite(STDERR, $error_message);
248+
logTwitterResponse($http_status, $error_message);
249+
exit(1);
250+
}
251+
252+
$response_data = json_decode($response, true);
253+
if ($http_status >= 200 && $http_status < 300 && isset($response_data['tweetId'])) {
254+
$success_message = "Tweet posted successfully with Xquik! Tweet ID: " . $response_data['tweetId'];
255+
echo $success_message;
256+
logTwitterResponse($http_status, $success_message);
257+
return;
258+
}
259+
260+
$error_message = "Failed to post tweet with Xquik. HTTP Status Code: $http_status\n";
261+
if (is_array($response_data) && isset($response_data['message'])) {
262+
$error_message .= "Message: " . $response_data['message'] . "\n";
263+
}
264+
fwrite(STDERR, $error_message);
265+
logTwitterResponse($http_status, $error_message);
266+
exit(1);
151267
}
152268

153269
/**

0 commit comments

Comments
 (0)