├── .github └── workflows │ └── readme-example.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── action.yml ├── helpers.ps1 └── main.ps1 /.github/workflows/readme-example.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Backup Mastodon Account 3 | on: 4 | workflow_dispatch: 5 | jobs: 6 | backup: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout the code 10 | uses: actions/checkout@v3 11 | 12 | - name: Backup Mastodon Account 13 | uses: potatoqualitee/fossilize@v1 14 | id: backup 15 | with: 16 | server: tech.lgbt 17 | env: 18 | ACCESS_TOKEN: "${{ secrets.ACCESS_TOKEN }}" -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "ms-vscode.PowerShell" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | // Place your settings in this file to overwrite default and user settings. 2 | { 3 | // cleans up whitespace 4 | "files.trimTrailingWhitespace": true, 5 | // formatting style this project adheres to: https://en.wikipedia.org/wiki/Indentation_style#Variant:_1TBS_(OTBS) 6 | "powershell.codeFormatting.preset": "OTBS", 7 | "powershell.codeFormatting.autoCorrectAliases": false, 8 | // editor settings for formatting standards on this project 9 | "editor.tabSize": 4, 10 | "editor.detectIndentation": false, 11 | "editor.insertSpaces": true, 12 | "editor.formatOnSave": true, 13 | // editor settings that will auto add the closing quote or curly brace 14 | "editor.autoClosingBrackets": "always", 15 | "editor.autoClosingQuotes": "always", 16 | "editor.autoSurround": "languageDefined", 17 | // now your double clicks on a variable names include the dollar sign '$' 18 | // https://twitter.com/TylerLeonhardt/status/1102749805233737729 19 | "[powershell]": { 20 | "editor.wordSeparators": "`~!@#%^&*()-=+[{]}\\|;:'\",.<>/?" 21 | } 22 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Chrissy LeMaire 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fossilize - Mastodon account backup tool 2 | 3 | This Action will help you backup Mastodon account items to CSV files, including: 4 | 5 | * Follows 6 | * Mutes 7 | * Account blocks 8 | * Lists 9 | * Bookmarks 10 | * Domain blocks 11 | * Followers 12 | * Posts 13 | 14 | The export is performed using the Mastodon API and an Access Token. 15 | 16 | Note that Followers and Posts cannot be imported using Mastodon's import web interface. Also, posts are exported in JSON format because they are more complex. 17 | 18 | ## Documentation 19 | 20 | Here's how you'd export your follows, lists, blocks, mutes, domain_blocks, bookmarks, followers, and posts if your account is on the dataplatform.social Mastodon instance. This will export the files to `./backups` then attach a zip of the `./backups` as an artifact to the workflow run. 21 | 22 | ```yaml 23 | - name: Backup account to files 24 | uses: potatoqualitee/fossilize@v2 25 | with: 26 | server: dataplatform.social 27 | env: 28 | ACCESS_TOKEN: "${{ secrets.ACCESS_TOKEN }}" 29 | ``` 30 | 31 | Note that Mastodon limits API calls to 300 per 5 minutes, which averages 1 second so each call will have a delay of one second, so that's why there seems to be a slight delay. 32 | 33 | # Usage 34 | 35 | ## Pre-requisites 36 | 37 | ### Get a Mastodon Bearer Token 38 | 39 | A Mastodon token is required for this Action to work. Fortuantely, it's very easy to get one. 40 | 41 | Go to your Mastodon profile/client/webpage and click Preferences -> Development -> New Application -> Application name: Whatever you like, I named mine Imports -> Limit Permissions (optional) -> Submit 42 | 43 | > **Note** 44 | > 45 | > If you limit your permissions too much when you create the app, you may need to recreate it. I was too strict with my permissions and it _looked_ like I could edit them but the edit is like a secondary scope 46 | 47 | Click new application link -> Your access token 48 | 49 | ### Add GitHub Secrets 50 | 51 | Once you have your authentication information, you will need to them to your [repository secrets](https://docs.github.com/en/codespaces/managing-codespaces-for-your-organization/managing-encrypted-secrets-for-your-repository-and-organization-for-github-codespaces#adding-secrets-for-a-repository). 52 | 53 | I named my secret `ACCESS_TOKEN`. You can use any secretname you want, though you must ensure that your environmental variables are named appropriately, as seen in the sample code. 54 | 55 | ### Create workflows 56 | 57 | Finally, create a workflow `.yml` file in your repositories `.github/workflows` directory. An [example workflow](#example-workflow) is available below. For more information, reference the GitHub Help Documentation for [Creating a workflow file](https://help.github.com/en/articles/configuring-a-workflow#creating-a-workflow-file). 58 | 59 | ## Inputs 60 | 61 | * `server` - Your Mastodon server. If you are dbatools@dataplatform.social, this would be dataplatform.social. 62 | * `path` - The path to the directory that will hold the CSV files, defaults to `./backups`. This Action will create the directory if it does not exist. 63 | * `type` - Which items to backup. Options include: follows, lists, blocks, mutes, domain_blocks, bookmarks, followers, posts and all. Defaults to all. 64 | * `auto-artifact` - Attach the csv files as an artifact to this workflow. Default is true. 65 | * `artifact-name` - The name of the artifact. Default is mastodon-backup. 66 | * `verbose` - Show verbose output. Defaults to true. 67 | 68 | ## Outputs 69 | 70 | * `csv-path` - The backup directory file path 71 | 72 | ### Example workflows 73 | 74 | Use the `Fossilize` action to backup your account to CSV each night at midnight and attach the zip as an artifact 75 | 76 | ```yaml 77 | name: Backup Mastodon Account 78 | on: 79 | workflow_dispatch: 80 | schedule: 81 | - cron: "0 0 * * *" 82 | jobs: 83 | backup: 84 | runs-on: ubuntu-latest 85 | steps: 86 | - name: Checkout the code 87 | uses: actions/checkout@v3 88 | 89 | - name: Backup Mastodon Account 90 | uses: potatoqualitee/fossilize@v2 91 | id: backup 92 | with: 93 | server: dataplatform.social 94 | env: 95 | ACCESS_TOKEN: "${{ secrets.ACCESS_TOKEN }}" 96 | ``` 97 | 98 | ### Details 99 | 100 | Here's some extra examples for the inputs. 101 | 102 | | Input | Example | Another Example | And Another 103 | | --- | --- | --- | --- | 104 | | server | dataplatform.social | dbatools@dataplatform.social | https://dataplatform.social 105 | | path | /tmp/backups | ./backups 106 | 107 | ### Want to run this locally? 108 | 109 | Just add your `$env:ACCESS_TOKEN` environmental variables to your `$profile` and reload, clone the repo, change directories, modify this command and run. 110 | 111 | ```powershell 112 | ./main.ps1 -Server yourinstance.tld -Path C:\temp\backups 113 | ``` 114 | 115 | ## Contributing 116 | Pull requests are welcome! 117 | 118 | ## You may also enjoy... 119 | 120 | * [Mastodon Influx](https://github.com/marketplace/actions/mastodon-influx) - This Action will help you import CSV files to Mastodon, including: Follows, Mutes, Account blocks, Lists, Bookmarks, and Domain blocks. 121 | * [Twitter Exodus](https://github.com/marketplace/actions/twitter-exodus) - This Action helps Twitter communities find members on Mastodon. Searches lists, hashtags, account followers and more for Mastodon links in their name, bio or pinned tweet. Once found, their info is exported to a CSV file that can be imported into Mastodon. 122 | * [Using GitHub Pages to Setup an Alias on Mastodon](https://blog.netnerds.net/2022/11/alias-mastodon-github-pages/) - This tutorial can help you keep your social media presence without running your own Mastodon server. 123 | 124 | ## TODO 125 | You tell me! I'm open to suggestions. But also 126 | 127 | * Add more account compontents after I make a Mastodon module, then I can just export it from there 128 | 129 | ## License 130 | The scripts and documentation in this project are released under the [MIT License](LICENSE) 131 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Mastodon Fossilize" 2 | branding: 3 | icon: "archive" 4 | color: "gray-dark" 5 | description: "Mastodon account backup tool" 6 | inputs: 7 | server: 8 | description: "Your Mastodon server. If you are dbatools@dataplatform.social, this would be dataplatform.social." 9 | required: true 10 | path: 11 | description: "The path to the directory that will hold the CSV files, defaults to `./backups`. This Action will create the directory if it does not exist." 12 | default: "./backups" 13 | required: false 14 | type: 15 | description: "Which items to backup. Options include: follows, lists, blocks, mutes, domain_blocks, bookmarks, followers, posts and all. Defaults to all." 16 | default: "all" 17 | required: false 18 | auto-artifact: 19 | description: "Attach the csv files as an artifact to this workflow. Default is true." 20 | required: false 21 | default: "true" 22 | artifact-name: 23 | description: "The name of the artifact. Default is mastodon-backup." 24 | default: "mastodon-backup" 25 | verbose: 26 | description: "Show verbose output. Defaults to true." 27 | required: true 28 | default: "true" 29 | runs: 30 | using: "composite" 31 | steps: 32 | - id: exports 33 | shell: pwsh 34 | run: | 35 | if ("${{ inputs.verbose }}" -eq $true) { 36 | $VerbosePreference = "Continue" 37 | } 38 | 39 | $env:MASTODON_SERVER = "${{ inputs.server }}" 40 | 41 | Write-Verbose "Running script" 42 | 43 | if (-not $env:ACCESS_TOKEN) { 44 | throw '$env:ACCESS_TOKEN is empty. Did you setup your secrets? Check the docs.' 45 | } else { 46 | $env:ACCESS_TOKEN = $env:ACCESS_TOKEN.Replace("Bearer ", "") 47 | } 48 | 49 | function ConvertTo-Array ($str) { 50 | # Let them add values in a variety of ways 51 | if ($str -match [System.Environment]::NewLine) { 52 | $results = $str.Split([System.Environment]::NewLine, [System.StringSplitOptions]::RemoveEmptyEntries) 53 | } else { 54 | $results = ($str -split ',').Split(" ", [System.StringSplitOptions]::RemoveEmptyEntries) 55 | } 56 | 57 | foreach ($result in $results) { 58 | $result.TrimStart().TrimEnd() 59 | } 60 | } 61 | 62 | $params = @{ 63 | Server = ConvertTo-Array "${{ inputs.server }}" 64 | Path = "${{ inputs.path }}" 65 | Type = ConvertTo-Array "${{ inputs.type }}" 66 | } 67 | 68 | ${{ github.action_path }}/main.ps1 @params 69 | 70 | 71 | - uses: actions/upload-artifact@v4 72 | if: inputs.auto-artifact == 'true' 73 | with: 74 | name: ${{ inputs.artifact-name }} 75 | path: | 76 | ${{ steps.exports.outputs.csv-path }} 77 | -------------------------------------------------------------------------------- /helpers.ps1: -------------------------------------------------------------------------------- 1 | $PSDefaultParameterValues["Invoke-Request:UseWebRequest"] = $true 2 | $PSDefaultParameterValues["Invoke-Request:Method"] = "GET" 3 | function Get-WhoFollowsMe { 4 | [CmdletBinding()] 5 | param( 6 | [psobject[]]$Id = $script:myid 7 | ) 8 | $script:link = $null 9 | Invoke-Request -Path "accounts/$Id/followers?limit=80" 10 | 11 | while ($null -ne $script:link) { 12 | Invoke-Request -Path $script:link 13 | } 14 | } 15 | 16 | function Get-WhoAmIFollowing { 17 | [CmdletBinding()] 18 | param( 19 | [psobject[]]$Id = $script:myid 20 | ) 21 | $script:link = $null 22 | Invoke-Request -Path "accounts/$Id/following?limit=80" 23 | 24 | while ($null -ne $script:link) { 25 | Invoke-Request -Path $script:link 26 | } 27 | } 28 | 29 | function Invoke-Request { 30 | param( 31 | [string]$Method = "GET", 32 | [string]$Server = $env:MASTODON_SERVER, 33 | [Parameter(Mandatory)] 34 | [Alias("Uri")] 35 | [string]$Path, 36 | [string]$Version = "v1", 37 | [string]$Body, 38 | [string]$OutFile, 39 | [switch]$UseWebRequest, 40 | [switch]$Raw 41 | ) 42 | 43 | if ($Path -match "://") { 44 | $url = $Path 45 | } else { 46 | $url = "https://$Server/api/$Version/$Path" 47 | } 48 | 49 | Write-Verbose "Going to $url" 50 | $parms = @{ 51 | Uri = $url 52 | ErrorAction = "Stop" 53 | Headers = @{ Authorization = "Bearer $env:ACCESS_TOKEN" } 54 | Method = $Method 55 | Verbose = $false # too chatty 56 | } 57 | 58 | if ($Body) { 59 | $parms.Body = $Body 60 | $parms.ContentType = "application/json" 61 | } 62 | 63 | if ($OutFile) { 64 | $parms.OutFile = $OutFile 65 | } 66 | 67 | if ($UseWebRequest) { 68 | $response = Invoke-WebRequest @parms 69 | 70 | if ($response.Headers.Link) { 71 | $script:link = $response.Headers.Link.Split(";") | Where-Object { $PSitem -match "max_id" } | Select-Object -First 1 72 | if ($script:link) { 73 | foreach ($term in "<", ">") { 74 | $script:link = $script:link.Replace($term, "") 75 | } 76 | } 77 | } else { 78 | $script:link = $null 79 | } 80 | 81 | if (-not $OutFile -and -not $Raw) { 82 | $response.Content | ConvertFrom-Json -Depth 10 83 | } 84 | 85 | if ($Raw) { 86 | $response.Content 87 | } 88 | } else { 89 | Invoke-RestMethod @parms 90 | } 91 | 92 | # This keeps it from calling too many times in a 5 minute period 93 | Start-Sleep -Seconds 1 94 | } 95 | 96 | function Get-Account { 97 | [CmdletBinding()] 98 | param( 99 | [Parameter(Mandatory)] 100 | [string[]]$UserName 101 | ) 102 | 103 | foreach ($user in $UserName) { 104 | $user = $user.Replace("@$env:MASTODON_SERVER", "") 105 | if ($user.StartsWith("@")) { 106 | $user = $user.Substring(1) 107 | } 108 | 109 | $ignored = "youtube.com", "medium.com", "withkoji.com", "counter.social", "twitter.com" 110 | foreach ($domain in $ignored) { 111 | if ($user -match $domain) { 112 | Write-Verbose "User ($user) matched invalid Mastodon domain ($domain). Skipping." 113 | continue 114 | } 115 | } 116 | 117 | $account = $script:accounts | Where-Object acct -eq $user 118 | 119 | if (-not $user.StartsWith("http")) { 120 | try { 121 | $address = [mailaddress]$user 122 | if ($address.Host -eq $Server) { 123 | $account = $script:accounts | Where-Object acct -eq $address.User 124 | if ($account) { 125 | $account 126 | continue 127 | } else { 128 | $user = "https://" + $address.Host + "/@" + $address.User 129 | } 130 | } 131 | } catch { 132 | # trying a variety of things because there is no specific 133 | # search for username, so just ignore it if this didn't work 134 | } 135 | } 136 | 137 | $user = $user.Replace("@$Server", "") 138 | $account = $script:accounts | Where-Object acct -eq $user 139 | 140 | if ($account.id) { 141 | $account 142 | continue 143 | } 144 | 145 | $parms = @{ 146 | Path = "search?type=accounts&q=$user&resolve=true" 147 | Method = "GET" 148 | Version = "v2" 149 | } 150 | 151 | $account = Invoke-Request @parms | Select-Object -ExpandProperty accounts 152 | 153 | if ($account) { 154 | # add to script variable and return 155 | $null = $script:accounts.Add($account) 156 | $account 157 | } else { 158 | throw "$user not found. The account may not exist, or it may be blocked by your account or Mastodon instance." 159 | } 160 | } 161 | } 162 | function Get-Bookmark { 163 | [CmdletBinding()] 164 | param( 165 | [psobject[]]$Id = $script:myid 166 | ) 167 | $script:link = $null 168 | Invoke-Request -Path "bookmarks" 169 | 170 | while ($null -ne $script:link) { 171 | Invoke-Request -Path $script:link 172 | } 173 | } 174 | 175 | function Get-AccountMute { 176 | [CmdletBinding()] 177 | param( 178 | [psobject[]]$Id = $script:myid 179 | ) 180 | $script:link = $null 181 | Invoke-Request -Path "mutes" 182 | 183 | while ($null -ne $script:link) { 184 | Invoke-Request -Path $script:link 185 | } 186 | } 187 | 188 | function Get-DomainBlock { 189 | [CmdletBinding()] 190 | param( 191 | [psobject[]]$Id = $script:myid 192 | ) 193 | $script:link = $null 194 | Invoke-Request -Path "domain_blocks" 195 | 196 | while ($null -ne $script:link) { 197 | Invoke-Request -Path $script:link 198 | } 199 | } 200 | 201 | function Get-List { 202 | [CmdletBinding()] 203 | param( 204 | [psobject[]]$Id = $script:myid 205 | ) 206 | $script:link = $null 207 | Invoke-Request -Path "lists" 208 | 209 | while ($null -ne $script:link) { 210 | Invoke-Request -Path $script:link 211 | } 212 | } 213 | function Get-ListMember { 214 | [CmdletBinding()] 215 | param( 216 | [psobject[]]$Id = $script:myid 217 | ) 218 | $script:link = $null 219 | Invoke-Request -Path "lists/$Id/accounts" 220 | 221 | while ($null -ne $script:link) { 222 | Invoke-Request -Path $script:link 223 | } 224 | } 225 | 226 | function Get-AccountBlock { 227 | [CmdletBinding()] 228 | param( 229 | [psobject[]]$Id = $script:myid 230 | ) 231 | $script:link = $null 232 | Invoke-Request -Path "blocks" 233 | 234 | while ($null -ne $script:link) { 235 | Invoke-Request -Path $script:link 236 | } 237 | } 238 | 239 | function Get-Post { 240 | [CmdletBinding()] 241 | param( 242 | [psobject[]]$Id = $script:myid 243 | ) 244 | $script:link = $null 245 | Invoke-Request -Path "accounts/$id/statuses?exclude_replies=true" 246 | 247 | while ($null -ne $script:link) { 248 | Invoke-Request -Path $script:link 249 | } 250 | } 251 | 252 | function Get-Relationship { 253 | [CmdletBinding()] 254 | param( 255 | [psobject[]]$Id = $script:myid 256 | ) 257 | 258 | $splits = Split-array -Objects $script:accounts.id -Size 100 259 | foreach ($split in $splits) { 260 | $idstring = $split -join "&id[]=" 261 | Invoke-Request -Path "accounts/relationships?id[]=$idstring" 262 | } 263 | } 264 | 265 | 266 | # thanks https://www.powershellgallery.com/packages/PSSharedGoods/0.0.252/Content/PSSharedGoods.psm1 267 | function Split-Array { 268 | <# 269 | .SYNOPSIS 270 | Split an array into multiple arrays of a specified size or by a specified number of elements 271 | 272 | .DESCRIPTION 273 | Split an array into multiple arrays of a specified size or by a specified number of elements 274 | 275 | .PARAMETER Objects 276 | Lists of objects you would like to split into multiple arrays based on their size or number of parts to split into. 277 | 278 | .PARAMETER Parts 279 | Parameter description 280 | 281 | .PARAMETER Size 282 | Parameter description 283 | 284 | .EXAMPLE 285 | This splits array into multiple arrays of 3 286 | Example below wil return 1,2,3 + 4,5,6 + 7,8,9 287 | Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Parts 3 288 | 289 | .EXAMPLE 290 | This splits array into 3 parts regardless of amount of elements 291 | Split-array -Objects @(1,2,3,4,5,6,7,8,9,10) -Size 3 292 | 293 | .NOTES 294 | 295 | #> 296 | [CmdletBinding()] 297 | param([alias('InArray', 'List')][Array] $Objects, 298 | [int]$Parts, 299 | [int]$Size) 300 | if ($Objects.Count -eq 1) { return $Objects } 301 | if ($Parts) { $PartSize = [Math]::Ceiling($inArray.count / $Parts) } 302 | if ($Size) { 303 | $PartSize = $Size 304 | $Parts = [Math]::Ceiling($Objects.count / $Size) 305 | } 306 | $outArray = [System.Collections.Generic.List[Object]]::new() 307 | for ($i = 1; $i -le $Parts; $i++) { 308 | $start = (($i - 1) * $PartSize) 309 | $end = (($i) * $PartSize) - 1 310 | if ($end -ge $Objects.count) { $end = $Objects.count - 1 } 311 | $outArray.Add(@($Objects[$start..$end])) 312 | } 313 | , $outArray 314 | } -------------------------------------------------------------------------------- /main.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param ( 3 | [string]$Server, 4 | [Alias("FullName")] 5 | [string]$Path, 6 | [string[]]$Type 7 | ) 8 | <# 9 | 10 | Validation 11 | 12 | #> 13 | 14 | if (-not $Path) { 15 | Write-Warning "FilePath empty or missing" 16 | return 17 | } 18 | Write-Verbose "Exporting $Type to $Path" 19 | 20 | . $PSScriptRoot/helpers.ps1 21 | $myaccount = Invoke-Request -Path "accounts/verify_credentials" -Method GET 22 | $script:myid = $myaccount.id 23 | 24 | $script:accounts = New-Object System.Collections.ArrayList 25 | 26 | # help 'em out 27 | if ($Server -match '://') { 28 | $Server = ([uri]$Server).DnsSafeHost 29 | } elseif ($Server -match '/@') { 30 | $Server = $($Server -split "/@" | Select-Object -First 1) 31 | } elseif ($Server.StartsWith("@") -or $Server -match "@") { 32 | $Server = $($Server -split "@" | Select-Object -Last 1) 33 | } 34 | 35 | $dir = Resolve-Path -Path $Path -ErrorAction Ignore 36 | if (-not $dir) { 37 | $dir = $Path 38 | } 39 | if (-not (Test-Path $dir)) { 40 | $null = New-Item -Path $dir -Type Directory -ErrorAction Stop 41 | } 42 | $dir = Resolve-Path -Path $dir -ErrorAction Ignore 43 | 44 | $items = "follows", "lists", "blocks", "mutes", "domain_blocks", "bookmarks", "followers", "posts" 45 | 46 | foreach ($item in $items) { 47 | if ($Type -contains $item -or "$Type" -eq "all") { 48 | Write-Verbose "Processing $item" 49 | 50 | if ($item -eq "blocks") { 51 | Write-Verbose "Exporting account blocks" 52 | $filepath = Join-Path -Path $dir -ChildPath blocked_accounts.csv 53 | # No header, Just acct 54 | (Get-AccountBlock).acct | Out-File -FilePath $filepath 55 | } 56 | if ($item -eq "lists") { 57 | Write-Verbose "Exporting lists" 58 | $filepath = Join-Path -Path $dir -ChildPath lists.csv 59 | # No header, two columns formatted like: listname, user@domain.tld 60 | foreach ($list in (Get-List)) { 61 | foreach ($member in (Get-ListMember -Id $list.Id)) { 62 | "$($list.title),$($member.acct)" | Add-Content -Path $filepath 63 | } 64 | } 65 | } 66 | if ($item -eq "follows") { 67 | Write-Verbose "Exporting following" 68 | $filepath = Join-Path -Path $dir -ChildPath following_accounts.csv 69 | # Account address,Show boosts,Notify on new posts,Languages 70 | # id[]=1&id[]=2 71 | <# 72 | id : 109344415928696897 73 | following : True 74 | showing_reblogs : True 75 | notifying : False 76 | languages : 77 | followed_by : True 78 | blocking : False 79 | blocked_by : False 80 | muting : False 81 | muting_notifications : False 82 | requested : False 83 | domain_blocking : False 84 | endorsed : False 85 | note : 86 | #> 87 | $follows = Get-WhoAmIFollowing 88 | $relationships = Get-Relationship 89 | $follows | ForEach-Object -Process { 90 | $rel = $relationships | Where-Object Id -eq $PSItem.Id 91 | $showboosts = $rel.showing_reblogs -eq $true 92 | $notify = $rel.notifying -eq $true 93 | [pscustomobject]@{ 94 | 'Account address' = $PSItem.acct 95 | 'Show boosts' = $showboosts 96 | 'Notify on new posts' = $notify 97 | 'Languages' = $rel.languages 98 | } 99 | } | Export-Csv -Path $filepath 100 | } 101 | 102 | if ($item -eq "mutes") { 103 | Write-Verbose "Exporting mutes" 104 | $filepath = Join-Path -Path $dir -ChildPath muted_accounts.csv 105 | # Account address,Hide notifications 106 | foreach ($account in (Get-AccountMute).acct) { 107 | "$account, $true" | Add-Content -Path $filepath 108 | } 109 | } 110 | 111 | if ($item -eq "domain_blocks") { 112 | Write-Verbose "Exporting domain blocks" 113 | $filepath = Join-Path -Path $dir -ChildPath blocked_domains.csv 114 | # Just the domain 115 | Get-DomainBlock | Out-File -Filepath $filepath 116 | } 117 | 118 | if ($item -eq "bookmarks") { 119 | Write-Verbose "Exporting bookmarks" 120 | $filepath = Join-Path -Path $dir -ChildPath bookmarks.csv 121 | (Get-Bookmark).uri | Out-File -Filepath $filepath 122 | } 123 | 124 | if ($item -eq "followers") { 125 | # Thi can't be imported 126 | Write-Verbose "Exporting followers" 127 | $filepath = Join-Path -Path $dir -ChildPath followers.csv 128 | "Follower" | Set-Content -Path $filepath 129 | (Get-WhoFollowsMe).acct | Add-Content -Path $filepath 130 | } 131 | 132 | if ($item -eq "posts") { 133 | Write-Verbose "Exporting posts" 134 | $filepath = Join-Path -Path $dir -ChildPath posts.json 135 | Get-Post | ConvertTo-Json -Depth 10 | Out-File -FilePath $filepath 136 | } 137 | 138 | if ((Test-Path -Path $filepath)) { 139 | Get-ChildItem -Path $filepath -ErrorAction Ignore 140 | } 141 | } 142 | } 143 | 144 | <# 145 | if (-not $PSBoundParameter.Type) { 146 | if ($first -match "Hide Notifications") { 147 | $Type = "mutes" 148 | $csv = Import-Csv -Path $file 149 | } elseif ($first -match "Show boosts") { 150 | $Type = "follows" 151 | $csv = Import-Csv -Path $file 152 | } elseif ($first -match "@" -and $first -notmatch ",") { 153 | $Type = "accountblocks" 154 | $csv = Get-Content -Path $file 155 | } elseif ($first -match "@" -and $first -match ",") { 156 | $Type = "lists" 157 | $csv = Import-Csv -Path $file -Header List, UserName 158 | } elseif ($first -notmatch "," -and $first -match "http") { 159 | $Type = "bookmarks" 160 | $csv = Get-Content -Path $file 161 | } elseif ($first -notmatch "http" -and $first -notmatch "," -and $first -match ".") { 162 | $Type = "domainblocks" 163 | $csv = Get-Content -Path $file 164 | } else { 165 | $basename = Split-Path -Path $file -Leaf 166 | throw "Can't auto-detect file type for $basename. Please specify type in the Action" 167 | } 168 | } 169 | #> 170 | 171 | 172 | "csv-path=$dir" >> $env:GITHUB_OUTPUT --------------------------------------------------------------------------------