Skip to content

Instantly share code, notes, and snippets.

@FiXato
Last active January 2, 2023 15:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FiXato/fb295890468b3396b9ef7c379c259a5b to your computer and use it in GitHub Desktop.
Save FiXato/fb295890468b3396b9ef7c379c259a5b to your computer and use it in GitHub Desktop.
Get list of mastodon relationships
#!/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>")
/* 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 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