628 lines
25 KiB
PHP
628 lines
25 KiB
PHP
|
|
<?php
|
|||
|
|
/**
|
|||
|
|
* 文章页发布日温度、天气、地点
|
|||
|
|
*
|
|||
|
|
* @package ArticleWeather
|
|||
|
|
* @author 石头厝
|
|||
|
|
* @version 22.1
|
|||
|
|
* @link https://www.shitoucuo.com/
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
// 防止直接访问
|
|||
|
|
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
|
|||
|
|
|
|||
|
|
class ArticleWeather_Plugin implements Typecho_Plugin_Interface
|
|||
|
|
{
|
|||
|
|
private static $tableName = 'article_weather';
|
|||
|
|
private static $pendingWeatherData = array();
|
|||
|
|
|
|||
|
|
public static function activate()
|
|||
|
|
{
|
|||
|
|
self::createTable();
|
|||
|
|
|
|||
|
|
// 文章编辑相关钩子
|
|||
|
|
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->write = array(__CLASS__, 'onWrite');
|
|||
|
|
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->finishPublish = array(__CLASS__, 'finishSaveWeatherData');
|
|||
|
|
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->finishSave = array(__CLASS__, 'finishSaveWeatherData');
|
|||
|
|
|
|||
|
|
// 删除文章时同步删除天气数据
|
|||
|
|
Typecho_Plugin::factory('Widget_Contents_Post_Edit')->delete = array(__CLASS__, 'onDelete');
|
|||
|
|
|
|||
|
|
return '天气插件已激活!';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function deactivate()
|
|||
|
|
{
|
|||
|
|
return '天气插件已禁用';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function config(Typecho_Widget_Helper_Form $form)
|
|||
|
|
{
|
|||
|
|
echo '<h3>高德地图API配置</h3>';
|
|||
|
|
|
|||
|
|
$apiKey = new Typecho_Widget_Helper_Form_Element_Text('apiKey',
|
|||
|
|
NULL, '', '高德地图API_KEY',
|
|||
|
|
'');
|
|||
|
|
$form->addInput($apiKey->addRule('required', '必须填写API Key'));
|
|||
|
|
|
|||
|
|
// 添加测试按钮
|
|||
|
|
echo '<div style="margin: 15px 0;">
|
|||
|
|
<button type="button" id="test-api-btn" class="btn">测试API连接</button>
|
|||
|
|
<span id="test-result" style="margin-left: 10px;"></span>
|
|||
|
|
</div>';
|
|||
|
|
|
|||
|
|
echo '<script>
|
|||
|
|
document.getElementById("test-api-btn").addEventListener("click", function() {
|
|||
|
|
var apiKey = document.querySelector("input[name=\'apiKey\']").value;
|
|||
|
|
var testResult = document.getElementById("test-result");
|
|||
|
|
|
|||
|
|
if (!apiKey) {
|
|||
|
|
testResult.innerHTML = "<span style=\'color: red;\'>请先填写API Key</span>";
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
testResult.innerHTML = "<span style=\'color: blue;\'>测试中...</span>";
|
|||
|
|
|
|||
|
|
// 高德天气API测试
|
|||
|
|
var xhr = new XMLHttpRequest();
|
|||
|
|
xhr.open("GET", "https://restapi.amap.com/v3/weather/weatherInfo?key=" + encodeURIComponent(apiKey) + "&city=110000&extensions=base", true);
|
|||
|
|
xhr.onreadystatechange = function() {
|
|||
|
|
if (xhr.readyState === 4) {
|
|||
|
|
if (xhr.status === 200) {
|
|||
|
|
try {
|
|||
|
|
var data = JSON.parse(xhr.responseText);
|
|||
|
|
if (data.status === "1" && data.infocode === "10000") {
|
|||
|
|
testResult.innerHTML = "<span style=\'color: green;\'>✓ API连接成功!</span>";
|
|||
|
|
} else {
|
|||
|
|
var msg = data.info || "未知错误";
|
|||
|
|
var code = data.infocode || "未知";
|
|||
|
|
testResult.innerHTML = "<span style=\'color: red;\'>API错误: " + msg + " (" + code + ")</span>";
|
|||
|
|
}
|
|||
|
|
} catch(e) {
|
|||
|
|
testResult.innerHTML = "<span style=\'color: red;\'>响应解析失败</span>";
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
testResult.innerHTML = "<span style=\'color: red;\'>网络请求失败 (HTTP " + xhr.status + ")</span>";
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
xhr.send();
|
|||
|
|
});
|
|||
|
|
</script>';
|
|||
|
|
|
|||
|
|
echo '<div style="padding: 12px; border-radius: 6px; margin-top: 15px; border-left: 4px solid #1890ff;">
|
|||
|
|
<strong>📝 如何获取高德API Key:</strong>
|
|||
|
|
<ol style="margin: 8px 0; padding-left: 20px; font-size: 13px;">
|
|||
|
|
<li>访问 <a href="https://lbs.amap.com/" target="_blank" style="color: #0066cc;">高德开放平台</a></li>
|
|||
|
|
<li>注册并登录账号</li>
|
|||
|
|
<li>进入"控制台" → "应用管理" → "我的应用"</li>
|
|||
|
|
<li>创建新应用,选择"Web服务"类型</li>
|
|||
|
|
<li>添加Key,服务选择"Web服务"(不要选Web端)</li>
|
|||
|
|
<li>免费版每日调用限制:天气API 5000次/Key</li>
|
|||
|
|
<li><strong>注意:</strong>需要实名认证后才能使用</li>
|
|||
|
|
</ol>
|
|||
|
|
</div>';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public static function personalConfig(Typecho_Widget_Helper_Form $form) {}
|
|||
|
|
|
|||
|
|
private static function createTable()
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$prefix = $db->getPrefix();
|
|||
|
|
|
|||
|
|
// 检查表是否存在
|
|||
|
|
$result = $db->fetchRow($db->query("SHOW TABLES LIKE '{$prefix}article_weather'", Typecho_Db::READ));
|
|||
|
|
|
|||
|
|
if (!$result) {
|
|||
|
|
// 表不存在,创建新表
|
|||
|
|
$sql = "CREATE TABLE IF NOT EXISTS `{$prefix}article_weather` (
|
|||
|
|
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
|
|||
|
|
`cid` int(10) unsigned NOT NULL,
|
|||
|
|
`location` varchar(100) DEFAULT '',
|
|||
|
|
`display_location` varchar(100) DEFAULT '',
|
|||
|
|
`weather` varchar(50) DEFAULT '',
|
|||
|
|
`temperature` varchar(50) DEFAULT '',
|
|||
|
|
`created` int(10) unsigned DEFAULT '0',
|
|||
|
|
PRIMARY KEY (`id`),
|
|||
|
|
UNIQUE KEY `cid` (`cid`)
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8";
|
|||
|
|
$db->query($sql);
|
|||
|
|
} else {
|
|||
|
|
// 表已存在,检查是否已有display_location字段
|
|||
|
|
$result = $db->fetchRow($db->query("SHOW COLUMNS FROM `{$prefix}article_weather` LIKE 'display_location'", Typecho_Db::READ));
|
|||
|
|
if (!$result) {
|
|||
|
|
// 添加display_location字段
|
|||
|
|
$db->query("ALTER TABLE `{$prefix}article_weather` ADD COLUMN `display_location` varchar(100) DEFAULT '' AFTER `location`");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
error_log("ArticleWeather创建表失败: " . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 提供主题调用的添加字段方法
|
|||
|
|
*/
|
|||
|
|
public static function addFieldToLayout($layout)
|
|||
|
|
{
|
|||
|
|
$cid = isset($_GET['cid']) ? intval($_GET['cid']) : 0;
|
|||
|
|
$currentLocation = '';
|
|||
|
|
$currentDisplayLocation = '';
|
|||
|
|
|
|||
|
|
if ($cid > 0) {
|
|||
|
|
$weatherData = self::getWeatherData($cid);
|
|||
|
|
if ($weatherData) {
|
|||
|
|
$currentLocation = isset($weatherData['location']) ? $weatherData['location'] : '';
|
|||
|
|
$currentDisplayLocation = isset($weatherData['display_location']) ? $weatherData['display_location'] : '';
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 天气地点字段
|
|||
|
|
$locationField = new Typecho_Widget_Helper_Form_Element_Text('weather_location',
|
|||
|
|
NULL, $currentLocation, '天气地点', '填写城市名称,获取发布当日天气、温度,留空则不使用本功能');
|
|||
|
|
|
|||
|
|
$locationField->input->setAttribute('style', 'width: 50%; max-width: 200px;');
|
|||
|
|
$locationField->input->setAttribute('placeholder', '例如:北京市');
|
|||
|
|
$locationField->input->setAttribute('name', 'fields[weather_location]');
|
|||
|
|
|
|||
|
|
$layout->addItem($locationField);
|
|||
|
|
|
|||
|
|
// 新增:发布地点字段
|
|||
|
|
$displayLocationField = new Typecho_Widget_Helper_Form_Element_Text('display_location',
|
|||
|
|
NULL, $currentDisplayLocation, '发布地点', '填写要显示在文章页的发布地点,留空则使用天气地点');
|
|||
|
|
|
|||
|
|
$displayLocationField->input->setAttribute('style', 'width: 50%; max-width: 200px;');
|
|||
|
|
$displayLocationField->input->setAttribute('placeholder', '例如:上海外等');
|
|||
|
|
$displayLocationField->input->setAttribute('name', 'fields[display_location]');
|
|||
|
|
|
|||
|
|
$layout->addItem($displayLocationField);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* write 钩子 - 获取天气数据
|
|||
|
|
*/
|
|||
|
|
public static function onWrite($contents, $widget)
|
|||
|
|
{
|
|||
|
|
// 处理天气地点
|
|||
|
|
if (isset($_POST['fields']) && isset($_POST['fields']['weather_location'])) {
|
|||
|
|
$location = trim($_POST['fields']['weather_location']);
|
|||
|
|
|
|||
|
|
if (!empty($location)) {
|
|||
|
|
$created = isset($contents['created']) && $contents['created'] ? $contents['created'] : time();
|
|||
|
|
$publishDate = date('Y-m-d', $created);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$weatherData = self::fetchWeatherFromAPI($location, $publishDate);
|
|||
|
|
|
|||
|
|
self::$pendingWeatherData = array(
|
|||
|
|
'location' => $location,
|
|||
|
|
'weatherData' => $weatherData
|
|||
|
|
);
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 保存错误信息,但不中断流程
|
|||
|
|
self::$pendingWeatherData = array(
|
|||
|
|
'location' => $location,
|
|||
|
|
'error' => $e->getMessage()
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
self::$pendingWeatherData = array();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $contents;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 文章保存完成后的钩子
|
|||
|
|
*/
|
|||
|
|
public static function finishSaveWeatherData($contents, $widget)
|
|||
|
|
{
|
|||
|
|
if (!isset($widget->cid) || !$widget->cid) {
|
|||
|
|
return $contents;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$cid = $widget->cid;
|
|||
|
|
|
|||
|
|
// 获取天气地点
|
|||
|
|
$location = '';
|
|||
|
|
if (isset($_POST['fields']) && isset($_POST['fields']['weather_location'])) {
|
|||
|
|
$location = trim($_POST['fields']['weather_location']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取显示地点
|
|||
|
|
$displayLocation = '';
|
|||
|
|
if (isset($_POST['fields']) && isset($_POST['fields']['display_location'])) {
|
|||
|
|
$displayLocation = trim($_POST['fields']['display_location']);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!empty(self::$pendingWeatherData) && self::$pendingWeatherData['location'] == $location) {
|
|||
|
|
$pendingData = self::$pendingWeatherData;
|
|||
|
|
|
|||
|
|
if (isset($pendingData['error'])) {
|
|||
|
|
error_log("ArticleWeather API错误: " . $pendingData['error']);
|
|||
|
|
self::$pendingWeatherData = array();
|
|||
|
|
return $contents;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$weatherData = $pendingData['weatherData'];
|
|||
|
|
// 如果显示地点为空,使用天气地点
|
|||
|
|
if (empty($displayLocation) && !empty($location)) {
|
|||
|
|
$displayLocation = $location;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::saveToDatabase($cid, $location, $displayLocation, $weatherData);
|
|||
|
|
self::$pendingWeatherData = array();
|
|||
|
|
} else {
|
|||
|
|
if (!empty($location)) {
|
|||
|
|
$created = isset($widget->created) && $widget->created ? $widget->created : time();
|
|||
|
|
$publishDate = date('Y-m-d', $created);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$weatherData = self::fetchWeatherFromAPI($location, $publishDate);
|
|||
|
|
// 如果显示地点为空,使用天气地点
|
|||
|
|
if (empty($displayLocation)) {
|
|||
|
|
$displayLocation = $location;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self::saveToDatabase($cid, $location, $displayLocation, $weatherData);
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
error_log("ArticleWeather API错误: " . $e->getMessage());
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
self::removeFromDatabase($cid);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $contents;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 删除文章时的钩子
|
|||
|
|
*/
|
|||
|
|
public static function onDelete($cid, $widget)
|
|||
|
|
{
|
|||
|
|
self::removeFromDatabase($cid);
|
|||
|
|
return $cid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从API获取天气数据 - 使用高德地图天气API
|
|||
|
|
*/
|
|||
|
|
private static function fetchWeatherFromAPI($location, $date = null)
|
|||
|
|
{
|
|||
|
|
$options = Typecho_Widget::widget('Widget_Options')->plugin('ArticleWeather');
|
|||
|
|
$apiKey = $options ? $options->apiKey : '';
|
|||
|
|
|
|||
|
|
if (empty($apiKey)) {
|
|||
|
|
throw new Exception('请先设置高德地图API Key');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty($date)) {
|
|||
|
|
$date = date('Y-m-d');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用高德地图天气API
|
|||
|
|
try {
|
|||
|
|
// 第一步:获取城市编码(adcode)
|
|||
|
|
$geocodeUrl = "https://restapi.amap.com/v3/geocode/geo?key=" . urlencode($apiKey) .
|
|||
|
|
"&address=" . urlencode($location);
|
|||
|
|
|
|||
|
|
$geocodeResponse = self::httpRequest($geocodeUrl);
|
|||
|
|
if (!$geocodeResponse) {
|
|||
|
|
throw new Exception('地理位置编码请求失败,请检查网络连接');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$geocodeData = json_decode($geocodeResponse, true);
|
|||
|
|
if (!$geocodeData || !isset($geocodeData['status'])) {
|
|||
|
|
throw new Exception('地理位置API返回数据格式错误');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($geocodeData['status'] !== '1') {
|
|||
|
|
$msg = isset($geocodeData['info']) ? $geocodeData['info'] : '未知错误';
|
|||
|
|
$code = isset($geocodeData['infocode']) ? $geocodeData['infocode'] : '未知';
|
|||
|
|
|
|||
|
|
if ($code === '10001') {
|
|||
|
|
throw new Exception('API Key无效或不存在,请检查Key是否正确');
|
|||
|
|
} elseif ($code === '10003') {
|
|||
|
|
throw new Exception('API Key每日调用超限(5000次/天)');
|
|||
|
|
} elseif ($code === '10004') {
|
|||
|
|
throw new Exception('API Key访问权限不足,请确认已开通Web服务');
|
|||
|
|
} elseif ($code === '10005') {
|
|||
|
|
throw new Exception('请求频率超限');
|
|||
|
|
} else {
|
|||
|
|
throw new Exception("地理位置查询失败 [{$code}]: {$msg}");
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty($geocodeData['geocodes'])) {
|
|||
|
|
throw new Exception('未找到该城市信息,请确认城市名称是否正确:' . $location);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 获取城市编码(adcode)
|
|||
|
|
$adcode = $geocodeData['geocodes'][0]['adcode'];
|
|||
|
|
|
|||
|
|
// 第二步:获取天气信息
|
|||
|
|
// 高德天气API提供实时天气(base)和预报天气(all)
|
|||
|
|
// 这里我们使用实时天气,因为免费版可能不支持历史天气
|
|||
|
|
$weatherUrl = "https://restapi.amap.com/v3/weather/weatherInfo?key=" . urlencode($apiKey) .
|
|||
|
|
"&city=" . urlencode($adcode) . "&extensions=base";
|
|||
|
|
|
|||
|
|
$weatherResponse = self::httpRequest($weatherUrl);
|
|||
|
|
if (!$weatherResponse) {
|
|||
|
|
throw new Exception('天气API请求失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$weatherData = json_decode($weatherResponse, true);
|
|||
|
|
if (!$weatherData || !isset($weatherData['status'])) {
|
|||
|
|
throw new Exception('天气API返回数据格式错误');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($weatherData['status'] !== '1') {
|
|||
|
|
$msg = isset($weatherData['info']) ? $weatherData['info'] : '未知错误';
|
|||
|
|
$code = isset($weatherData['infocode']) ? $weatherData['infocode'] : '未知';
|
|||
|
|
throw new Exception("天气查询失败 [{$code}]: {$msg}");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (empty($weatherData['lives']) || empty($weatherData['lives'][0])) {
|
|||
|
|
throw new Exception('天气API返回数据为空');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$live = $weatherData['lives'][0];
|
|||
|
|
|
|||
|
|
// 检查是否是今天
|
|||
|
|
$today = date('Y-m-d');
|
|||
|
|
$reportTime = isset($live['reporttime']) ? substr($live['reporttime'], 0, 10) : $today;
|
|||
|
|
|
|||
|
|
// 如果是过去日期,尝试获取预报天气
|
|||
|
|
if ($date != $today && strtotime($date) > strtotime($today)) {
|
|||
|
|
// 尝试获取预报天气
|
|||
|
|
$forecastUrl = "https://restapi.amap.com/v3/weather/weatherInfo?key=" . urlencode($apiKey) .
|
|||
|
|
"&city=" . urlencode($adcode) . "&extensions=all";
|
|||
|
|
|
|||
|
|
$forecastResponse = self::httpRequest($forecastUrl);
|
|||
|
|
if ($forecastResponse) {
|
|||
|
|
$forecastData = json_decode($forecastResponse, true);
|
|||
|
|
|
|||
|
|
if ($forecastData && $forecastData['status'] === '1' && !empty($forecastData['forecasts'][0]['casts'])) {
|
|||
|
|
$casts = $forecastData['forecasts'][0]['casts'];
|
|||
|
|
|
|||
|
|
// 查找指定日期的预报
|
|||
|
|
foreach ($casts as $cast) {
|
|||
|
|
if ($cast['date'] == $date) {
|
|||
|
|
return array(
|
|||
|
|
'weather' => $cast['dayweather'],
|
|||
|
|
'temperature' => $cast['nighttemp'] . '~' . $cast['daytemp'] . '°C',
|
|||
|
|
'date' => $date,
|
|||
|
|
'note' => '预报天气'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用第一天预报
|
|||
|
|
$cast = $casts[0];
|
|||
|
|
return array(
|
|||
|
|
'weather' => $cast['dayweather'],
|
|||
|
|
'temperature' => $cast['nighttemp'] . '~' . $cast['daytemp'] . '°C',
|
|||
|
|
'date' => $cast['date'],
|
|||
|
|
'note' => '预报天气'
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 使用实时天气数据
|
|||
|
|
return array(
|
|||
|
|
'weather' => $live['weather'],
|
|||
|
|
'temperature' => $live['temperature'] . '°C',
|
|||
|
|
'date' => $reportTime,
|
|||
|
|
'note' => '实时天气'
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
throw $e;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 通用的HTTP请求方法
|
|||
|
|
*/
|
|||
|
|
private static function httpRequest($url)
|
|||
|
|
{
|
|||
|
|
// 优先使用cURL
|
|||
|
|
if (function_exists('curl_init')) {
|
|||
|
|
$ch = curl_init();
|
|||
|
|
curl_setopt($ch, CURLOPT_URL, $url);
|
|||
|
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
|||
|
|
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
|
|||
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
|
|||
|
|
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
|
|||
|
|
curl_setopt($ch, CURLOPT_USERAGENT, 'ArticleWeather Plugin/22.1');
|
|||
|
|
|
|||
|
|
$response = curl_exec($ch);
|
|||
|
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|||
|
|
$error = curl_error($ch);
|
|||
|
|
curl_close($ch);
|
|||
|
|
|
|||
|
|
if ($httpCode == 200 && $response) {
|
|||
|
|
return $response;
|
|||
|
|
} else {
|
|||
|
|
error_log("ArticleWeather cURL请求失败: URL: {$url}, HTTP Code: {$httpCode}, Error: {$error}");
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 备选方案:使用file_get_contents
|
|||
|
|
$context = stream_context_create([
|
|||
|
|
'http' => [
|
|||
|
|
'timeout' => 10,
|
|||
|
|
'ignore_errors' => true,
|
|||
|
|
'header' => "User-Agent: ArticleWeather Plugin/22.1\r\n"
|
|||
|
|
],
|
|||
|
|
'ssl' => [
|
|||
|
|
'verify_peer' => false,
|
|||
|
|
'verify_peer_name' => false
|
|||
|
|
]
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
$response = @file_get_contents($url, false, $context);
|
|||
|
|
|
|||
|
|
if ($response === false) {
|
|||
|
|
$error = error_get_last();
|
|||
|
|
error_log("ArticleWeather HTTP请求失败: " . ($error['message'] ?? '未知错误'));
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return $response;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 保存到数据库
|
|||
|
|
*/
|
|||
|
|
private static function saveToDatabase($cid, $location, $displayLocation, $weatherData)
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
|
|||
|
|
$data = array(
|
|||
|
|
'cid' => $cid,
|
|||
|
|
'location' => $location,
|
|||
|
|
'display_location' => $displayLocation,
|
|||
|
|
'weather' => isset($weatherData['weather']) ? $weatherData['weather'] : '',
|
|||
|
|
'temperature' => isset($weatherData['temperature']) ? $weatherData['temperature'] : '',
|
|||
|
|
'created' => time()
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 检查是否已存在
|
|||
|
|
$exists = $db->fetchRow($db->select()->from('table.' . self::$tableName)->where('cid = ?', $cid));
|
|||
|
|
|
|||
|
|
if ($exists) {
|
|||
|
|
$db->query($db->update('table.' . self::$tableName)->rows($data)->where('cid = ?', $cid));
|
|||
|
|
} else {
|
|||
|
|
$db->query($db->insert('table.' . self::$tableName)->rows($data));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return true;
|
|||
|
|
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
error_log("ArticleWeather保存失败: " . $e->getMessage() . " CID: " . $cid);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 从数据库删除记录
|
|||
|
|
*/
|
|||
|
|
private static function removeFromDatabase($cid)
|
|||
|
|
{
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$db->query($db->delete('table.' . self::$tableName)->where('cid = ?', $cid));
|
|||
|
|
return true;
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
error_log("ArticleWeather删除失败: " . $e->getMessage() . " CID: " . $cid);
|
|||
|
|
return false;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取天气数据
|
|||
|
|
*/
|
|||
|
|
private static function getWeatherData($cid, $field = '')
|
|||
|
|
{
|
|||
|
|
if (!$cid) return null;
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$db = Typecho_Db::get();
|
|||
|
|
$row = $db->fetchRow($db->select()->from('table.' . self::$tableName)->where('cid = ?', $cid));
|
|||
|
|
|
|||
|
|
if ($row && !empty($row['location'])) {
|
|||
|
|
if ($field && isset($row[$field])) {
|
|||
|
|
return $row[$field];
|
|||
|
|
}
|
|||
|
|
return $row;
|
|||
|
|
}
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 忽略错误
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 生成天气HTML - 紧凑单行显示
|
|||
|
|
*/
|
|||
|
|
private static function generateWeatherHtml($location, $weather, $temperature)
|
|||
|
|
{
|
|||
|
|
$weatherIcon = self::getWeatherIcon($weather);
|
|||
|
|
|
|||
|
|
return <<<HTML
|
|||
|
|
<div class="article-weather-compact" style="
|
|||
|
|
background: #f15a22;
|
|||
|
|
color: white;
|
|||
|
|
padding: 12px 12px;
|
|||
|
|
border-radius: 20px;
|
|||
|
|
margin: 15px auto;
|
|||
|
|
max-width:180px;
|
|||
|
|
font-size: 14px;
|
|||
|
|
line-height: 1;">
|
|||
|
|
<!--<span style="font-size:12px;">📍</span>-->
|
|||
|
|
<span>{$location}</span>
|
|||
|
|
<span style="opacity: 0.8;">|</span>
|
|||
|
|
<!--<span style="font-size:12px;">{$weatherIcon}</span>-->
|
|||
|
|
<span>{$weather}</span>
|
|||
|
|
<span style="opacity: 0.8;">|</span>
|
|||
|
|
<!--<span style="font-size:12px;">🌡️</span>-->
|
|||
|
|
<span>{$temperature}</span>
|
|||
|
|
|
|||
|
|
</div>
|
|||
|
|
HTML;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取天气图标
|
|||
|
|
*/
|
|||
|
|
private static function getWeatherIcon($weather)
|
|||
|
|
{
|
|||
|
|
if (strpos($weather, '晴') !== false) return '☀️';
|
|||
|
|
if (strpos($weather, '多云') !== false) return '⛅';
|
|||
|
|
if (strpos($weather, '阴') !== false) return '☁️';
|
|||
|
|
if (strpos($weather, '雨') !== false) return '🌧️';
|
|||
|
|
if (strpos($weather, '雪') !== false) return '❄️';
|
|||
|
|
if (strpos($weather, '雷') !== false) return '⛈️';
|
|||
|
|
if (strpos($weather, '雾') !== false) return '🌫️';
|
|||
|
|
if (strpos($weather, '沙') !== false) return '🌪️';
|
|||
|
|
return '🌤️';
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 手动调用方法 - 主要使用这个
|
|||
|
|
*/
|
|||
|
|
public static function show($cid = null)
|
|||
|
|
{
|
|||
|
|
if (!$cid) {
|
|||
|
|
$widget = Typecho_Widget::widget('Widget_Archive');
|
|||
|
|
if (!$widget->is('single')) return '';
|
|||
|
|
$cid = $widget->cid;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$weatherData = self::getWeatherData($cid);
|
|||
|
|
|
|||
|
|
if ($weatherData && !empty($weatherData['location']) && !empty($weatherData['weather'])) {
|
|||
|
|
// 优先使用显示地点,如果为空则使用天气地点
|
|||
|
|
$displayLocation = !empty($weatherData['display_location'])
|
|||
|
|
? $weatherData['display_location']
|
|||
|
|
: $weatherData['location'];
|
|||
|
|
|
|||
|
|
return self::generateWeatherHtml(
|
|||
|
|
$displayLocation,
|
|||
|
|
$weatherData['weather'],
|
|||
|
|
$weatherData['temperature']
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return '';
|
|||
|
|
}
|
|||
|
|
}
|