当前位置:网站首页 > 更多 > 玩电脑 > 正文

[玩转系统] 周五乐趣:PowerShell 天气小工具

作者:精品下载站 日期:2024-12-14 08:01:52 浏览:12 分类:玩电脑

周五乐趣:PowerShell 天气小工具


最近,Twitter 上的某人向我推荐了一种可以在 PowerShell 会话中使用来显示天气信息的资源。这显然是一个成熟且备受推崇的来源。一旦我弄清楚了基础知识,我自然想看看我还能用什么来做到这一点。这就是我的想法。

我今天要讨论的所有内容都涉及使用 Invoke-RestMethod 来使用 REST API。您可以在浏览器中测试的主要 URI 是 http://wttr.in。在 URI 中包含一个位置,例如 http://wttr.in/chicago。但情况会变得更好。 API 源代码和文档可以在 https://github.com/chubin/wttr.in 找到。一旦我花了一点时间研究我能做什么,我就确定了一个像这样的基本命令:

Invoke-RestMethod -Uri http://wttr.in/Syracuse?format=2 -UseBasicParsing -DisableKeepAlive

[玩转系统] 周五乐趣:PowerShell 天气小工具

我觉得这很酷。尽管表情符号仅显示在支持它们的终端控制台中,例如在 Windows 终端中运行的 PowerShell 会话。在 PowerShell ISE 或 VS Code 控制台中运行此程序的运气会较差。

现在我需要推动这个概念。

设置位置格式

构建一个快速脚本来运行此命令非常简单。我什至添加了一个位置参数。该位置可以是邮政编码或地名。我希望创建包含位置的输出。但我想确保该位置的格式正确。

