2020-04-03 16:59:12 +00:00
|
|
|
__filename__ = "metadata.py"
|
|
|
|
__author__ = "Bob Mottram"
|
|
|
|
__license__ = "AGPL3+"
|
2021-01-26 10:07:42 +00:00
|
|
|
__version__ = "1.2.0"
|
2020-04-03 16:59:12 +00:00
|
|
|
__maintainer__ = "Bob Mottram"
|
2021-09-10 16:14:50 +00:00
|
|
|
__email__ = "bob@libreserver.org"
|
2020-04-03 16:59:12 +00:00
|
|
|
__status__ = "Production"
|
2021-06-15 15:08:12 +00:00
|
|
|
__module_group__ = "Metadata"
|
2019-11-13 10:32:12 +00:00
|
|
|
|
2019-11-13 12:45:41 +00:00
|
|
|
import os
|
2021-12-26 18:46:43 +00:00
|
|
|
from utils import is_account_dir
|
2021-12-26 15:13:34 +00:00
|
|
|
from utils import load_json
|
2021-12-28 14:41:10 +00:00
|
|
|
from utils import no_of_accounts
|
|
|
|
from utils import no_of_active_accounts_monthly
|
2019-11-13 10:32:12 +00:00
|
|
|
|
2020-04-03 16:59:12 +00:00
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def _get_status_count(base_dir: str) -> int:
|
2021-06-05 09:22:35 +00:00
|
|
|
"""Get the total number of posts
|
|
|
|
"""
|
|
|
|
statusCtr = 0
|
2021-12-25 16:17:53 +00:00
|
|
|
accountsDir = base_dir + '/accounts'
|
2021-06-05 09:22:35 +00:00
|
|
|
for subdir, dirs, files in os.walk(accountsDir):
|
|
|
|
for acct in dirs:
|
2021-12-26 18:46:43 +00:00
|
|
|
if not is_account_dir(acct):
|
2021-06-05 09:22:35 +00:00
|
|
|
continue
|
2021-12-26 12:02:29 +00:00
|
|
|
acct_dir = os.path.join(accountsDir, acct + '/outbox')
|
|
|
|
for subdir2, dirs2, files2 in os.walk(acct_dir):
|
2021-06-05 09:22:35 +00:00
|
|
|
statusCtr += len(files2)
|
|
|
|
break
|
|
|
|
break
|
|
|
|
return statusCtr
|
|
|
|
|
|
|
|
|
2021-12-28 17:20:43 +00:00
|
|
|
def meta_data_node_info(base_dir: str,
|
|
|
|
aboutUrl: str,
|
|
|
|
termsOfServiceUrl: str,
|
|
|
|
registration: bool, version: str,
|
|
|
|
showAccounts: bool) -> {}:
|
2019-11-13 10:32:12 +00:00
|
|
|
""" /nodeinfo/2.0 endpoint
|
2021-05-03 10:05:05 +00:00
|
|
|
Also see https://socialhub.activitypub.rocks/t/
|
|
|
|
fep-f1d5-nodeinfo-in-fediverse-software/1190/4
|
|
|
|
|
|
|
|
Note that there are security considerations with this. If an adversary
|
|
|
|
sees a lot of accounts and "local" posts then the instance may be
|
|
|
|
considered a higher priority target.
|
|
|
|
Also exposure of the version number and number of accounts could be
|
|
|
|
sensitive
|
2019-11-13 10:32:12 +00:00
|
|
|
"""
|
2021-05-03 10:05:05 +00:00
|
|
|
if showAccounts:
|
2021-12-28 14:41:10 +00:00
|
|
|
activeAccounts = no_of_accounts(base_dir)
|
|
|
|
activeAccountsMonthly = no_of_active_accounts_monthly(base_dir, 1)
|
|
|
|
activeAccountsHalfYear = no_of_active_accounts_monthly(base_dir, 6)
|
2021-12-29 21:55:09 +00:00
|
|
|
localPosts = _get_status_count(base_dir)
|
2021-05-03 10:05:05 +00:00
|
|
|
else:
|
|
|
|
activeAccounts = 1
|
|
|
|
activeAccountsMonthly = 1
|
|
|
|
activeAccountsHalfYear = 1
|
2021-06-05 09:22:35 +00:00
|
|
|
localPosts = 1
|
2021-05-03 10:05:05 +00:00
|
|
|
|
2020-04-03 16:59:12 +00:00
|
|
|
nodeinfo = {
|
2019-11-13 10:32:12 +00:00
|
|
|
'openRegistrations': registration,
|
|
|
|
'protocols': ['activitypub'],
|
|
|
|
'software': {
|
|
|
|
'name': 'epicyon',
|
|
|
|
'version': version
|
|
|
|
},
|
2021-05-04 09:39:15 +00:00
|
|
|
'documents': {
|
|
|
|
'about': aboutUrl,
|
|
|
|
'terms': termsOfServiceUrl
|
|
|
|
},
|
2019-11-13 10:32:12 +00:00
|
|
|
'usage': {
|
2021-06-05 09:22:35 +00:00
|
|
|
'localPosts': localPosts,
|
2019-11-13 10:32:12 +00:00
|
|
|
'users': {
|
2019-11-13 15:19:43 +00:00
|
|
|
'activeHalfyear': activeAccountsHalfYear,
|
|
|
|
'activeMonth': activeAccountsMonthly,
|
2019-11-13 14:02:45 +00:00
|
|
|
'total': activeAccounts
|
2019-11-13 10:32:12 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
'version': '2.0'
|
|
|
|
}
|
|
|
|
return nodeinfo
|
2019-11-13 12:45:41 +00:00
|
|
|
|
2020-04-03 16:59:12 +00:00
|
|
|
|
2021-12-29 21:55:09 +00:00
|
|
|
def meta_data_instance(showAccounts: bool,
|
|
|
|
instanceTitle: str,
|
|
|
|
instanceDescriptionShort: str,
|
|
|
|
instanceDescription: str,
|
|
|
|
http_prefix: str, base_dir: str,
|
|
|
|
adminNickname: str, domain: str, domain_full: str,
|
|
|
|
registration: bool, system_language: str,
|
|
|
|
version: str) -> {}:
|
2019-11-13 12:45:41 +00:00
|
|
|
""" /api/v1/instance endpoint
|
|
|
|
"""
|
2020-04-03 16:59:12 +00:00
|
|
|
adminActorFilename = \
|
2021-12-25 16:17:53 +00:00
|
|
|
base_dir + '/accounts/' + adminNickname + '@' + domain + '.json'
|
2019-11-13 12:45:41 +00:00
|
|
|
if not os.path.isfile(adminActorFilename):
|
|
|
|
return {}
|
|
|
|
|
2021-12-26 15:13:34 +00:00
|
|
|
adminActor = load_json(adminActorFilename, 0)
|
2019-11-13 12:45:41 +00:00
|
|
|
if not adminActor:
|
2021-12-29 21:55:09 +00:00
|
|
|
print('WARN: json load exception meta_data_instance')
|
2019-11-13 12:45:41 +00:00
|
|
|
return {}
|
|
|
|
|
2021-10-12 11:09:04 +00:00
|
|
|
rulesList = []
|
|
|
|
rulesFilename = \
|
2021-12-25 16:17:53 +00:00
|
|
|
base_dir + '/accounts/tos.md'
|
2021-10-12 11:09:04 +00:00
|
|
|
if os.path.isfile(rulesFilename):
|
|
|
|
with open(rulesFilename, 'r') as fp:
|
|
|
|
rulesLines = fp.readlines()
|
|
|
|
ruleCtr = 1
|
|
|
|
for line in rulesLines:
|
|
|
|
line = line.strip()
|
|
|
|
if not line:
|
|
|
|
continue
|
|
|
|
if line.startswith('#'):
|
|
|
|
continue
|
|
|
|
rulesList.append({
|
|
|
|
'id': str(ruleCtr),
|
|
|
|
'text': line
|
|
|
|
})
|
|
|
|
ruleCtr += 1
|
|
|
|
|
2020-04-03 16:59:12 +00:00
|
|
|
isBot = False
|
2021-10-12 10:31:22 +00:00
|
|
|
isGroup = False
|
|
|
|
if adminActor['type'] == 'Group':
|
|
|
|
isGroup = True
|
|
|
|
elif adminActor['type'] != 'Person':
|
2020-04-03 16:59:12 +00:00
|
|
|
isBot = True
|
|
|
|
|
|
|
|
url = \
|
2021-12-26 10:00:46 +00:00
|
|
|
http_prefix + '://' + domain_full + '/@' + \
|
2020-04-03 16:59:12 +00:00
|
|
|
adminActor['preferredUsername']
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2021-06-05 09:22:35 +00:00
|
|
|
if showAccounts:
|
2021-12-28 14:41:10 +00:00
|
|
|
activeAccounts = no_of_accounts(base_dir)
|
2021-12-29 21:55:09 +00:00
|
|
|
localPosts = _get_status_count(base_dir)
|
2021-06-05 09:22:35 +00:00
|
|
|
else:
|
|
|
|
activeAccounts = 1
|
|
|
|
localPosts = 1
|
|
|
|
|
2021-10-12 10:31:22 +00:00
|
|
|
createdAt = ''
|
|
|
|
if adminActor.get('published'):
|
|
|
|
createdAt = adminActor['published']
|
|
|
|
|
2020-04-03 16:59:12 +00:00
|
|
|
instance = {
|
2019-11-13 12:55:37 +00:00
|
|
|
'approval_required': False,
|
2021-10-12 10:31:22 +00:00
|
|
|
'invites_enabled': False,
|
|
|
|
'registrations': registration,
|
2020-03-22 20:36:19 +00:00
|
|
|
'contact_account': {
|
|
|
|
'acct': adminActor['preferredUsername'],
|
2021-10-12 10:31:22 +00:00
|
|
|
'created_at': createdAt,
|
2020-03-22 20:36:19 +00:00
|
|
|
'avatar': adminActor['icon']['url'],
|
|
|
|
'avatar_static': adminActor['icon']['url'],
|
|
|
|
'header': adminActor['image']['url'],
|
|
|
|
'header_static': adminActor['image']['url'],
|
2021-10-12 10:31:22 +00:00
|
|
|
'bot': isBot,
|
|
|
|
'discoverable': True,
|
|
|
|
'group': isGroup,
|
|
|
|
'display_name': adminActor['name'],
|
2020-03-22 20:36:19 +00:00
|
|
|
'locked': adminActor['manuallyApprovesFollowers'],
|
2021-05-04 10:17:06 +00:00
|
|
|
'note': '<p>Admin of ' + domain + '</p>',
|
2020-04-03 16:59:12 +00:00
|
|
|
'url': url,
|
2020-03-22 20:36:19 +00:00
|
|
|
'username': adminActor['preferredUsername']
|
2019-11-13 12:45:41 +00:00
|
|
|
},
|
|
|
|
'description': instanceDescription,
|
2021-12-25 23:03:28 +00:00
|
|
|
'languages': [system_language],
|
2019-11-13 12:45:41 +00:00
|
|
|
'short_description': instanceDescriptionShort,
|
|
|
|
'stats': {
|
2021-10-12 10:31:22 +00:00
|
|
|
'domain_count': 2,
|
2021-06-05 09:22:35 +00:00
|
|
|
'status_count': localPosts,
|
|
|
|
'user_count': activeAccounts
|
2019-11-13 12:45:41 +00:00
|
|
|
},
|
2021-12-26 10:00:46 +00:00
|
|
|
'thumbnail': http_prefix + '://' + domain_full + '/login.png',
|
2019-11-13 12:45:41 +00:00
|
|
|
'title': instanceTitle,
|
2021-12-26 10:00:46 +00:00
|
|
|
'uri': domain_full,
|
2019-11-13 12:45:41 +00:00
|
|
|
'urls': {},
|
2021-10-12 10:31:22 +00:00
|
|
|
'version': version,
|
|
|
|
'rules': rulesList,
|
|
|
|
'configuration': {
|
|
|
|
'statuses': {
|
|
|
|
'max_media_attachments': 1
|
|
|
|
},
|
|
|
|
'media_attachments': {
|
|
|
|
'supported_mime_types': [
|
|
|
|
'image/jpeg',
|
|
|
|
'image/png',
|
|
|
|
'image/gif',
|
|
|
|
'image/webp',
|
|
|
|
'image/avif',
|
|
|
|
'image/svg+xml',
|
|
|
|
'video/webm',
|
|
|
|
'video/mp4',
|
|
|
|
'video/ogv',
|
|
|
|
'audio/ogg',
|
|
|
|
'audio/flac',
|
|
|
|
'audio/mpeg'
|
|
|
|
],
|
|
|
|
'image_size_limit': 10485760,
|
|
|
|
'image_matrix_limit': 16777216,
|
|
|
|
'video_size_limit': 41943040,
|
|
|
|
'video_frame_rate_limit': 60,
|
|
|
|
'video_matrix_limit': 2304000
|
|
|
|
}
|
|
|
|
}
|
2019-11-13 12:45:41 +00:00
|
|
|
}
|
2020-03-22 21:16:02 +00:00
|
|
|
|
2019-11-13 12:45:41 +00:00
|
|
|
return instance
|
2021-05-27 22:08:49 +00:00
|
|
|
|
|
|
|
|
2021-12-28 17:20:43 +00:00
|
|
|
def metadata_custom_emoji(base_dir: str,
|
|
|
|
http_prefix: str, domain_full: str) -> {}:
|
2021-05-27 22:08:49 +00:00
|
|
|
"""Returns the custom emoji
|
|
|
|
Endpoint /api/v1/custom_emojis
|
|
|
|
See https://docs.joinmastodon.org/methods/instance/custom_emojis
|
|
|
|
"""
|
|
|
|
result = []
|
2021-12-26 10:00:46 +00:00
|
|
|
emojisUrl = http_prefix + '://' + domain_full + '/emoji'
|
2021-12-25 16:17:53 +00:00
|
|
|
for subdir, dirs, files in os.walk(base_dir + '/emoji'):
|
2021-05-27 22:08:49 +00:00
|
|
|
for f in files:
|
2021-05-27 22:19:52 +00:00
|
|
|
if len(f) < 3:
|
|
|
|
continue
|
|
|
|
if f[0].isdigit() or f[1].isdigit():
|
2021-05-27 22:08:49 +00:00
|
|
|
continue
|
|
|
|
if not f.endswith('.png'):
|
|
|
|
continue
|
|
|
|
url = os.path.join(emojisUrl, f)
|
|
|
|
result.append({
|
|
|
|
"shortcode": f.replace('.png', ''),
|
|
|
|
"url": url,
|
|
|
|
"static_url": url,
|
|
|
|
"visible_in_picker": True
|
|
|
|
})
|
|
|
|
break
|
|
|
|
return result
|