{"id":11244,"date":"2025-10-12T14:43:19","date_gmt":"2025-10-12T13:43:19","guid":{"rendered":"https:\/\/phoenixgamedevelopment.com\/blog\/?p=11244"},"modified":"2025-10-12T15:45:39","modified_gmt":"2025-10-12T14:45:39","slug":"sillytavern-extension-weather-checker","status":"publish","type":"post","link":"https:\/\/phoenixgamedevelopment.com\/blog\/sillytavern-extension-weather-checker\/","title":{"rendered":"SillyTavern Extension: Weather Checker"},"content":{"rendered":"\n<p>I have created another extension for SillyTavern. <\/p>\n\n\n\n<p>This extension uses the &#8220;<a href=\"https:\/\/open-meteo.com\/\" title=\"\">OpenMeteo<\/a>&#8221; open source weather API.<\/p>\n\n\n\n<p>I have previously tried the &#8220;Accuweather&#8221; plugin for SillyTavern, but this didn&#8217;t work for me, it did not return any results. I did manage to get the WebSearch plugin working, and this can be used to generate weather reports\/forecasts in SillyTavern, but the formatting is inconsistent, and I found this to be a poor solution.<\/p>\n\n\n\n<p>My solution using OpenMeteo works well and produces easily understood and correctly formatted output, in the following form:<br><\/p>\n\n\n\n<p>The following content is weather data starting from today:<\/p>\n\n\n\n<p>Coordinates: &lt;REMOVED><br>Timezone: Europe\/London GMT+1<\/p>\n\n\n\n<p><em>12\/10\/25 Overcast<br>13\/10\/25 Light Drizzle<br>14\/10\/25 Overcast<br>15\/10\/25 Overcast<br>16\/10\/25 Overcast<br>17\/10\/25 Overcast<br>18\/10\/25 Light Drizzle<br>19\/10\/25 Light Drizzle<br>20\/10\/25 Overcast<br>21\/10\/25 Light Drizzle<br>22\/10\/25 Light Drizzle<br>23\/10\/25 Light Drizzle<br>24\/10\/25 Overcast<br>25\/10\/25 Moderate Drizzle<br>26\/10\/25 Overcast<br>27\/10\/25 Moderate Drizzle<br>[WEATHERDATA]<\/em><\/p>\n\n\n\n<p>Currently, the extension only displays a 16 day weather forecast with a human-readable weather code, as shown above, however it is very easy to improve this to display any desired data. OpenMeteo has a very large amount of data available, including rainfall, temperature, wind speed, sunset\/sunrise times, etc, etc.<\/p>\n\n\n\n<p>I haven&#8217;t added this extension to github yet, so I will just post the code here. This code is based on my earlier <a href=\"https:\/\/phoenixgamedevelopment.com\/blog\/sillytavern-extension-email-checker\/\" title=\"\">Email Checker extension<\/a>. <\/p>\n\n\n\n<p>You will need to do an NPM install for openmeteo to make this code work.<\/p>\n\n\n\n<p>You will also need to enter your latitude and longitude in the &#8220;const params&#8221; section of the server script below.<\/p>\n\n\n\n<p>In addition, <\/p>\n\n\n\n<p>On the server, add this at the top of index.js:<\/p>\n\n\n\n<p><\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const { fetchWeatherApi } = require('openmeteo');<\/code><\/pre>\n\n\n\n<p>Then, to access the API:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>async function fetchweather(ws)\n{\n\nconst params = {\n\t\"latitude\": &lt;ENTER LATITUDE HERE>\n\t\"longitude\": &lt;ENTER LONGITUDE HERE>\n\t\"daily\": \"weather_code\",\n\t\"timezone\": \"Europe\/London\",\n\t\"forecast_days\": 16,\n};\nconst url = \"https:\/\/api.open-meteo.com\/v1\/forecast\";\nconst responses = await fetchWeatherApi(url, params);\n  \n\/\/ Process first location. Add a for-loop for multiple locations or weather models\nconst response = responses&#91;0];\n\n\/\/ Attributes for timezone and location\nconst latitude = response.latitude();\nconst longitude = response.longitude();\nconst elevation = response.elevation();\nconst timezone = response.timezone();\nconst timezoneAbbreviation = response.timezoneAbbreviation();\nconst utcOffsetSeconds = response.utcOffsetSeconds();\n\nvar retdta = \"\";\n\nconsole.log(\n\t`\\nCoordinates: ${latitude}\u00b0N ${longitude}\u00b0E`,\n\t`\\nElevation: ${elevation}m asl`,\n\t`\\nTimezone: ${timezone} ${timezoneAbbreviation}`,\n\t`\\nTimezone difference to GMT+0: ${utcOffsetSeconds}s`,\n);\n\nretdta += \n\t`\\nCoordinates: ${latitude}\u00b0N ${longitude}\u00b0E` +\t\n\t`\\nTimezone: ${timezone} ${timezoneAbbreviation}` + \"\\n\\n\";\n\t\nconst daily = response.daily();\n\n\/\/ Setup\nconst startTime = Number(daily.time());\nconst endTime = Number(daily.timeEnd());\nconst interval = daily.interval();\nconst weatherCodes = daily.variables(0).valuesArray();\n\nconst timeArray = &#91;];\n\nfor (let i = 0, timestamp = startTime; timestamp &lt; endTime; i++, timestamp += interval) {\n\tconst date = new Date((timestamp + utcOffsetSeconds) * 1000);\n\n\t\/\/ Format as DD\/MM\/YY\n\tconst day = String(date.getDate()).padStart(2, '0');\n\tconst month = String(date.getMonth() + 1).padStart(2, '0'); \/\/ Months are 0-indexed\n\tconst year = String(date.getFullYear()).slice(-2); \/\/ Get last two digits\n\t\n\tconst weatherCode = parseInt(weatherCodes&#91;i], 10);\n\tconst weathercodetext = getweathercodetext(weatherCode);\n\n\t\/\/ Combine with weather code\n\tconsole.log(day + \"\/\" + month + \"\/\" + year + \" \" + weatherCode + \" \" + weathercodetext);\n\tretdta += day + \"\/\" + month + \"\/\" + year + \" \" + weathercodetext + \"\\n\";\n\ttimeArray.push(`${day}\/${month}\/${year} - ${weatherCode}`)\n}\n\nretdta+= \"&#91;WEATHERDATA]\";\nws.send(retdta);\n\nconst weatherData = {\n\tdaily: {\n\t\ttime: timeArray,\n\t\tweather_code: weatherCodes,\n\t},\n};\n\n}\n\nfunction getweathercodetext(weathercode)\n{\nvar ret = \"\";\n switch(weathercode) {\n        case 0:\n           ret = \"Fair\";\/\/\"Clear Sky\";\n            break;\n\n        case 1:\n           ret = \"Mainly Clear\";\n            break;\n\n                case 2:\n           ret = \"Partly Cloudy\";\n            break;  \n \n                case 3:\n           ret = \"Overcast\";\n            break;  \n\n                case 45:\n           ret = \"Fog\";\n            break;  \n\n                case 48:\n           ret = \"Depositing Rime Fog\";\n            break;  \n\n               case 51:\n           ret = \"Light Drizzle\";\n            break;  \n\n              case 53:\n           ret = \"Moderate Drizzle\";\n            break;  \n\n       case 55:\n           ret = \"Dense Drizzle\";\n            break;  \n\n       case 56:\n           ret = \"Light Freezing Drizzle\";\n            break;  \n\n       case 57:\n           ret = \"Dense Freezing Drizzle\";\n            break;  \n\n     case 61:\n           ret = \"Light Rain\";\n            break;  \n\n     case 63:\n           ret = \"Moderate Rain\";\n            break;  \n\n    case 65:\n           ret = \"Heavy Rain\";\n            break; \n\n    case 66:\n           ret = \"Light Freezing Rain\";\n            break; \n\n    case 67:\n           ret = \"Heavy Freezing Rain\";\n            break; \n\n    case 71:\n           ret = \"Slight Snow Fall\";\n            break; \n\n    case 73:\n           ret = \"Moderate Snow Fall\";\n            break; \n\n    case 75:\n           ret = \"Heavy Snow Fall\";\n            break; \n\n    case 77:\n           ret = \"Snow Grains\";\n            break; \n\n    case 80:\n           ret = \"Slight Rain Showers\";\n            break; \n\n   case 81:\n           ret = \"Moderate Rain Showers\";\n            break; \n\n   case 82:\n           ret = \"Violent Rain Showers\";\n            break; \n\n   case 85:\n           ret = \"Slight Snow Showers\";\n            break; \n\n   case 86:\n           ret = \"Heavy Rain Showers\";\n            break; \n\n   case 95:\n           ret = \"Slight or Moderate Thunderstorm\";\n            break; \n\n   case 96:\n           ret = \"Thunderstorm with Slight Hail\";\n            break; \n\n   case 99:\n           ret = \"Thunderstorm with Heavy Hail\";\n            break; \n\n        default:\n        ret = \"INVALID WEATHER CODE: \" + weathercode;\n           break;\n    }\n    console.log(\"getweathercodetext: \" + weathercode + \" \"  + ret);\nreturn ret;\n}<\/code><\/pre>\n\n\n\n<p>Then, on the client, use this function to display the weather data:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>async function printweatherdata(txt){\n\t   const context = getContext();\n\t\n\t   if(txt == \"STARTUPMESSAGE\")\n\t  \t return;\t   \n\n   console.log(\"printweatherdata:\" + txt);\n   context.executeSlashCommands('\/reasoning-set The following content is weather data starting from today:\\n ' + txt + ' ').pipe\n}\n<\/code><\/pre>\n\n\n\n<p><\/p>\n\n\n\n\n<!-- wp:themify-builder\/canvas \/-->\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I have created another extension for SillyTavern. This extension uses the &#8220;OpenMeteo&#8221; open source weather API. I have previously tried the &#8220;Accuweather&#8221; plugin for SillyTavern, but this didn&#8217;t work for me, it did not return any results. I did manage to get the WebSearch plugin working, and this can be used to generate weather reports\/forecasts [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":11249,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[37,12],"tags":[],"class_list":["post-11244","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ai","category-tutorials","has-post-title","has-post-date","has-post-category","has-post-tag","has-post-comment","has-post-author",""],"aioseo_notices":[],"builder_content":"","_links":{"self":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/11244"}],"collection":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/comments?post=11244"}],"version-history":[{"count":5,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/11244\/revisions"}],"predecessor-version":[{"id":11252,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/posts\/11244\/revisions\/11252"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/media\/11249"}],"wp:attachment":[{"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/media?parent=11244"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/categories?post=11244"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/phoenixgamedevelopment.com\/blog\/wp-json\/wp\/v2\/tags?post=11244"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}