if($Location-match'^([a-zA-Z\s\.\,])+$'){
$Location=[System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ToTitleCase($location.tolower())
}

如果位置是字符串,例如“chicago”,则此文本片段会将其转换为“Chicago”。像“las vegas”这样的字符串会变成“Las Vegas”。

使用当地时间

当我开发代码时,我添加了当前日期和时间。但后来我意识到,如果我在另一个地点(例如拉斯维加斯)运行脚本,并显示东部时区的时间,可能会令人困惑。我需要获取该位置的时区,以便我可以相应地调整时间。

在遇到可选的 v2 API 之前,我在基本 wttr.in 请求中找不到包含该信息的任何内容。格式类似于 http://v2.wttr.in/chicago。不幸的是,没有办法以结构化数据(如 JSON 或 XML)的形式获取结果。所以我不得不求助于正则表达式来解析时区名称。我构建了一个辅助函数来做到这一点。

FunctionGetTZ{
Param([string]$location)
Write-Verbose"Gettingv2datafromwttr.infor$location"
$tmp=Invoke-RestMethod-uri"http://v2.wttr.in/$location"-disableKeepAlive-useBasicParsing
$rx=[System.Text.RegularExpressions.Regex]::new("\b([a-z_]+\/[a-z_]+)\b","IgnoreCase,Multiline")
$timezone=$rx.match($tmp).Value
Write-Verbose"Detectedtimezone$timezone"
$timezone
}

这会给我一个像 America/Chicago 这样的值。下一步是确定时区偏移量。幸运的是,我在 http://worldtimeapi.org 上有另一个使用 REST API 的代码片段。使用此 API,我创建了一个自定义对象。

[玩转系统] 周五乐趣:PowerShell 天气小工具

我的自定义对象包括该时区的当地时间,然后我可以在输出中使用它。

用 ANSI 让它变得漂亮

最后一步是让它变得漂亮。我想使用 ANSI 和特殊字符在天气和位置信息周围绘制彩色线框。通常,这并不太困难,因为我可以计算线长度。

然而,表情符号字符串存在一些挑战。尽管 PowerShell 可能会在两个不同位置显示相同的长度,但如果天气表情符号不同,则可能会导致间距混乱。这意味着我的结束 |因为线路可能会随着天气变化而关闭。我还没有找到一种方法来解决使用表情符号时的字偶距问题,因此我求助于使用 Write-Host。

通过这种方法,我可以保存上一行的光标位置,显示天气行,移动光标,然后显示结束 |。

Write-Host$line1
Write-Host$line2
Write-Host$line3a-NoNewline
#getthecursorposition
$pos=$host.ui.RawUI.CursorPosition
#adjustit
$pos.x=$line1.Length-$boxAnsi.Length-1
#movethecursor
$host.ui.RawUI.CursorPosition=$pos
#writetheclosingboxelement
Write-Host$line3b
Write-Host$line4

我并不为这个黑客感到自豪,但它确实有效。

[玩转系统] 周五乐趣:PowerShell 天气小工具

ANSI 序列被编写为在 Windows PowerShell 或 PowerShell 7 中运行。以下是完整的脚本。

#requires-version5.1

#seehttps://github.com/chubin/wttr.inforAPIinformation

#thismustberuninaWindowsTerminalsessionthatsupportstheglyphs
#oruse-Forceifyouknowyouare.

[cmdletbinding()]
Param([string]$Location="Syracuse,NY",[switch]$Force)

FunctionTestWT{
$parent=Get-CimInstance-ClassNamewin32_process-Filter"processid=$pid"-PropertyParentProcessID
(Get-Process-Id$parent.ParentProcessId).ProcessName-eq"WindowsTerminal"
}
FunctionGetTZ{
Param([string]$location)
Write-Verbose"Gettingv2datafromwttr.infor$location"
$tmp=Invoke-RestMethod-uri"http://v2.wttr.in/$location"-disableKeepAlive-useBasicParsing
$rx=[System.Text.RegularExpressions.Regex]::new("\b([a-z_]+\/[a-z_]+)\b","IgnoreCase,Multiline")
$timezone=$rx.match($tmp).Value
Write-Verbose"Detectedtimezone$timezone"
$timezone
}
FunctionGetTZData{
[cmdletbinding()]
[OutputType("pscustomobject","TimeZoneData")]
Param(
[Parameter(Position=0,Mandatory,ValueFromPipeline,
HelpMessage="EnteratimezonelocationlikePacific/Auckland.Itiscasesensitive.")]
[string]$TimeZoneArea,
[parameter(HelpMessage="Returnraw,unformatteddata.")]
[switch]$Raw
)
Begin{
Write-Verbose"Starting$($myinvocation.mycommand)"
$base="http://worldtimeapi.org/api/timezone"
}#begin

Process{
Write-Verbose"Gettingtimezoneinformationfor$TimeZoneArea"
$target="$base/$TimeZoneArea"
Try{
$data=Invoke-RestMethod-Uri$target-DisableKeepAlive-UseBasicParsing-ErrorActionStop-ErrorVariablee
}
Catch{
Throw$e.innerexception
}
if($data-AND$Raw){
$data
}
elseif($data){
if($data.utc_offset-match"\+"){
$offset=($data.utc_offset.substring(1)-as[timespan])
}
else{
$offset=($data.utc_offset-as[timespan])
}

[datetime]$dt=$data.DateTime
[pscustomobject]@{
PSTypename="TimeZoneData"
Timezone=$data.timezone
Abbreviation=$data.abbreviation
Offset=$offset
DaylightSavingTime=$data.dst
Time=$dt.ToUniversalTime().Addseconds($data.raw_offset)
}
#(Get-Date($data.datetime-split"[\+-]\d{2}:\d{2}")[0])
}
}#process

End{
Write-Verbose"Ending$($myinvocation.mycommand)"

}#end

}#closeGetTZData

#charactersforbuildingalinebox
$charHash=@{
upperLeft=[char]0x250c
upperRight=[char]0x2510
lowerRight=[char]0x2518
lowerLeft=[char]0x2514
horizontal=[char]0x2500
vertical=[char]0x2502
}

Write-Verbose"Gettingweathersummaryfor$location"

#ANSIsequences
$boxAnsi="$([char]0x1b)[38;5;11m"
$closeAnsi="$([char]0x1b)[0m"
$textAnsi="$([char]0x1b)[38;5;191m"

#convertlocationtotitlecaseifitiswords
if($Location-match'^([a-zA-Z\s\.\,])+$'){
$Location=[System.Globalization.CultureInfo]::CurrentCulture.TextInfo.ToTitleCase($location.tolower())
}
if($Force-OR(TestWT)){
$w=Invoke-RestMethod-Uri"http://wttr.in/{$location}?format=2"
Write-Verbose"Gettinglocaltimesettingsfor$Location"
$localtime=gettzdata(gettz$location)
[string]$date="{0:g}"-f$localtime.time

$data=($w.trim())-replace"\s+",""
#internalsumoftheindividualelements.Tryingtofigureoutkerningorspacing
#$internalLength=$($data.split()|foreach{$_.length}|measure-object-sum).sum
$header="{0}"-f$location
$headerAnsi="{0}{1}"-f$textAnsi,$header
$value="{0}{1}"-f$textAnsi,$data

$line1="{0}{1}{2}{3}{4}"-f$boxAnsi,$charHash.upperleft,$date,([string]$charHash.horizontal*($data.length-$date.length)),$charhash.upperRight
$line2="{0}{1}{2}{3}{4}"-f$boxAnsi,$charHash.Vertical,$headerAnsi.padright($line1.length-1),$boxAnsi,$charHash.Vertical
$line3a="{0}{1}{2}"-f$boxAnsi,$charHash.Vertical,$value
#($value.padright($header.length))

$line3b="{0}{1}"-f$boxAnsi,$charHash.Vertical
$line4="{0}{1}{2}{3}{4}"-f$boxAnsi,$charHash.Lowerleft,([string]$charHash.horizontal*($data.length+2)),$charhash.LowerRight,$closeAnsi

Write-Host$line1
Write-Host$line2
Write-Host$line3a-NoNewline
#getthecursorposition
$pos=$host.ui.RawUI.CursorPosition
#adjustit
$pos.x=$line1.Length-$boxAnsi.Length-1
#movethecursor
$host.ui.RawUI.CursorPosition=$pos
#writetheclosingboxelement
Write-Host$line3b
Write-Host$line4
else{
Write-Warning"ThisneedstoberuninaWindowsTerminalsession."
}
}

下一步

我应该把它变成一个带有别名的函数,以使其易于运行。我可能会更深入地研究 wttri.in 的源文件,看看我是否无法直接进行其中一些调用。这可能会更容易同时获取时区信息。我还可以尝试将其添加到我的 PowerShell 提示功能中,并将其显示在我的会话的一角。

尝试一下,玩得开心,然后让我知道您的想法。

更新

我已在 GitHub 上发布了此函数的更新版本:https://gist.github.com/jdhitsolutions/f2fb0184c2dbab107f2416fb775d462b。该版本接受管道输入。

您需要 登录账户 后才能发表评论

取消回复欢迎 发表评论:

关灯