Last active
January 2, 2023 15:19
-
-
Save FiXato/fb295890468b3396b9ef7c379c259a5b to your computer and use it in GitHub Desktop.
Get list of mastodon relationships
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
# encoding: utf-8 | |
import html | |
from mastodon import Mastodon | |
api = Mastodon( | |
client_id="REPLACE_WITH_APPLICATIONS_CLIENT_ID", | |
client_secret="REPLACE_WITH_APPLICATIONS_CLIENT_SECRET", | |
access_token="REPLACE_WITH_APPLICATIONS_ACCESS_TOKEN", | |
api_base_url="https://your.instance.name.example" | |
) | |
me = api.me() | |
host = me.url.split('//', 1)[1].split('/', 1)[0] | |
following = api.account_following(id=me,limit=80) | |
following += api.fetch_remaining(following) | |
accounts = {} | |
for account in following: | |
acct = account.acct | |
if '@' not in acct: | |
acct = f"""@{acct}@{host}""" | |
else: | |
acct = f"""@{acct}""" | |
account.full_acct = acct | |
accounts[account.id] = account | |
ids = list(accounts.keys()) | |
relationships = api.account_relationships(id=ids) | |
accounts_requested_following = [[accounts[rel.id], rel] for rel in relationships if rel.requested] | |
accounts_not_following = [[accounts[rel.id], rel] for rel in relationships if not rel.following] | |
accounts_following = [[accounts[rel.id], rel] for rel in relationships if rel.following] | |
def full_acct(account, host): | |
return acct | |
def print_account_summary(account, rel): | |
print(f""" - [{account.full_acct} ({account.id}) {account.url}] {account.display_name} | {"following them" if rel.following else "*NOT* following them"} | {"request pending " if rel.requested else "no request pending"} | {" are following back by them " if rel.followed_by else " aren't followed back by them"}""") | |
def account_html_row(account, rel): | |
return f"""<tr> | |
<td>{html.escape(str(account.id))}</td> | |
<td>{html.escape(account.full_acct)}</td> | |
<td>{html.escape(account.display_name)}</td> | |
<td>{"✅" if rel.following else "❌"}</td> | |
<td>{"✅" if rel.followed_by else "❌"}</td> | |
<td>{"✅" if rel.requested else "❌"}</td> | |
<td><a href="{html.escape(account.url)}">{html.escape(account.url)}</a></td> | |
</tr>""" | |
with open('mastodon_relationships.html', "w+", encoding="utf-8") as f: | |
f.write(f"""<html><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Mastodon Relationships</title><link rel="stylesheet" href="sortable-table.css" /><script src="sortable-table.js"></script></head><body>""") | |
f.write(f""" | |
<div class="table-wrap"> | |
<table class="sortable"> | |
<thead style="height: 2em;"> | |
<tr> | |
<th> | |
<button> | |
ID | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Account | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Display Name | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Following? | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Followed? | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Requested? | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
<th> | |
<button> | |
Profile URL | |
<span aria-hidden="true"></span> | |
</button> | |
</th> | |
</tr> | |
</thead> | |
<tbody>""") | |
print("# Requested Following:") | |
for account, rel in accounts_requested_following: | |
print_account_summary(account, rel) | |
f.write(account_html_row(account, rel)) | |
print("# Accounts not following:") | |
for account, rel in accounts_not_following: | |
print_account_summary(account, rel) | |
f.write(account_html_row(account, rel)) | |
print("# Accounts following:") | |
for account, rel in accounts_following: | |
print_account_summary(account, rel) | |
f.write(account_html_row(account, rel)) | |
f.write("</tbody></table></div></body></html>") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* Taken from: https://www.w3.org/WAI/ARIA/apg/example-index/table/css/sortable-table.css */ | |
table.sortable td, | |
table.sortable th { | |
padding: 0.125em 0.25em; | |
width: 8em; | |
} | |
table.sortable th { | |
font-weight: bold; | |
border-bottom: thin solid #888; | |
position: relative; | |
} | |
table.sortable th.no-sort { | |
padding-top: 0.35em; | |
} | |
table.sortable th:nth-child(5) { | |
width: 10em; | |
} | |
table.sortable th button { | |
position: absolute; | |
padding: 4px; | |
margin: 1px; | |
font-size: 100%; | |
font-weight: bold; | |
background: transparent; | |
border: none; | |
display: inline; | |
right: 0; | |
left: 0; | |
top: 0; | |
bottom: 0; | |
width: 100%; | |
text-align: left; | |
outline: none; | |
cursor: pointer; | |
} | |
table.sortable th button span { | |
position: absolute; | |
right: 4px; | |
} | |
table.sortable th[aria-sort="descending"] span::after { | |
content: "▼"; | |
color: currentcolor; | |
font-size: 100%; | |
top: 0; | |
} | |
table.sortable th[aria-sort="ascending"] span::after { | |
content: "▲"; | |
color: currentcolor; | |
font-size: 100%; | |
top: 0; | |
} | |
table.show-unsorted-icon th:not([aria-sort]) button span::after { | |
content: "♢"; | |
color: currentcolor; | |
font-size: 100%; | |
position: relative; | |
top: -3px; | |
left: -4px; | |
} | |
table.sortable td.num { | |
text-align: right; | |
} | |
table.sortable tbody tr:nth-child(odd) { | |
background-color: #ddd; | |
} | |
/* Focus and hover styling */ | |
table.sortable th button:focus, | |
table.sortable th button:hover { | |
padding: 2px; | |
border: 2px solid currentcolor; | |
background-color: #e5f4ff; | |
} | |
table.sortable th button:focus span, | |
table.sortable th button:hover span { | |
right: 2px; | |
} | |
table.sortable th:not([aria-sort]) button:focus span::after, | |
table.sortable th:not([aria-sort]) button:hover span::after { | |
content: "▼"; | |
color: currentcolor; | |
font-size: 100%; | |
top: 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* This content is licensed according to the W3C Software License at | |
* https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document | |
* | |
* File: sortable-table.js | |
* | |
* Desc: Adds sorting to a HTML data table that implements ARIA Authoring Practices | |
* Taken from: https://www.w3.org/WAI/ARIA/apg/example-index/table/js/sortable-table.js | |
*/ | |
'use strict'; | |
class SortableTable { | |
constructor(tableNode) { | |
this.tableNode = tableNode; | |
this.columnHeaders = tableNode.querySelectorAll('thead th'); | |
this.sortColumns = []; | |
for (var i = 0; i < this.columnHeaders.length; i++) { | |
var ch = this.columnHeaders[i]; | |
var buttonNode = ch.querySelector('button'); | |
if (buttonNode) { | |
this.sortColumns.push(i); | |
buttonNode.setAttribute('data-column-index', i); | |
buttonNode.addEventListener('click', this.handleClick.bind(this)); | |
} | |
} | |
this.optionCheckbox = document.querySelector( | |
'input[type="checkbox"][value="show-unsorted-icon"]' | |
); | |
if (this.optionCheckbox) { | |
this.optionCheckbox.addEventListener( | |
'change', | |
this.handleOptionChange.bind(this) | |
); | |
if (this.optionCheckbox.checked) { | |
this.tableNode.classList.add('show-unsorted-icon'); | |
} | |
} | |
} | |
setColumnHeaderSort(columnIndex) { | |
if (typeof columnIndex === 'string') { | |
columnIndex = parseInt(columnIndex); | |
} | |
for (var i = 0; i < this.columnHeaders.length; i++) { | |
var ch = this.columnHeaders[i]; | |
var buttonNode = ch.querySelector('button'); | |
if (i === columnIndex) { | |
var value = ch.getAttribute('aria-sort'); | |
if (value === 'descending') { | |
ch.setAttribute('aria-sort', 'ascending'); | |
this.sortColumn( | |
columnIndex, | |
'ascending', | |
ch.classList.contains('num') | |
); | |
} else { | |
ch.setAttribute('aria-sort', 'descending'); | |
this.sortColumn( | |
columnIndex, | |
'descending', | |
ch.classList.contains('num') | |
); | |
} | |
} else { | |
if (ch.hasAttribute('aria-sort') && buttonNode) { | |
ch.removeAttribute('aria-sort'); | |
} | |
} | |
} | |
} | |
sortColumn(columnIndex, sortValue, isNumber) { | |
function compareValues(a, b) { | |
if (sortValue === 'ascending') { | |
if (a.value === b.value) { | |
return 0; | |
} else { | |
if (isNumber) { | |
return a.value - b.value; | |
} else { | |
return a.value < b.value ? -1 : 1; | |
} | |
} | |
} else { | |
if (a.value === b.value) { | |
return 0; | |
} else { | |
if (isNumber) { | |
return b.value - a.value; | |
} else { | |
return a.value > b.value ? -1 : 1; | |
} | |
} | |
} | |
} | |
if (typeof isNumber !== 'boolean') { | |
isNumber = false; | |
} | |
var tbodyNode = this.tableNode.querySelector('tbody'); | |
var rowNodes = []; | |
var dataCells = []; | |
var rowNode = tbodyNode.firstElementChild; | |
var index = 0; | |
while (rowNode) { | |
rowNodes.push(rowNode); | |
var rowCells = rowNode.querySelectorAll('th, td'); | |
var dataCell = rowCells[columnIndex]; | |
var data = {}; | |
data.index = index; | |
data.value = dataCell.textContent.toLowerCase().trim(); | |
if (isNumber) { | |
data.value = parseFloat(data.value); | |
} | |
dataCells.push(data); | |
rowNode = rowNode.nextElementSibling; | |
index += 1; | |
} | |
dataCells.sort(compareValues); | |
// remove rows | |
while (tbodyNode.firstChild) { | |
tbodyNode.removeChild(tbodyNode.lastChild); | |
} | |
// add sorted rows | |
for (var i = 0; i < dataCells.length; i += 1) { | |
tbodyNode.appendChild(rowNodes[dataCells[i].index]); | |
} | |
} | |
/* EVENT HANDLERS */ | |
handleClick(event) { | |
var tgt = event.currentTarget; | |
this.setColumnHeaderSort(tgt.getAttribute('data-column-index')); | |
} | |
handleOptionChange(event) { | |
var tgt = event.currentTarget; | |
if (tgt.checked) { | |
this.tableNode.classList.add('show-unsorted-icon'); | |
} else { | |
this.tableNode.classList.remove('show-unsorted-icon'); | |
} | |
} | |
} | |
// Initialize sortable table buttons | |
window.addEventListener('load', function () { | |
var sortableTables = document.querySelectorAll('table.sortable'); | |
for (var i = 0; i < sortableTables.length; i++) { | |
new SortableTable(sortableTables[i]); | |
} | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment