Skip to content

Commit 77ec2c2

Browse files
committed
[proxy/retry]: init
1 parent 7f58636 commit 77ec2c2

File tree

9 files changed

+543
-76
lines changed

9 files changed

+543
-76
lines changed

app/bin/lerama

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ require_once __DIR__ . '/../vendor/autoload.php';
77

88
use League\CLImate\CLImate;
99
use Lerama\Commands\FeedProcessor;
10+
use Lerama\Services\ProxyService;
11+
use Lerama\Services\EmailService;
1012

1113
$dotenv = Dotenv\Dotenv::createImmutable(__DIR__ . '/..');
1214
$dotenv->load();
@@ -46,6 +48,23 @@ switch ($command) {
4648

4749
$processor->process((int)$feedId);
4850
break;
51+
52+
case 'feed:check-status':
53+
// Check and update status of paused feeds
54+
$processor->checkPausedFeeds();
55+
break;
56+
57+
case 'proxy:update':
58+
// Update proxy list
59+
$climate->info("Updating proxy list...");
60+
$proxyService = new ProxyService();
61+
if ($proxyService->fetchProxyList()) {
62+
$climate->green("Proxy list updated successfully");
63+
} else {
64+
$climate->red("Failed to update proxy list");
65+
exit(1);
66+
}
67+
break;
4968

5069
default:
5170
$climate->error("Unknown command: {$command}");
@@ -58,4 +77,6 @@ function showUsage(CLImate $climate): void
5877
$climate->out("Usage:");
5978
$climate->out(" php bin/lerama feed:process Process all feeds");
6079
$climate->out(" php bin/lerama feed:id {ID_DO_FEED} Process a specific feed by ID");
80+
$climate->out(" php bin/lerama feed:check-status Check and update status of paused feeds");
81+
$climate->out(" php bin/lerama proxy:update Update proxy list from PROXY_LIST URL");
6182
}

app/composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"laminas/laminas-diactoros": "^2.24",
1313
"laminas/laminas-httphandlerrunner": "^2.5",
1414
"league/plates": "^3.5",
15-
"guzzlehttp/guzzle": "^7.9"
15+
"guzzlehttp/guzzle": "^7.9",
16+
"phpmailer/phpmailer": "^6.8"
1617
},
1718
"autoload": {
1819
"psr-4": {

app/src/Commands/FeedProcessor.php

Lines changed: 217 additions & 73 deletions
Large diffs are not rendered by default.

app/src/Services/EmailService.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Lerama\Services;
5+
6+
use PHPMailer\PHPMailer\PHPMailer;
7+
use PHPMailer\PHPMailer\Exception;
8+
9+
class EmailService
10+
{
11+
private PHPMailer $mailer;
12+
private bool $enabled;
13+
private string $adminEmail;
14+
15+
public function __construct()
16+
{
17+
$this->enabled = !empty($_ENV['SMTP_HOST']) && !empty($_ENV['SMTP_PORT']);
18+
$this->adminEmail = $_ENV['ADMIN_EMAIL'];
19+
20+
if ($this->enabled) {
21+
$this->mailer = new PHPMailer(true);
22+
23+
$this->mailer->isSMTP();
24+
$this->mailer->Host = $_ENV['SMTP_HOST'];
25+
$this->mailer->Port = (int)$_ENV['SMTP_PORT'];
26+
$this->mailer->SMTPAuth = !empty($_ENV['SMTP_USERNAME']) && !empty($_ENV['SMTP_PASSWORD']);
27+
28+
if ($this->mailer->SMTPAuth) {
29+
$this->mailer->Username = $_ENV['SMTP_USERNAME'];
30+
$this->mailer->Password = $_ENV['SMTP_PASSWORD'];
31+
}
32+
33+
$this->mailer->SMTPSecure = $_ENV['SMTP_SECURE'] ?? PHPMailer::ENCRYPTION_STARTTLS;
34+
$this->mailer->setFrom($_ENV['SMTP_FROM_EMAIL'], $_ENV['SMTP_FROM_NAME']);
35+
}
36+
}
37+
38+
public function sendFeedOfflineNotification(array $feed): bool
39+
{
40+
if (!$this->enabled) {
41+
error_log("Feed marked as offline: {$feed['title']} ({$feed['feed_url']})");
42+
return false;
43+
}
44+
45+
try {
46+
$this->mailer->clearAddresses();
47+
$this->mailer->addAddress($this->adminEmail);
48+
$this->mailer->Subject = "Lerama: Feed offline: {$feed['title']}";
49+
50+
$body = "<h1>Feed marcado como offline</h1>";
51+
$body .= "<h2>Detalhes do feed</h2>";
52+
$body .= "<ul>";
53+
$body .= "<li><strong>Título:</strong> {$feed['title']}</li>";
54+
$body .= "<li><strong>URL:</strong> {$feed['feed_url']}</li>";
55+
$body .= "<li><strong>Tipo:</strong> {$feed['feed_type']}</li>";
56+
$body .= "<li><strong>Última verificação:</strong> {$feed['last_checked']}</li>";
57+
$body .= "</ul>";
58+
$body .= "<p>O feed foi pausado inicialmente em {$feed['paused_at']} e está inacessível há mais de 72 horas.</p>";
59+
60+
$this->mailer->isHTML(true);
61+
$this->mailer->Body = $body;
62+
$this->mailer->AltBody = strip_tags(str_replace(['<li>', '</li>'], ["\n- ", ''], $body));
63+
64+
return $this->mailer->send();
65+
} catch (Exception $e) {
66+
error_log("Error sending feed offline notification: " . $e->getMessage());
67+
return false;
68+
}
69+
}
70+
}

app/src/Services/ProxyService.php

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<?php
2+
declare(strict_types=1);
3+
4+
namespace Lerama\Services;
5+
6+
use GuzzleHttp\Client;
7+
use GuzzleHttp\Exception\GuzzleException;
8+
9+
class ProxyService
10+
{
11+
private array $proxyList = [];
12+
private string $cacheFile;
13+
private Client $httpClient;
14+
15+
public function __construct()
16+
{
17+
$this->cacheFile = __DIR__ . '/../../storage/proxy_list.cache';
18+
$this->httpClient = new Client([
19+
'timeout' => 10,
20+
'connect_timeout' => 5,
21+
'http_errors' => false
22+
]);
23+
24+
$storageDir = dirname($this->cacheFile);
25+
if (!is_dir($storageDir)) {
26+
mkdir($storageDir, 0755, true);
27+
}
28+
29+
$this->loadCachedProxyList();
30+
}
31+
32+
public function getRandomProxy(): ?array
33+
{
34+
if (empty($this->proxyList)) {
35+
$this->fetchProxyList();
36+
}
37+
38+
if (empty($this->proxyList)) {
39+
return null;
40+
}
41+
42+
return $this->proxyList[array_rand($this->proxyList)];
43+
}
44+
45+
public function fetchProxyList(): bool
46+
{
47+
$proxyListUrl = $_ENV['PROXY_LIST'] ?? null;
48+
49+
if (empty($proxyListUrl)) {
50+
return false;
51+
}
52+
53+
try {
54+
$response = $this->httpClient->get($proxyListUrl);
55+
56+
if ($response->getStatusCode() !== 200) {
57+
return false;
58+
}
59+
60+
$content = (string) $response->getBody();
61+
$lines = explode("\n", $content);
62+
$proxies = [];
63+
64+
foreach ($lines as $line) {
65+
$line = trim($line);
66+
if (empty($line)) {
67+
continue;
68+
}
69+
70+
$proxy = $this->parseProxyString($line);
71+
if ($proxy) {
72+
$proxies[] = $proxy;
73+
}
74+
}
75+
76+
if (!empty($proxies)) {
77+
$this->proxyList = $proxies;
78+
$this->saveCachedProxyList($proxies);
79+
return true;
80+
}
81+
82+
return false;
83+
} catch (GuzzleException $e) {
84+
error_log("Error fetching proxy list: " . $e->getMessage());
85+
return false;
86+
}
87+
}
88+
89+
public function loadCachedProxyList(): bool
90+
{
91+
if (!file_exists($this->cacheFile)) {
92+
return false;
93+
}
94+
95+
$content = file_get_contents($this->cacheFile);
96+
if ($content === false) {
97+
return false;
98+
}
99+
100+
$proxies = json_decode($content, true);
101+
if (json_last_error() !== JSON_ERROR_NONE || !is_array($proxies)) {
102+
return false;
103+
}
104+
105+
$this->proxyList = $proxies;
106+
return true;
107+
}
108+
109+
public function saveCachedProxyList(array $proxies): bool
110+
{
111+
$content = json_encode($proxies);
112+
if ($content === false) {
113+
return false;
114+
}
115+
116+
return file_put_contents($this->cacheFile, $content) !== false;
117+
}
118+
119+
public function parseProxyString(string $proxyString): ?array
120+
{
121+
if (preg_match('/^([^:]+):(\d+):([^:]+):(.+)$/', $proxyString, $matches)) {
122+
return [
123+
'host' => $matches[1],
124+
'port' => (int) $matches[2],
125+
'username' => $matches[3],
126+
'password' => $matches[4]
127+
];
128+
}
129+
130+
if (preg_match('/^([^@]+)@([^:]+):([^:]+):(\d+)$/', $proxyString, $matches)) {
131+
return [
132+
'host' => $matches[3],
133+
'port' => (int) $matches[4],
134+
'username' => $matches[1],
135+
'password' => $matches[2]
136+
];
137+
}
138+
139+
if (preg_match('/^([^:]+):(\d+)$/', $proxyString, $matches)) {
140+
return [
141+
'host' => $matches[1],
142+
'port' => (int) $matches[2],
143+
'username' => null,
144+
'password' => null
145+
];
146+
}
147+
148+
return null;
149+
}
150+
}

docker-compose.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,16 @@ services:
1212
MYSQL_PASSWORD: root
1313
ADMIN_USERNAME: admin
1414
ADMIN_PASSWORD: admin
15+
ADMIN_EMAIL:
1516
ADD_BLOG_LINK: https:// # optional
17+
PROXY_LIST: # optional
18+
SMTP_HOST=smtp.resend.com
19+
SMTP_PORT=587
20+
SMTP_USERNAME=resend
21+
SMTP_PASSWORD=re_
22+
SMTP_SECURE: tls
23+
SMTP_FROM_EMAIL: [email protected]
24+
SMTP_FROM_NAME: Lerama
1625
ports:
1726
- 8077:8077
1827
volumes:

docker-entrypoint.sh

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,15 @@ DB_PASS=${MYSQL_PASSWORD:-root}
9090
ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
9191
ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin}
9292
ADD_BLOG_LINK=${ADD_BLOG_LINK:-}
93+
PROXY_LIST=${PROXY_LIST:-}
94+
ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com}
95+
SMTP_HOST=${SMTP_HOST:-}
96+
SMTP_PORT=${SMTP_PORT:-587}
97+
SMTP_USERNAME=${SMTP_USERNAME:-}
98+
SMTP_PASSWORD=${SMTP_PASSWORD:-}
99+
SMTP_SECURE=${SMTP_SECURE:-tls}
100+
SMTP_FROM_EMAIL=${SMTP_FROM_EMAIL:-lerama@example.com}
101+
SMTP_FROM_NAME=${SMTP_FROM_NAME:-"Lerama Feed Aggregator"}
93102
EOL
94103

95104
log_success "Environment variables set in /app/.env"
@@ -137,18 +146,45 @@ echo "$(date): Starting feed:process cron job"
137146
echo "$(date): Finished feed:process cron job"
138147
EOL
139148

149+
cat > /app/cron-scripts/feed_check_status.sh << 'EOL'
150+
#!/bin/bash
151+
echo "$(date): Starting feed:check-status cron job"
152+
/usr/local/bin/php /app/bin/lerama feed:check-status 2>&1 | sed "s/^/[feed:check-status] /"
153+
echo "$(date): Finished feed:check-status cron job"
154+
EOL
155+
156+
cat > /app/cron-scripts/proxy_update.sh << 'EOL'
157+
#!/bin/bash
158+
echo "$(date): Starting proxy_update cron job"
159+
/usr/local/bin/php /app/bin/lerama proxy:update 2>&1 | sed "s/^/[proxy:update] /"
160+
echo "$(date): Finished proxy_update cron job"
161+
EOL
162+
140163
# Make the scripts executable
141164
chmod +x /app/cron-scripts/*.sh
142165

143166
# Set up crontab to use the wrapper scripts and redirect output to Docker logs
144167
(
145168
echo "0 * * * * /app/cron-scripts/feed_process.sh >> /proc/1/fd/1 2>> /proc/1/fd/2"
169+
echo "30 * * * * /app/cron-scripts/feed_check_status.sh >> /proc/1/fd/1 2>> /proc/1/fd/2"
170+
echo "0 0 * * * /app/cron-scripts/proxy_update.sh >> /proc/1/fd/1 2>> /proc/1/fd/2"
146171
) | crontab -
147172

148173
service cron restart
149174

150175
log_success "Cron jobs added with stdout logging"
151176

177+
# Update proxy list if PROXY_LIST is defined
178+
if [ -n "$PROXY_LIST" ]; then
179+
log_info "PROXY_LIST environment variable detected, updating proxy list..."
180+
php /app/bin/lerama proxy:update
181+
if [ $? -eq 0 ]; then
182+
log_success "Proxy list updated successfully"
183+
else
184+
log_info "Failed to update proxy list, will retry later"
185+
fi
186+
fi
187+
152188
# Set correct permissions for /app/storage
153189
log_info "Setting permissions for /app/public/storage..."
154190
chown -R www-data:www-data /app/public/storage
@@ -165,5 +201,4 @@ fi
165201

166202
echo -e "\n${GREEN}Lerama: Initialized ===${NC}\n"
167203

168-
wait -n
169-
204+
wait -n

0 commit comments

Comments
 (0)