From 14714a9e1bf225ba6747a7697b180e4c5f5ed44c Mon Sep 17 00:00:00 2001
From: XIGE <710062962@qq.com>
Date: Mon, 23 Feb 2026 17:09:10 +0800
Subject: [PATCH] 1.0
---
Plugin.php | 628 +++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 628 insertions(+)
create mode 100644 Plugin.php
diff --git a/Plugin.php b/Plugin.php
new file mode 100644
index 0000000..8990095
--- /dev/null
+++ b/Plugin.php
@@ -0,0 +1,628 @@
+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 '
高德地图API配置
';
+
+ $apiKey = new Typecho_Widget_Helper_Form_Element_Text('apiKey',
+ NULL, '', '高德地图API_KEY',
+ '');
+ $form->addInput($apiKey->addRule('required', '必须填写API Key'));
+
+ // 添加测试按钮
+ echo '
+
+
+
';
+
+ echo '';
+
+ echo '
+
📝 如何获取高德API Key:
+
+ - 访问 高德开放平台
+ - 注册并登录账号
+ - 进入"控制台" → "应用管理" → "我的应用"
+ - 创建新应用,选择"Web服务"类型
+ - 添加Key,服务选择"Web服务"(不要选Web端)
+ - 免费版每日调用限制:天气API 5000次/Key
+ - 注意:需要实名认证后才能使用
+
+
';
+ }
+
+ 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 <<
+
+ {$location}
+ |
+
+ {$weather}
+ |
+
+ {$temperature}
+
+
+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 '';
+ }
+}
\ No newline at end of file