Habitica.psm1
# https://habitica.fandom.com/wiki/Application_Programming_Interface # https://habitica.com/apidoc/ # Habitica Markdown formatting https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet ################################################################################ # Habitica API related functions ################################################################################ function Get-HabiticaGroup { <# .SYNOPSIS Returns information about a Habitica group .DESCRIPTION Returns detailed information about a group By default, returns information about the user's party .PARAMETER GroupID The UUID of a group, or common names of 'party' for the user party and 'habitrpg' for tavern are accepted Defaults to 'party' .PARAMETER Full If -Full is included, the full RESTApi response will be included with details such as success, userV, and appVersion. If not specified, only the data field is returned .LINK https://habitica.com/apidoc/#api-Group-GetGroup #> [CmdletBinding()] param ( [string]$GroupID='party', [switch]$Full = $False ) if ($Full) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$GroupID" -Headers $HabiticaHeader -Method GET } Else { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$GroupID" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } } function Get-HabiticaUser { <# .SYNOPSIS Returns user information .DESCRIPTION Returns all user data found in the export of userdata from Habitica .LINK https://habitica.com/apidoc/#api-DataExport-ExportUserDataJson #> $UserData = Invoke-RestMethod -Uri "https://habitica.com/export/userdata.json" -Headers $HabiticaHeader -Method GET Return $UserData } function Get-HabiticaGroupChat { <# .SYNOPSIS Returns the full group chat log .DESCRIPTION Returns all group chat log data that is currently available .PARAMETER GroupID The UUID of a group, or common names of 'party' for the user party and 'habitrpg' for tavern are accepted Defaults to 'party' .PARAMETER Full If -Full is included, the full RESTApi response will be included with details such as success, userV, and appVersion. If not specified, only the data field is returned .LINK https://habitica.com/apidoc/#api-Chat-GetChat #> [cmdletbinding()] param ( [string]$GroupID = 'party', [switch]$Full = $False ) If ($Full) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$GroupID/chat" -Headers $HabiticaHeader -Method GET } Else { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$GroupID/chat" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } } function Send-HabiticaPrivateMessage { <# .SYNOPSIS Sends a private message to another Habitica user .PARAMETER Message The message to be sent to another user, surrounded in quotes .PARAMETER UserID The UUID of a particular member to send the message to in the format of '11111111-2222-3333-4444-555555555555' If a username is provided, the user's party will be searched for matching usernames to resolve to a UUID .EXAMPLE Send-HabiticaPrivateMessage -Message 'Hi there!' -UserID (Get-HabiticaGroup -Group 'party').leader.id Sends a message to the leader of the party .LINK https://habitica.com/apidoc/#api-Member-SendPrivateMessage #> [cmdletbinding()] param ( $UserID, $Message ) if ($UserID -notmatch '\w\w\w\w\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w\w\w\w\w\w\w\w\w') { $SearchResult = Get-HabiticaGroupMember -Group 'party' | Where-Object {$_.profile.name -like "*$UserID*"} | Select-Object -ExpandProperty id if (!$SearchResult) { #Searching Challenges Here $SearchResult = Get-HabiticaUserChallenge | Get-HabiticaChallengeMember | Where-Object {$_.profile.name -like "*$UserID*"} | Select-Object -ExpandProperty id } if (!$SearchResult) { Write-Error "Unable to match UserID. Message not sent" -ErrorAction Stop} Else {$UserID = $SearchResult} } $Body = @{ # Join each line with two spaces and \n for a new line 'message'=($Message) 'toUserId'=$UserID } | ConvertTo-Json Invoke-RestMethod -Uri "$HabiticaBaseURI/members/send-private-message" -Headers $HabiticaHeader -Method POST -Body $Body } Function Get-HabiticaInboxMessage { <# .SYNOPSIS Returns all inbox messages for the user .PARAMETER Full If -Full is included, the full RESTApi response will be included with details such as success, userV, and appVersion. If not specified, only the data field is returned .EXAMPLE Get-HabiticaInboxMessage Returns all inbox messages for the user .LINK https://habitica.com/apidoc/#api-Inbox-GetInboxMessages #> [cmdletbinding()] param ( $Full = $False ) If ($Full) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/inbox/messages" -Headers $HabiticaHeader -Method GET } Else { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/inbox/messages" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } } Function Get-HabiticaGroupMember { <# .SYNOPSIS Returns information about members of a specific group .DESCRIPTION Returns detailed information about members in a group or party including their name and ID .PARAMETER Group The UUID of a group or 'party' for the user's current party If not provided, the default is 'party' .PARAMETER ID The UUID of a particular member to return in the format of '11111111-2222-3333-4444-555555555555' Only the Username will be returned .PARAMETER UserName The name of the user in the party to return. Only the user's ID will be returned If both the ID and UserName as provided, then the complete information for that user will be returned .PARAMETER Full If -Full is included, the full RESTApi response will be included with details such as success, userV, and appVersion. If not specified, only the data field is returned .PARAMETER includeAllPublicFields If specified, includes all public fields for members, similar to Get-HabiticaMember for each group member .EXAMPLE Get-HabiticaGroupMember Return information about members in the current party .EXAMPLE Get-HabitiaGroupMember -Group '11111111-2222-3333-4444-555555555555' Return the members of the group with specified UUID .EXAMPLE Get-HabitiaGroupMember -ID '11111111-2222-3333-4444-555555555555' Return the username of the specified UUID .EXAMPLE Get-HabitiaGroupMember -UserName 'Example' Return the UUID of the specified group member .LINK https://habitica.com/apidoc/#api-Member-GetMembersForGroup #> # May need to add Query Parameters for more than 30 members and to include more public fields [cmdletbinding()] param ( $Group = 'party', $ID, $UserName, [switch]$Full=$False, [switch]$includeAllPublicFields ) $parameters = '' if ($includeAllPublicFields) { $parameters += "?includeAllPublicFields=true" #true is case sensitive } If ($Full) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$Group/members$parameters" -Headers $HabiticaHeader -Method GET } Else { if (!$ID -and !$UserName) { #If no parameters provided for userid or username Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$Group/members$parameters" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } elseif ($ID -and !$UserName) { #If only UserID provided, return the username Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$Group/members$parameters" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data | Where-Object {$_.id -eq $ID} | Select-Object -ExpandProperty Profile | Select-Object -ExpandProperty Name } ElseIf ($UserName -and !$ID) { #If only the Username provided, return the user ID Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$Group/members$parameters" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data | Where-Object {$_.Profile.Name -eq $Username} | Select-Object -ExpandProperty ID } ElseIf ($ID -and $UserName) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$Group/members$parameters" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data | Where-Object {$_.id -eq $ID -and $_.Profile.Name -eq $Username} } } } Function Get-HabiticaMember { <# .SYNOPSIS Returns information about a specified Habitia user .DESCRIPTION Returns detailed information about a specific user/member including their name, party, inventory, achieviments, etc. .PARAMETER ID The UUID of the member in the format of '11111111-2222-3333-4444-555555555555' This can often be found by listing members in a party which only displays the UUID and then using Get-HabiticaMember to resolve it to a username .EXAMPLE Get-HabitiaMember -ID '11111111-2222-3333-4444-555555555555' Return the specified member based on UUID .LINK https://habitica.com/apidoc/#api-Member-GetMember #> [CmdletBinding()] param ( [Parameter(Mandatory=$True,ValueFromPipeline=$True,ValueFromPipelineByPropertyName=$True)] [Alias("memberID")] $ID, $Full = $False ) If ($Full) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/members/$ID" -Headers $HabiticaHeader -Method GET } Else { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/members/$ID" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } } Function Get-HabiticaTag { <# .SYNOPSIS Returns tags from Habitica .DESCRIPTION Returns either all tags for a user, tags containing a particular name, or tags with a specific ID. By default, returns all tags for the user .PARAMETER Name A partial name of a tag to search for. If not provided, all available tags are returned for the user .PARAMETER TagID The UUID of a specific tag to return .EXAMPLE Get-HabiticaTag Returns all tags for the user .EXAMPLE Get-HabiticaTag -Name 'work' Returns tags with 'work' in the name for the user .EXAMPLE Get-HabitiaTag -tagID '11111111-2222-3333-4444-555555555555' Return the specified tag based on its UUID .LINK https://habitica.com/apidoc/#api-Tag-GetTag #> [CmdletBinding()] param ( [string]$Name, [Alias("id")] $TagID ) if ($Name) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/tags" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data | Where-Object {$_.name -like "*$Name*"} } elseif ($TagID) { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/tags/$TagID" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } else { Return Invoke-RestMethod -Uri "$HabiticaBaseURI/tags" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } } Function Get-HabiticaTask { <# .SYNOPSIS Returns a task object from Habitica .DESCRIPTION Returns a user, group, or challenge task. .PARAMETER Name A partial name of a task to search for. If not provided, all available tasks are returned for the scope .PARAMETER Type The type of task to return [habits, dailys, todos, rewards, completedTodos] .PARAMETER taskID The exact taskID to return, in the format of 11111111-2222-3333-4444-555555555555 .PARAMETER Scope The scope of tasks to return [user, group, challenge] Defaults to current user. If group or challenge, use the ID parameter to provide the ID. If only group is provided, it defaults to the user's current Party .PARAMETER ID Used with Scope value of Group or Challenge for the full ID of the group or challenge to return .PARAMETER Tag A single UUID or name of a tag to retrieve tasks associated .EXAMPLE Get-HabiticaTask Returns all tasks for the user .EXAMPLE Get-HabiticaTask -Scope group Returns all tasks for the user's current party since no ID was provided .EXAMPLE Get-HabitiaTask -Scope Challenge -ID '11111111-2222-3333-4444-555555555555' Returns all tasks for the specified challenge .EXAMPLE Get-HabiticaTask -Tag 'work' Returns all tasks for the user that have the tag "work" .LINK https://habitica.com/apidoc/#api-Task-GetUserTasks https://habitica.com/apidoc/#api-Task-GetTask https://habitica.com/apidoc/#api-Task-GetGroupTasks https://habitica.com/apidoc/#api-Task-GetChallengeTasks #> [CmdletBinding()] param ( [Parameter(Position=0)] [string]$Name, [ValidateSet("habits", "dailys", "todos", "rewards", "completedTodos","")] $Type, $taskId, [ValidateSet('user','group','challenge','party')] $Scope = 'user', $ID, #Party or challenge ID. If not provided, will use current party ID $Tag ) $parameters = '' if ($Type) { $parameters += "?type=$Type" } #Change URI depending on the scope to retrieve switch ($Scope) { 'user' { $Uri = "$HabiticaBaseURI/tasks/user$parameters"; break } 'group' { if (!$ID) { $ID = Get-HabiticaGroup -Group 'party' | Select-Object -ExpandProperty id $Uri = "$HabiticaBaseURI/tasks/group/$ID/$parameters"; break } $Uri = "$HabiticaBaseURI/tasks/group/$ID/$parameters"; break } 'challenge' {$Uri = "$HabiticaBaseURI/tasks/challenge/$ID/$parameters"; break} 'party' { $ID = Get-HabiticaGroup -Group 'party' | Select-Object -ExpandProperty id $Uri = "$HabiticaBaseURI/tasks/group/$ID/$parameters"; break } Default {} } if ($taskID) { #If a specific Task UUID was provided, retrieve only that task $Uri = "$HabiticaBaseURI/tasks/$TaskID" } #If a name was specified, return only tasks that contain that name if ($Name) { $Result = Invoke-RestMethod -Uri $Uri -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data | Where-Object {$_.text -like "*$name*"} } Else { $Result = Invoke-RestMethod -Uri $Uri -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } if ($Tag) { #Ensure it matches UUID format or look up the UUID if ($Tag -notmatch '\w\w\w\w\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w\w\w\w\w\w\w\w\w') { Write-Verbose "Not a valid UUID. Looking up the UUID based on name" $TagUUID += (Get-HabiticaTag $Tag | Select-Object -ExpandProperty id) } else {$TagUUID += $Tag} $Result = $Result | Where-Object {$_.tags -contains $TagUUID} } Return $Result } Function New-HabiticaTask { <# .SYNOPSIS Creates a new task .DESCRIPTION Creates a new task for a user, group, or challenge. Defaults to creating a Todo task for a user with normal priority See the link in Related Links for full documentation .PARAMETER Text The text to be displayed with the task. May also be considered the name. .PARAMETER Type Type of task to create [habit, daily, todo, or reward] Defaults to todo .PARAMETER Tags Tags to assign to the task. A tag ID is needed, but if text is provided, the matching tag id is attempted to be found .PARAMETER Scope The scope of the task to be created. [user, challenge, group] Defaults to User. If challenge or group, also provide -ScopeID .PARAMETER ScopeID If the Scope parameter is challenge or group, the full ID of the challenge or group to create the task for .EXAMPLE New-HabiticaTask -Text 'Example Task' .EXAMPLE New-HabiticaTask -Text 'Hard Task' -Priority 'Hard' .LINK https://habitica.com/apidoc/#api-Task-CreateUserTasks #> [CmdletBinding()] param ( [Parameter(Position=0)] [Alias("Name")] [string]$Text, [ValidateSet("habit", "daily", "todo", "reward")] $Type = "todo", [Alias("Tag")] [string[]]$Tags, [string]$Alias, [ValidateSet("str", "int", "per", "con")] $Attribute, [switch]$CollapseChecklist = $False, [string]$Notes, [string]$Date, [ValidateSet("0.1", "1", "1.5", "2","Trivial","Easy","Medium","Hard")] [string]$Priority = '1', [string[]]$Reminders, [ValidateSet("weekly", "daily")] [string]$Frequency='weekly', [string]$Repeat, [int]$Every=1, [int]$Streak=0, [datetime]$StartDate, [switch]$UpDisabled=$False, [switch]$DownDisabled=$False, [int]$Value=0, [ValidateSet('user','challenge','group')] $Scope='user', $ScopeID ) $Body = @{} $Body.add('text', $Text) $Body.add('type', $Type) if ($Tags) { $TagUUID = @() #Array of tags in UUID format foreach ($Tag in $Tags) { #Ensure it matches UUID format or look up the UUID if ($Tag -notmatch '\w\w\w\w\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w-\w\w\w\w\w\w\w\w\w\w\w\w') { Write-Verbose "Not a valid UUID. Looking up the UUID based on name" $TagUUID += (Get-HabiticaTag $Tag | Select-Object -ExpandProperty id) } else {$TagUUID += $Tag} } $Body.add('tags', $TagUUID) } if ($Alias) {$Body.add('alias', $Alias)} if ($Attribute) {$Body.add('attribute', $Attribute)} if ($CollapseChecklist) {$Body.add('collapseChecklist', $CollapseChecklist)} if ($Notes) {$Body.add('notes', $Notes)} if ($Date -and $Type -eq 'todo') {$Body.add('date', $Date)} if ($Priority -ne '1') { Switch ($Priority) { 'Trivial' {$Priority = '0.1'} 'Easy' {$Priority = '1'} 'Medium' {$Priority = '1.5'} 'Hard' {$Priority = '2'} } $Body.add('priority', $Priority) } if ($Reminders) {$Body.add('reminders', $Reminders)} if ($Frequency -ne 'weekly' -and $Type -eq 'daily') {$Body.add('frequency', $Frequency)} if ($Repeat -and $Type -eq 'daily') {$Body.add('repeat', $Repeat)} #This will need more work if ($Every -gt 1 -and $Type -eq 'daily') {$Body.add('everyX', $Every)} if ($Streak -gt 0 -and $Type -eq 'daily') {$Body.add('streak', $Streak)} if ($StartDate -and $Type -eq 'daily') {$Body.add('startDate', $StartDate)} if ($UpDisabled) {$Body.add('up', $Up)} if ($DownDisabled) {$Body.add('down', $Down)} if ($Value -and $Type -eq 'reward') {$Body.add('value', $Value)} $body | ConvertTo-Json if ($Scope -eq 'user') {Invoke-RestMethod -Uri "$HabiticaBaseURI/tasks/user" -Headers $HabiticaHeader -Method POST -Body ($Body | Convertto-Json) } else {Invoke-RestMethod -Uri "$HabiticaBaseURI/tasks/$Scope/$ScopeID" -Headers $HabiticaHeader -Method POST -Body ($Body | Convertto-Json) } } Function Remove-HabiticaTask { <# .SYNOPSIS Deletes/Removes a task without marking it as complete. Named Remove-HabiticaTask instead of Delete-HabiticaTask as Remove is a supported Powershell verb .PARAMETER Name The text name of a task to remove. The more specific the name of the task the better as it will do a search for tasks containing the provided test .PARAMETER _ID The Habitica _ID value of the task to delete. Can be found by using Get-HabiticaTask and piping it to Remove-HabiticaTask .EXAMPLE Remove-HabiticaTask "Document Functions" Removes a task with a name containing "Document Functions" .EXAMPLE Remove-HabiticaTask -_ID '11111111-2222-3333-4444-555555555555' Remove the task with the specified ID .LINK https://habitica.com/apidoc/#api-Task-DeleteTask #> [CmdletBinding()] param ( [Parameter(Position=0)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName=$true)] [Alias("id")] $_id ) if ($Name) {$_id = Get-HabiticaUserTask -name $Name | Select-Object -ExpandProperty _id} Invoke-RestMethod -Uri "$HabiticaBaseURI/tasks/$_id" -Headers $HabiticaHeader -Method Delete } Function Complete-HabiticaTask { <# .SYNOPSIS Marks a specified task as complete .DESCRIPTION Habitca API calls this "Scoring a task" At this time, supports a single task at a time. Potential future improvements may include handling multiple tasks .PARAMETER Name The text name of a task to complete. The more specific the name of the task the better as it will do a search for tasks containing the provided test .PARAMETER _ID The Habitica _ID value of the task to mark as complete. Can be found by using Get-HabiticaTask and piping it to Complete-HabiticaTask .PARAMETER Direction The direction to mark a task as complete. By default, the direction is Up. If task is a Habit with a negative effect, Down will mark as such .EXAMPLE Complete-HabiticaTask "Document Functions" Completes a task with a name containing "Document Functions" .EXAMPLE Complete-HabiticaTask -_ID '11111111-2222-3333-4444-555555555555' Completes the task with the specified ID .EXAMPLE Get-HabiticaTask 'Document Functions' | Complete-HabiticaTask The task object with a name containing 'Document Functions' is returned and passed through the pipeline to Complete-HabiticaTask and is marked complete .LINK https://habitica.com/apidoc/#api-Task-ScoreTask #> [CmdletBinding()] param ( [Parameter(Position=0)] [string]$Name, [Parameter(ValueFromPipelineByPropertyName=$true)] [Alias("id")] $_id, [ValidateSet("up","down")] $Direction = "up" ) if ($Name) {$_id = Get-HabiticaTask -name $Name | Select-Object -ExpandProperty _id} Invoke-RestMethod -Uri "$HabiticaBaseURI/tasks/$_id/score/$Direction" -Headers $HabiticaHeader -Method POST | Select-Object -ExpandProperty Data } Function Connect-Habitica { <# .SYNOPSIS Sets variables needed for Habitica RESTApi calls and other functions to work properly .DESCRIPTION Uses provided UserID and API Tokens to set the $HabitcaBaseURI and $HabiticaHeader variables. Can also save credentials to a file and if the file exists, will load saved data. Once the Save parameter is used, it will attempt to be loaded automatically when no parameters are provided other than a non-default path If using Powershell Core on Linux or MacOS, saving encrypted credentials to file is not available and will be saved as plain text .PARAMETER UserID The Habitica UserID to configure the connection with. Can be found by logging into Habitica, clicking the user icon in the upper right corner, selecting Settings, then API. A prompt for the API Token will appear after running the fuction to store it securely .PARAMETER Save UserID and API Token will be saved to a file. By default, will be saved to the same folder as the Powershell profile with a name of Habitica.xml unless provided with the Path parameter If saved on Windows, the API Token in the file can only be read by the same user on the same computer. If accessed by a different user or copied to another device, it will not be readable If using Powershell Core on Linux or MacOS, saving encrypted credentials to file is not available and will be saved as plain text .PARAMETER Credential A PSCredential object with UserID for the username and API Token for the Password .PARAMETER Path The full file path including filename to save credentials to or to load saved credentials from. If not provided, the default path is the Powershell Profile folder and file name Habitica.xml .EXAMPLE Connect-Habitica If saved credentials exist, they will be loaded. If no saved credentials exist, a prompt for UserID and API Token will appear .EXAMPLE Connect-Habitica -Save After entering a UserID and API Token, credentials will be saved securely to the local computer. .EXAMPLE Connect-Habbitica -Path C:\Scripts\HabiticaUser.xml Loads saved credentials from the specified path. No prompt for credentials. #> [CmdletBinding()] param ( [Alias("User")] [string]$UserID, $Credential, [switch]$Save, $Path = (Join-Path (Split-Path $profile) Habitica.xml) #Powershell Profile path folder ) if (!$Credential -and !$UserID) { #If saved credentials exist, use those if (Test-Path $Path) { Write-Verbose "Loading saved credentials from $Path" $Credential = Import-Clixml -Path $Path } Else { #If no credential object, prompt to get credentials Write-Verbose 'No saved credentials found. Prompting for credentials' $Credential = Get-Credential -Message "Enter your Habitica UserID for the Username and API Token for the Password. This can be found by logging into Habitica, clicking the user icon in the upper right corner, selecting Settings, then API." } } elseif ($UserID) { Write-Verbose 'UserID provided. Prompting for API Token' $Credential = Get-Credential -UserName $UserID -Message "Enter your Habitica API Token as the Password for user $UserID. This can be found by logging into Habitica, clicking the user icon in the upper right corner, selecting Settings, then API." } if ($Save) { If(!(Test-Path $path)) #If the folder or file does not exist, a particular problem if the folder structure does not exist { New-Item -ItemType 'file' -Force -Path $path } Write-Verbose "Saving credentials to $Path" if ($isLinux -or $isMacOs) { #Unable to save encrypted credentials with non-Windows OS, so converting to plain text $CredentialPlain = [PSCustomObject] @{ UserName = $Credential.UserName Password = $Credential.GetNetworkCredential().Password } [PSCustomObject]$Credential = $CredentialPlain } $Credential | Export-Clixml -Path $Path } $Global:HabiticaBaseURI = 'https://habitica.com/api/v3' if ($isLinux -or $isMacOs) { $Global:HabiticaHeader = @{ "Content-Type" = "application/json" 'x-api-user' = $Credential.UserName 'x-api-key' = $Credential.Password } } Else { $Global:HabiticaHeader = @{ "Content-Type" = "application/json" 'x-api-user' = $Credential.UserName 'x-api-key' = $Credential.GetNetworkCredential().Password } } } Function Disconnect-Habitica { <# .SYNOPSIS Removes variables needed for Habitica RESTApi connections .DESCRIPTION Removes $HabiticaBaseURI and $HabiticaHeader variables used by the Connect-Habitica function which prevents future API calls from functioning #> Try {Remove-Variable -Name 'HabiticaHeader','HabiticaBaseURI' -Scope 'Script' -ErrorAction Stop} Catch {Write-Error 'Not configured to connect to Habitica'} } Function Get-HabiticaUserChallenges { <# .SYNOPSIS Returns all challenges available to the user .DESCRIPTION Get challenges the user has access to. Includes public challenges, challenges belonging to the user's group, and challenges the user has already joined. .LINK https://habitica.com/apidoc/#api-Challenge-GetUserChallenges #> Return Invoke-RestMethod -Uri "$HabiticaBaseURI/challenges/user" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } Function Get-HabiticaUserChallenge { <# .SYNOPSIS Returns challenges currently associated with the current user .DESCRIPTION Returns only challenges the current user is a part of. This is a smaller list compared to Get-HabiticaUserChallenges which lists all challenges a user COULD join #> $Challenges = Get-HabiticaUser | Select-Object -ExpandProperty Challenges $Output = @() foreach ($Challenge in $Challenges) { $Output += Get-HabiticaChallenge -ChallengeID $Challenge } Return $Output } Function Get-HabiticaChallenge { <# .SYNOPSIS Returns a challenge, given it's UUID .DESCRIPTION Returns details about a specific challenge, based on it's UUID .EXAMPLE Get-HabiticaChallenge -ChallengeID 'd46d09fb-760b-4945-9aa1-28184699d158' .LINK https://habitica.com/apidoc/#api-Challenge-GetChallenge #> [CmdletBinding()] param ( [Alias('ID')] $ChallengeID ) Return Invoke-RestMethod -Uri "$HabiticaBaseURI/challenges/$ChallengeID" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } Function Get-HabiticaChallengeMember { <# .SYNOPSIS Returns members of a challenge, given it's UUID .DESCRIPTION Returns members of a specific challenge, based on it's UUID .EXAMPLE Get-HabiticaChallengeMembers -ChallengeID 'd46d09fb-760b-4945-9aa1-28184699d158' .LINK https://habitica.com/apidoc/#api-Member-GetMembersForChallenge #> [CmdletBinding()] param ( [Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True)] [Alias('ID')] $ChallengeID ) Return Invoke-RestMethod -Uri "$HabiticaBaseURI/challenges/$ChallengeID/members" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } Function Get-HabiticaChallengeTask { <# .SYNOPSIS Returns tasks for a challenge, given it's UUID .DESCRIPTION Returns all tasks a specific challenge, based on it's UUID .EXAMPLE Get-HabiticaChallengeTask -ChallengeID 'd46d09fb-760b-4945-9aa1-28184699d158' .EXAMPLE Get-HabiticaUserChallenge | Get-HabiticaChallengeTask .LINK https://habitica.com/apidoc/#api-Task-GetChallengeTasks #> [CmdletBinding()] param ( [Parameter(Mandatory=$True,ValueFromPipelineByPropertyName=$True)] [Alias('ID')] $ChallengeID ) Return Invoke-RestMethod -Uri "$HabiticaBaseURI/tasks/challenge/$ChallengeID" -Headers $HabiticaHeader -Method GET | Select-Object -ExpandProperty Data } ################################################################################ # Habitica Custom functions ################################################################################ function Get-HabiticaQuestMessage { <# .SYNOPSIS Separates party chat log entries to only those related to a single quest .DESCRIPTION When provided the party chat log, will return only the chat log related to a single quest from the start to the completion. Can specific how many quests in the past to return .PARAMETER PartyChat The party chat log to be parsed. If not provided, the current party chat log is retrieved .PARAMETER QuestHistory The number of the past quest to retrieve. Default is 1, indicating the most recent completed quest. Value of 2 would indicate the second most recent quest, etc. If 0, the in progress quest data is returned. .EXAMPLE Get-HabiticaQuestMessage Retrieves the chat log for the most recently completed quest .EXAMPLE Get-HabiticaQuestMessage -QuestHistory 3 Retrieves the chat log for the third most recenty completed quest #> [CmdletBinding()] param ( $PartyChat = (Get-HabiticaGroupChat -GroupID 'party'), [int]$QuestHistory = 1 ) if ($QuestHistory -gt 0) { $QuestCompleted = ($PartyChat | Where-Object {$_.text -match 'receive the rewards|received their rewards'})[($QuestHistory-1)] $QuestStarted = $PartyChat | Where-Object {$_.text -like '*Quest*Started*' -and $_.timestamp -lt $Questcompleted.timestamp } | Select-Object -first 1 $QuestMessages = $PartyChat | Where-Object {$_.timestamp -le $QuestCompleted.timestamp -and $_.timestamp -ge $QuestStarted.timestamp} } else { $QuestStarted = $PartyChat | Where-Object {$_.text -like '*Quest*Started*'} | Select-Object -first 1 $QuestMessages = $PartyChat | Where-Object {$_.timestamp -ge $QuestStarted.timestamp} } $QuestMessages } function Get-HabiticaQuestAction { <# .SYNOPSIS Adds values to PartyChat data about specific actions performed by users .DESCRIPTION When provided the party chat log that is related to a specific quest, will add additional fields for each entry, specifying the username, the action (casts, attacks, found), the target of the action, the damage done, and damage done to the party .PARAMETER Data A subset of the party chat log that is specific to a single quest .EXAMPLE $PartyChat = (Get-HabiticaGroupChat) $QuestActions = (Get-HabiticaQuestMessage -PartyChat $PartyChat | Get-HabiticaQuestAction) #> [cmdletbinding()] param ( [Parameter(ValueFromPipeline=$True)] [Object[]]$Data = (Get-HabiticaQuestMessage) ) Process{ foreach ($Message in $Data) { #$Message | Add-Member Time $Message.timestamp -Force If($Message.text -like '*casts*') { $Message | Add-Member User (($Message.text -split ' casts')[0] -replace ('`','')) -Force $Message | Add-Member Action 'casts' -Force $Message | Add-Member Target ((((($Message.text -split 'casts ')[1]) -split ' for the party.`')[0] -split ' on ')[0]) -Force #Split casting on the party or on a single person } If($Message.text -like '*attacks*') { $Message | Add-Member User (($Message.text -split ' attacks')[0] -replace ('`','')) -Force $Message | Add-Member Action 'attacks' -Force $Message | Add-Member Target ((($Message.text -split 'attacks ')[1] -split ' for')[0]) -Force $Message | Add-Member -Type NoteProperty Damage ([decimal](($Message.text -split 'for ')[1] -split ' damage')[0]) -Force $Message | Add-Member -Type NoteProperty PartyDamage ([decimal](($Message.text -split 'for ')[2] -split ' damage')[0]) -Force } If($Message.text -like '*found*') { $Message | Add-Member User (($Message.text -split ' found')[0] -replace ('`','')) -Force $Message | Add-Member Action 'found' -Force $Message | Add-Member -Type NoteProperty Damage ([decimal](($Message.text -split 'found ')[1] -split ' ')[0]) -Force $Message | Add-Member Target (((($Message.text -split "$($Message.damage) ")[1]) -split '.`')[0]) -Force #Split items found on the damage number to end of line } $Message } } } function Get-HabiticaTopUser { <# .SYNOPSIS Returns the top user(s) of a particular subset of actions .DESCRIPTION When provided a list of actions, such as all actions where Blessing was used, returns the actions for the user(s) who performed it the most .PARAMETER Data A subset of QuestActions performing a specific action that only the top user information is returned .EXAMPLE Get-HabiticaTopUser (Get-HabiticaQuestMessage | Get-HabiticaQuestAction | Where-Object {$_.target -eq 'Blessing'}) Output: User ActionCount Target Action ---- ----------- ------ ------ MallocArray 1 Blessing casts #> [cmdletbinding()] param ( $Data ) Process{ $Output = @() $UniqueUsers = ($Data.User | Select-Object -unique) foreach ($User in $UniqueUsers) { $TotalActions = [PSCustomObject] @{ User = $User ActionCount = ($Data.user | Where-Object {$_ -eq $User} | Measure-Object).count Target = $Data | Where-Object {$_.User -eq $User} | Select-Object -ExpandProperty Target -Unique Action = $Data | Where-Object {$_.User -eq $User} | Select-Object -ExpandProperty Action -Unique } $Output += $TotalActions } $Output = $Output | Sort-Object ActionCount -Descending #Sort so the highest is on top $Output | Where-Object {$_.ActionCount -eq $Output.ActionCount[0]} #Find all items that have the same as the highest in case of multiples } } function Get-HabiticaAward { <# .SYNOPSIS Generates a line of text for a Quest Report that displays the name of the award, the user, and what action .PARAMETER Title The Title of the award to be listed as a free text field. Preferrably end with a : Space is automatically added to the end of this field Defaults to 'MVP:' .PARAMETER Action The QuestAction object associated with the award, such as the first damage done or the user(s) with the most buffs .PARAMETER ActionUser The username associated with the action that is being awarded. If not provided, the user from the Action object is used .PARAMETER ActionCount The number of actions performed by the user to be included in the report, such as the number of attacks, the number of buffs, etc If not provided, the ActionCount value from the Action object is used .PARAMETER ActionName The name of the action performed by the user, such as attacking, collecting, or buffs used If not provided, the unique targets from the Action object is used .PARAMETER HabiticaFormat Indicates that the Habitica Markdown formatting should be used. ` to use a script block to offset the Title ** to bold the ActionName ## to increase the size of the header .EXAMPLE $Actions = ($QuestActions | Where-Object {$_.action -eq 'attacks'}) $Damage = $Actions | Sort-Object timestamp | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "First Hit" -Action $Damage -ActionUser $Damage.User -ActionCount $Damage.Damage -ActionName 'damage' Output: `First Hit:` ARESS with **13.2 damage** #> [CmdletBinding()] param( $Title='MVP:', $Action, $ActionUser = $Action.User, $ActionCount = $Action.ActionCount, $ActionName = ($Action.target | Select-Object -Unique), [switch]$HabiticaFormat ) Begin{ #Change this to Process If($Action) { If(($Action).count -gt 1) { #Multiple actions indicates a tie with multiple users if ($HabiticaFormat) {Return "``$($Title):`` Tie! $($ActionUser -join ' and ') with **$($ActionCount[0]) $($ActionName) each**" #`` to escape the ` character } Else { Return "$($Title): Tie! $($ActionUser -join ' and ') with $($ActionCount[0]) $($ActionName) each" } } Else { #No tie, so awarding to a single user if ($HabiticaFormat) {Return "``$($Title):`` $($ActionUser) with **$($ActionCount[0]) $($ActionName)**" } Else { Return "$($Title): $($ActionUser) with $($ActionCount[0]) $($ActionName)" } } } } } function Format-HabiticaQuestReport { <# .SYNOPSIS Generates a pre-formatted quest report with statistics .DESCRIPTION Generates a report showing how long a quest took to complete, the number of users that participated, who did first damage, most damage total and in one hit, and various buff statistics .PARAMETER QuestActions An object of all quest actions generated by the Get-HabiticaQuestAction command. Can be historic quest data or current .PARAMETER Header The header of the report to show on the first line before the name of the quest. Default is 'Quest Results for:' .EXAMPLE $PartyChat = Get-HabiticaGroupChat $QuestActions = Get-HabiticaQuestMessage -PartyChat $PartyChat | Get-HabiticaQuestAction $Report = Format-HabiticaQuestReport -QuestActions $QuestActions #> [cmdletbinding()] param ( [Parameter(Mandatory=$True)] [object[]]$QuestActions, $Header = 'Quest Results for:' ) $Report = @() $QuestTitle = $QuestActions.text | Select-Object -last 1 # ## for Habitica Markdown formatting for Header 2 # Splitting after the word quest and before has for the quest name $Report += "## $($Header) $(((($QuestTitle) -split ('quest, '))[1] -split (', has '))[0])" $Report += '\n' #Time for quest $Time = (Convertfrom-habiticatimestamp $QuestActions[0].timestamp) - (Convertfrom-habiticatimestamp $QuestActions[-1].timestamp) $Report += "``Time to complete:`` **$($Time.Days) days, $($Time.Hours) hours, $($Time.Minutes) minutes**" #Total number of members who did some type of action $Actions = ($QuestActions | Select-Object User -Unique).count $Report += "``Total participating members:`` **$Actions**" #Top Total Damage $Actions = ($QuestActions | Where-Object {$_.action -eq 'attacks'}) $UserDamageTotals = @() foreach ($User in ($Actions | Select-Object -ExpandProperty User -Unique)) { $UserActions = $Actions | Where-Object {$_.user -eq $User} $TotalDamage = ($UserActions | Measure-Object Damage -Sum).sum $UserActions[0] | Add-Member TotalDamage $TotalDamage -Force $UserDamageTotals += $UserActions[0] } $Damage = $UserDamageTotals | Sort-Object TotalDamage -Desc | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "Most Brutal" -Action $Damage -ActionUser $Damage.User -ActionCount $Damage.TotalDamage -ActionName 'total damage' #First Hit $Actions = ($QuestActions | Where-Object {$_.action -eq 'attacks'}) $Damage = $Actions | Sort-Object timestamp | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "First Hit" -Action $Damage -ActionUser $Damage.User -ActionCount $Damage.Damage -ActionName 'damage' #Hardest Hit $Actions = ($QuestActions | Where-Object {$_.action -eq 'attacks'}) $Damage = $Actions | Sort-Object Damage -desc | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "Most Potent" -Action $Damage -ActionUser $Damage.User -ActionCount $Damage.Damage -ActionName 'in one hit' #Damage to Party $Actions = ($QuestActions | Where-Object {$_.action -eq 'attacks'}) $UserDamageTotals = @() foreach ($User in ($Actions | Select-Object -ExpandProperty User -Unique)) { $UserActions = $Actions | Where-Object {$_.user -eq $User} $TotalDamage = ($UserActions | Measure-Object PartyDamage -Sum).sum $UserActions[0] | Add-Member TotalPartyDamage $TotalDamage -Force $UserDamageTotals += $UserActions[0] } $Damage = $UserDamageTotals | Sort-Object TotalPartyDamage -Desc | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "Stop Hitting Yourself" -Action $Damage -ActionUser $Damage.User -ActionCount $Damage.TotalPartyDamage -ActionName 'total damage to the party' #Items Found $Actions = ($QuestActions | Where-Object {$_.action -eq 'found'}) $UserItemTotals = @() foreach ($User in ($Actions | Select-Object -ExpandProperty User -Unique)) { $UserActions = $Actions | Where-Object {$_.user -eq $User} $TotalDamage = ($UserActions | Measure-Object Damage -Sum).sum $UserActions[0] | Add-Member TotalDamage $TotalDamage -Force $UserItemTotals += $UserActions[0] } $Items = $UserItemTotals | Sort-Object TotalDamage -Desc | Select-Object -First 1 $Report += Get-HabiticaAward -HabiticaFormat -Title "Shiny Hoarder" -Action $Items -ActionUser $Items.User -ActionCount $Items.TotalDamage -ActionName 'items found' #New line to separate damage vs skills $Report += '\n' #$Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Protective Aura'}) $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -match 'Protective Aura|Intimidating Gaze'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Resilient' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Blessing'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Healing' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Ethereal Surge'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Refreshing' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Earthquake'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Wise' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Tools of the Trade'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Crafty' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Valorous Presence'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Inspiring' -Action $Actions #$Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.target -eq 'Intimidating Gaze'}) #$Report += Get-HabiticaAward -HabiticaFormat -Title 'Fearmonger' -Action $Actions $Actions = Get-HabiticaTopUser ($QuestActions | Where-Object {$_.action -eq 'casts'}) $Report += Get-HabiticaAward -HabiticaFormat -Title 'Most Supportive' -Action $Actions -ActionName 'Party Buffs' Return $Report } function Test-HabiticaReportNeeded { <# .SYNOPSIS Tests to see if a quest report is needed .DESCRIPTION Compares the timestamp of the last quest report and the last time a quest was completed. If the last report was before the last completed quest, it returns True .PARAMETER PartyChat The contents of the party's chat history. If saved values are not provided, the current chat log is retrieved by default .PARAMETER QuestActions The contents of quest action provided by Get-HabiticaQuestAction. If saved values are not provided, current list of actions is generated .PARAMETER ReportHeader The header used in the quest report that is used to search for the last time it was put into the chat. Default value is 'Quest Results for:' .EXAMPLE Test-HabiticaReportNeeded #> [CmdletBinding()] param ( $PartyChat = (Get-HabiticaGroupChat -groupid 'party'), $QuestActions = (Get-HabiticaQuestMessage -PartyChat $PartyChat | Get-HabiticaQuestAction), $ReportHeader = 'Quest Results for:' ) #Get the last time a report was posted to the chat $LastReport = $PartyChat | Where-Object {$_.text -like "*$ReportHeader*"} | Select-Object -First 1 #If the report was last posted before the most recent quest completed, post a new one if ($LastReport.timestamp -lt ($QuestActions | Select-Object -ExpandProperty Timestamp -First 1)) { Return $True } Else {$False} } function Format-HabiticaReport { <# .SYNOPSIS Formats a block of text for Habitica's Markdown requirements .DESCRIPTION Receives report data, formats it for Habitica's Markdown requirements Habitica uses two spaces and \n for a new line ' \n' .PARAMETER Report An array of text to be formatted Text will be formatted using Habitica's Markdown requirements to ensure line breaks work as expected. .EXAMPLE Format-HabiticaReport -Report $Report .LINK https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet #> [CmdletBinding()] param ( $Report ) $Body = @{ # Join each line with two spaces and \n for a new line 'message'=($Report -join ' \n') } | ConvertTo-Json #ConverTo-Json doubles the slashes, but Habitica wants a single. Replacing with just n leaves \n in the result $body = $body -replace ('\\n','n') $body } function Format-DiscordReport { <# .SYNOPSIS Formats a block of text for Discord's Markdown requirements .DESCRIPTION Receives report data, formats it for Discord's Markdown requirements Discord uses \r\n for a new line Also puts a header that tags everyone .PARAMETER Report An array of text to be formatted. Text will be formatted using Discord's Markdown requirements to ensure line breaks work as expected. .EXAMPLE Format-DiscordReport -Report $Report .LINK https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet #> [CmdletBinding()] param ( $Report ) $Report[0] = '@everyone - `Quest Results:` ' + ($Report[0] -split 'for: ')[1] $Body = @{ # Join each line with a carriage return and strip out \n from a double space used for Habitica 'content'=($Report -replace ('\\n',"") -join "`n") } | ConvertTo-Json #Discord wants \r\n for a new line, replacing \n in the original, but need two slashes due to escape character $body = $body -replace ('\\n','\r\n') Return $body } function Publish-HabiticaReport { <# .SYNOPSIS Publishes information to the party chat .DESCRIPTION Receives report data, formats it for Habitica's Markdown requirements, and posts it to the party chat .PARAMETER Report An array of text to be published. Text will be formatted using Habitica's Markdown requirements to ensure line breaks work as expected. .EXAMPLE Publish-HabiticaReport -Report $Report .LINK https://habitica.fandom.com/wiki/Markdown_Cheat_Sheet #> [CmdletBinding()] param ( $Report ) #Remove special characters that cause problems in Habitica and Discord $Report = Format-HabiticaCharacters -Report $Report $Body = Format-HabiticaReport $Report Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/party/chat" -Headers $HabiticaHeader -Method POST -Body $Body } function Format-HabiticaCharacters { <# .SYNOPSIS Replaces special characters that are not handled properly when sending messages or posts .DESCRIPTION Some unicode characters do not pass through to messages correctly when processed by this method. This function replaces the problematics characters with their best aproximations so results do not have odd or missing characters. Some characters include ï .PARAMETER Report An array of text to be published. .EXAMPLE $Report = Format-HabiticaCharacters -Report $Report #> [CmdletBinding()] param ( $Report = $Report ) $Report = $Report -replace 'ï','i' $Report = $Report -replace '軍','' $Report = $Report -replace '曹','' $Report = $Report -replace 'ø','o' $Report } function Publish-DiscordReport { <# .SYNOPSIS Posts information to a Discord channel .DESCRIPTION Receives report data, formats it for Discord's Markdown requirements, and posts it to the Discord Webhook URL To create or retrieve a Webhook URL, in Discord: Right click on a channel, select Edit Channel, Webhooks, Create Webook and copy the URL into $DiscordWebhookUrl .PARAMETER Report An array of text to be published. Text will be formatted using Discords Markdown requirements to ensure line breaks work as expected. .PARAMETER DiscordWebhookUrl The full Webhook URL for the desired Discord channel .EXAMPLE Publish-DiscordReport -Report $Report -DiscordWebhookUrl https://discordapp.com/api/webhooks/100899273626745649/WzfQpTrK... .LINK https://www.gngrninja.com/script-ninja/2018/3/10/using-discord-webhooks-with-powershell #> [CmdletBinding()] param ( $Report = $Report, $DiscordWebhookUrl = $DiscordWebhookUrl ) #Remove special characters that cause problems in Habitica and Discord $Report = Format-HabiticaCharacters -Report $Report $content = Format-DiscordReport $Report Invoke-RestMethod -Uri $DiscordWebhookUrl -Method Post -Body $content } Function Get-HabiticaQuestStatus { <# .SYNOPSIS Returns the status of a party quest .DESCRIPTION Returns the status of a party quest, either none, pending, or active. Pending is one that invites have been sent, but has not been started .PARAMETER PartyData Output of Get-HabiticaGroup that contains information about the party. If parameter is not supplied, it will get current party data. If previous Get-HabiticaGroup information was saved for a point-in-time reference or if an attempt to limit traffic, it can be supplied. .EXAMPLE Get-HabiticaGroup Pending #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline=$True)] $PartyData = (Get-HabiticaGroup) ) Process { #$PartyData.quest.members is not a string by default but multiple properties. Converting to string for comparison if ($PartyData.quest.active -eq $False -and ($PartyData.quest.members | Out-String) -notlike '*:*') { Write-Verbose 'No Quest is active or pending' Return 'None' } Elseif ($PartyData.quest.active -eq $False -and $PartyData.quest.key -ne $Null) { Write-Verbose "Quest $($PartyData.quest.key) is pending" Return 'Pending' } Else {Return 'Active'} } } Function ConvertFrom-HabiticaTimestamp { <# .SYNOPSIS Converts Habitica timestamp fields to human readable date and time .DESCRIPTION Habitica time is in Unix Epoch time, multipled by 1000. This function converts this to a human readable date and time format .PARAMETER HabiticaTimestamp The Habitica timestamp to convert .EXAMPLE $Timestamp = Get-HabiticaGroupChat | Select-Object -ExpandProperty timestamp -First 1 ConvertFrom-HabiticaTimestamp $Timestamp Return the timestamp of the most recent Party chat #> [cmdletbinding()] param ( [Parameter(ValueFromPipeline=$True)] $HabiticaTimestamp ) [timezone]::CurrentTimeZone.ToLocalTime(([datetime]'1/1/1970').AddSeconds($HabiticaTimestamp/1000)) } Function ConvertTo-HabiticaTimestamp { <# .SYNOPSIS Converts datetime fields to Habitica timestamp fields .DESCRIPTION Habitica time is in Unix Epoch time, multipled by 1000. This function converts a human readable date and time to the Habitica format .PARAMETER Date A date field to convert .EXAMPLE ConvertTo-HabiticaTimestamp Converts the current date and time into Habitica timestamp format .EXAMPLE ConvertTo-HabiticaTimestamp '1/1/2018 4:00 pm' Converts the specified date and time into Habitica timestamp #> [cmdletbinding()] param ( #Casting as Decimal to keep it from casting as a string $Date = (Get-Date) ) [decimal]$DateTime = Get-Date $Date -UFormat %s #Habitica time is in Unix Epoc time multiplied by 1000. Using Long to round to whole number Return [long]($DateTime * 1000) } Function Get-HabiticaQuestQueue { <# .SYNOPSIS Retrieves a saved list of users and what quest they are scheduled to complete next .DESCRIPTION Retrieves a saved list of quests from the -Path location to be used with sending reminders to users .PARAMETER Path The full file path including filename to load saved quest queue from. If not provided, the default path is the Powershell Profile folder and file name HabiticaQuestQueue.xml .EXAMPLE $QuestList = Get-HabiticaQuestQueue #> [CmdletBinding()] param ( $Path = (Join-Path (Split-Path $profile) HabiticaQuestQueue.xml) #Powershell Profile path folder ) if (Test-Path $Path) { Write-Verbose "Loading saved quest queue from $Path" Return Import-Clixml -Path $Path } Else { #If no credential object, prompt to get credentials Write-Verbose 'No saved quest queue found. Please use Save-HabiticaQuestQueue to create a saved list' } } Function Save-HabiticaQuestQueue { <# .SYNOPSIS Saves list of users and what quest they are scheduled to complete next .DESCRIPTION Saves a list of quests to the -Path location to be used with sending reminders to users .PARAMETER QuestQueue Variable containing the quest queue to be saved .PARAMETER Path The full file path including filename to save quest queue list If not provided, the default path is the Powershell Profile folder and file name HabiticaQuestQueue.xml .EXAMPLE Save-HabiticaQuestQueue #> [CmdletBinding()] param ( [Parameter(Mandatory=$True)] $QuestQueue, $Path = (Join-Path (Split-Path $profile) HabiticaQuestQueue.xml) #Powershell Profile path folder ) Write-Verbose "Saving quest queue to $Path" $QuestQueue | Export-Clixml -Path $Path } Function Send-HabiticaQuestQueueReminder { <# .SYNOPSIS Sends a private message to the next user in the queue to remind them to start the quest .PARAMETER QuestQueue Variable containing the quest queue to use or can use Get-HabiticaQuestQueue to load current queue from saved file .EXAMPLE Send-HabiticaQuestQueueReminder -QuestQueue (Get-HabiticaQuestQueue) #> [CmdletBinding()] param ( $QuestQueue ) write-Verbose "Sending private message to $($QuestQueue[0].User) asking them to please start $($QuestQueue[0].Quest)" Send-HabiticaPrivateMessage -UserID $QuestQueue[0].User -Message "Your quest is next. Please start $($QuestQueue[0].Quest)" } Function Add-HabiticaQuestQueueEntry { <# .SYNOPSIS Adds another entry to the end of the quest queue list .PARAMETER QuestQueue Variable containing the existing quest queue to use or can use Get-HabiticaQuestQueue .PARAMETER User The Habitica name of the user to be added to the queue .PARAMETER Quest The name of the Habitica quest for the user to start .EXAMPLE $QuestQueue = Get-HabiticaQuestQueue $QuestQueue = Add-HabiticaQuestQueueEntry $QuestQueue -user 'User1' -quest 'Recidivate, Part 1: The Moonstone Chain' Save-HabiticaQuestQueue -QuestQueue $QuestQueue #> [CmdletBinding()] param ( [Object[]]$QuestQueue, [Parameter(Mandatory=$True)] [string]$User, [Parameter(Mandatory=$True)] [string]$Quest ) $QuestQueue += [PSCustomObject] @{ User = $User Quest = $Quest } Return $QuestQueue } Function Remove-HabiticaQuestQueueEntry { <# .SYNOPSIS Removes the first entry of the QuestQueue and returns the remaining entries .PARAMETER QuestQueue Variable containing the quest queue to use or can use Get-HabiticaQuestQueue .EXAMPLE $QuestQueue = Remove-HabiticaQuestQueueEntry -QuestQueue $QuestQueue #> [CmdletBinding()] param ( $QuestQueue = (Get-HabiticaQuestQueue) ) for ($i=1; $i -le $QuestQueue.count; $i++) {$QuestQueue | Select-Object -Index $i} } Function Skip-HabiticaQuestQueueEntry { <# .SYNOPSIS Skips the next entry of the QuestQueue and saves the remaining entries .Description Used if a player will not be starting the next quest entry and needs to be skipped .PARAMETER QuestQueue Variable containing the quest queue to use or can use Get-HabiticaQuestQueue .EXAMPLE Skip-HabiticaQuestQueueEntry #> [CmdletBinding()] param ( $QuestQueue = (Get-HabiticaQuestQueue) ) $QuestQueue = Remove-HabiticaQuestQueueEntry -QuestQueue $QuestQueue Save-HabiticaQuestQueue -QuestQueue $QuestQueue } Function Connect-Discord { <# .SYNOPSIS Sets variables needed for Discord Webhook usage .DESCRIPTION Used to configure a Discord Webhook connection for any functions that can post to a channel To get the Webhook URL if you have appropriate Discord permissions: Right click on a Discord channel, select Edit Channel, Webhooks, Create Webook When prompted, but the URL into the Password field of the credential prompt .PARAMETER Save Webook URL will be saved to a file. By default, will be saved to the same folder as the Powershell profile with a name of Discord- followed by the computer name unless provided with the Path parameter File is saved in XML format. If saved on Windows, the Webhook URL in the file can only be read by the same user on the same computer. If accessed by a different user or copied to another device, it will not be readable If using Powershell Core on Linux or MacOS, saving encrypted text to file is not available and will be saved as plain text .PARAMETER Path The full file path including filename to save credentials to or to load saved credentials from. If not provided, the default path is the Powershell Profile folder and file name Discord-'ComputerName'.xml .EXAMPLE Connect-Discord If saved Webhook exist, they will be loaded. If no saved Webhook exist, a prompt for the URL will appear .EXAMPLE Connect-Discord -Save After being prompted for the URL, it will be saved securely to the local computer if on Windows .EXAMPLE Connect-Discord -Path C:\Scripts\Discord-Testing.xml Load saved URL from the specified path. No prompt for a URL #> [CmdletBinding()] param ( [switch]$Save, $Path = (Join-Path (Split-Path $profile) "Discord-$env:Computername.xml") #Powershell Profile path folder ) #If saved file exists, use it if ((Test-Path $Path) -and !$Save) { Write-Verbose "Loading Webhook from $Path" $Credential = Import-Clixml -Path $Path } Else { #If no saved file, prompt for URL Write-Verbose 'No saved file found. Prompting for Webhook' $Credential = Get-Credential -UserName "Discord" -Message "Enter your full Discord Webhook URL in the Password field. This can be found by logging into Discord, Right click on a Discord channel, select Edit Channel, Webhooks, Create Webook" } if ($Save) { If(!(Test-Path $path)) #If the folder or file does not exist, a particular problem if the folder structure does not exist { New-Item -ItemType 'file' -Force -Path $Path } Write-Verbose "Saving credentials to $Path" if ($isLinux -or $isMacOs) { #Unable to save encrypted credentials with non-Windows OS, so converting to plain text $CredentialPlain = [PSCustomObject] @{ UserName = $Credential.UserName Password = $Credential.GetNetworkCredential().Password } [PSCustomObject]$Credential = $CredentialPlain } $Credential | Export-Clixml -Path $Path } #Making variable available outside of this function for use with others. #Powershell Core on Linux does not appear to work like Windows does with $Script:DiscordWebhookUrl so changed to Global if ($isLinux -or $isMacOs) { $Global:DiscordWebhookUrl = $Credential.Password } Else { $Global:DiscordWebhookUrl = $Credential.GetNetworkCredential().Password } #Return $Global:DiscordWebhookUrl } Function Publish-HabiticaQuestReport { <# .SYNOPSIS Generates a report to the Party chat with stats about the most recent quest .DESCRIPTION Generates a Quest Results report showing details about the most recent quest. Originally designed by Habitica user Dispatch009 Shows various stats including how long the quest took, who did the most damage, who did the most party buffs and more Will only send the report if one has not been generated since the last quest was completed, allowing it to be ran on a schedule such as every hour without spammming the chat .PARAMETER Discord If desired, the same report can be sent to a Discord channel through a Webhook. See Connect-Discord for details on where to get the URL and save credentials .PARAMETER QueueReminder Reminders will be sent to the next user in the queue to being their quest as a private message. To setup a queue, use the following commands $QuestQueue = Add-HabiticaQuestQueueEntry -user 'User1' -quest 'Recidivate, Part 1: The Moonstone Chain' $QuestQueue = Add-HabiticaQuestQueueEntry $QuestQueue -user 'User2' -quest 'Recidivate, Part 3: The Moonstone Chain' Save-HabiticaQuestQueue -QuestQueue $QuestQueue .EXAMPLE Publish-HabiticaQuestReport Generates the report using default credentials from Connect-Habitica and publishes them to the chat if it has not already done so .EXAMPLE Publish-HabiticaQuestReport -Discord Generates the report and also publishes it to a Discord channel #> [CmdletBinding()] param ( [switch]$Discord, [switch]$QueueReminder ) Connect-Habitica $PartyChat = Get-HabiticaGroupChat -GroupID 'party' $QuestActions = Get-HabiticaQuestMessage -PartyChat $PartyChat | Get-HabiticaQuestAction $Report = Format-HabiticaQuestReport -QuestActions $QuestActions #Checking if the last report was before the last quest completed and if so, will post it if (Test-HabiticaReportNeeded -PartyChat $PartyChat -QuestActions $QuestActions){ Publish-HabiticaReport $Report if ($Discord) { if (!$DiscordWebhookUrl) { Write-Verbose "Not already connected to Discord. Running Connect-Discord to load saved data or prompt for the URL" Connect-Discord } Publish-DiscordReport $Report } if ($QueueReminder) { Write-Verbose 'Processing quest queue' $QuestQueue = Get-HabiticaQuestQueue Send-HabiticaPrivateMessage -UserID $QuestQueue[0].User -Message "Your quest is next. Please start $($QuestQueue[0].Quest)" if ($Discord) {Publish-DiscordReport "$($QuestQueue[0].user) is up next with quest $($QuestQueue[0].quest)"} $QuestQueue = Remove-HabiticaQuestQueueEntry -QuestQueue $QuestQueue Save-HabiticaQuestQueue -QuestQueue $QuestQueue } } } Function Publish-HabiticaQuestPendingNotice { <# .SYNOPSIS Checks how long a quest has been in the pending state and attempts to start the quest or notify leaders to start the quest .DESCRIPTION When ran, checks to see if a quest is pending. If so, a custom chat message is published to reference the elapsed time. When the PendingQuestTimer value is exceeded (defaults to 24 hours minus 0.1 hours) the quest is attempted to be started using the Habitica quest account running the command. If the account is the party or quest leader, the quest will automatically start. If the account is not the party or quest leader, a private message will be sent to both leaders asking them to start the quest .PARAMETER PendingHeader The header text to be put into the party chat followed by "started by <QuestLeaderName>". Defaults to: Invites sent for pending quest .PARAMETER PendingQuestTimer Number of hours the quest will be pending before a message is sent. Defaults to 24 hours. Is actually subtracting 0.1 hours so if ran on an hourly schedule it will not run for an extra cycle. .EXAMPLE Publish-HabiticaQuestPendingNotice Uses default values to send pending notices to the party chat and reminders to the quest owner and party leader after 24 hours .EXAMPLE Publish-HabiticaQuestPendingNotice -PendingHeader 'Fly you fools! In 6 hours we begin a quest' -PendingQuestTimer 6 Modifies the party chat message to custom text and will send messages to the quest owner and party leader after 6 hours #> [CmdletBinding()] param ( [string]$PendingHeader = 'Invites sent for pending quest', [int]$PendingQuestTimer = 24 ) Connect-Habitica $PartyData = Get-HabiticaGroup $QuestStatus = $PartyData | Get-HabiticaQuestStatus if ($QuestStatus -eq 'Pending') { #If there is a pending quest $PartyChat = Get-HabiticaGroupChat -GroupID 'party' $QuestActions = (Get-HabiticaQuestMessage -PartyChat $PartyChat | Get-HabiticaQuestAction) $PendingNotice = $PartyChat | Where-Object {$_.text -like "*$PendingHeader*"} | Select-Object -First 1 #If no pending notice posted or it was last posted before previous quest ended if (!$PendingNotice -or $PendingNotice.timestamp -lt $QuestActions[0].timestamp) { Publish-HabiticaReport "$PendingHeader started by $(Get-HabiticaGroupMember -id $PartyData.quest.leader)" } else { #See if the PendingQuestTimer has been exceeded if ((ConvertFrom-HabiticaTimestamp $pendingnotice.timestamp).addhours($PendingQuestTimer-0.1) -lt (Get-Date)) { #Subtracting 0.1 so it is just before a full 24 hours #If the user running this script is the QuestLeader or GroupLeader, force start it Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/party/quests/force-start" -Headers $HabiticaHeader -Method POST -ErrorAction Continue -ErrorVariable RestError If ($RestError) { #If an error is logged (Because quest is in progress or you don't have permission to start it) send a private message to quest owner #First check to see if the desired message has already been sent $Messages = Get-HabiticaInboxMessage $Body = "The current quest invite has been pending for more than $PendingQuestTimer hours. Please start the quest" $PrivateMessage = $Messages | Where-Object {$_.text -eq $Body} | Select-Object -First 1 #If never sent a private message or the last private message with the proper header was sent was prior to the pending quest notice, send the message if (!$PrivateMessage -or (Get-Date $PrivateMessage.timestamp) -lt (ConvertFrom-HabiticaTimestamp $pendingnotice.timestamp)) { Send-HabiticaPrivateMessage -UserID $PartyData.quest.leader -Message $Body #Quest Leader Send-HabiticaPrivateMessage -UserID $PartyData.leader.id -Message $Body #Party Leader } $RestError = $Null } } } } } Function Start-HabiticaQuest { <# .SYNOPSIS Starts a pending quest .DESCRIPTION Attempts to start a pending quest if the user is the quest owner or part leader. Otherwise, returns an error to variable RestError .PARAMETER GroupID The UUID of a group, or common names of 'party' for the user party and 'habitrpg' for tavern are accepted Defaults to 'party' .EXAMPLE Start-HabiticaQuest .LINK https://habitica.com/apidoc/#api-Quest-ForceQuestStart #> [CmdletBinding()] param( [string]$GroupID='party' ) Invoke-RestMethod -Uri "$HabiticaBaseURI/groups/$GroupID/quests/force-start" -Headers $HabiticaHeader -Method POST -ErrorAction Continue } New-Alias -Name Get-HabiticaPartyChat -Value Get-HabiticaGroupChat New-Alias -Name Get-HabiticaParty -Value Get-HabiticaGroup Export-ModuleMember -Alias * -Function * |