Moved to webhooks for discord and added discord
avatars if linked to matter most user id
This commit is contained in:
parent
99d606f2a1
commit
28e2e4243c
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
generate_images/2025-06-08 150855867880-gen-PNG-ujZRQA.png
|
generate_images/*
|
||||||
generate_images/test-gen-image.png
|
|
||||||
|
__pycache__/*
|
||||||
|
|
||||||
|
|||||||
BIN
assets/images/owncast.jpg
Normal file
BIN
assets/images/owncast.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 87 KiB |
104
bridges.py
104
bridges.py
@ -31,7 +31,26 @@ def parse_links_from_mattermost(string):
|
|||||||
|
|
||||||
DISCORD_BOT_URL = 'http://localhost:5001/post_message'
|
DISCORD_BOT_URL = 'http://localhost:5001/post_message'
|
||||||
|
|
||||||
def send_mattermost_to_discord(post, channel_to, avatar_url=None):
|
def getDiscordUserProfile(discord_user_id):
|
||||||
|
print("test")
|
||||||
|
bot_token = "MTMyNzcxNDM3MTEyMzgxMDMwNA.GwLjEd.quGP0FA5gHRe1xLyuYq-ANuJ5cRuRQ6dhJiojI"
|
||||||
|
guild_id = "954201387770736751"
|
||||||
|
headers = {'Authorization': f'Bot {bot_token}'}
|
||||||
|
discord_guild_endpoint = f"https://discord.com/api/v10/guilds/{guild_id}/members/{discord_user_id}"
|
||||||
|
response = requests.get(discord_guild_endpoint, headers=headers)
|
||||||
|
print(response)
|
||||||
|
if response.status_code == 200:
|
||||||
|
discord_user = response.json()
|
||||||
|
username = discord_user['nick']
|
||||||
|
if discord_user['avatar']:
|
||||||
|
avatar_url = f"https://cdn.discordapp.com/guilds/{guild_id}/users/{discord_user['user']['id']}/avatars/{discord_user['avatar']}.png"
|
||||||
|
else:
|
||||||
|
avatar_url = f"https://cdn.discordapp.com/avatars/{discord_user['user']['id']}/{discord_user['user']['avatar']}.png"
|
||||||
|
return True, username, avatar_url
|
||||||
|
else:
|
||||||
|
return False, "Unknown", ""
|
||||||
|
|
||||||
|
def send_mattermost_to_discord(post, webhook_url, avatar_url=""):
|
||||||
|
|
||||||
post_message = parse_links_from_mattermost(post['message'])
|
post_message = parse_links_from_mattermost(post['message'])
|
||||||
|
|
||||||
@ -41,26 +60,15 @@ def send_mattermost_to_discord(post, channel_to, avatar_url=None):
|
|||||||
else:
|
else:
|
||||||
username = post['user']['nickname']
|
username = post['user']['nickname']
|
||||||
|
|
||||||
if avatar_url:
|
|
||||||
payload = {
|
payload = {
|
||||||
"message": post_message,
|
"content": post_message,
|
||||||
"channel_to": channel_to,
|
|
||||||
"username": username,
|
"username": username,
|
||||||
"avatar_url": avatar_url,
|
"avatar_url": avatar_url
|
||||||
"discord_id": post['discord_id']
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
payload = {
|
|
||||||
"message": post_message,
|
|
||||||
"channel_to": channel_to,
|
|
||||||
"username": username,
|
|
||||||
"avatar_url": None,
|
|
||||||
"discord_id": post['discord_id']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
response = requests.post(DISCORD_BOT_URL, json=payload)
|
response = requests.post(webhook_url, data=payload)
|
||||||
|
print("webhook response: ", response)
|
||||||
if response.status_code == 200:
|
if response.status_code == 204:
|
||||||
print("Message sent successfully.")
|
print("Message sent successfully.")
|
||||||
else:
|
else:
|
||||||
print("Failed to send the message.")
|
print("Failed to send the message.")
|
||||||
@ -82,34 +90,12 @@ def parse_links_from_discord(string):
|
|||||||
return string
|
return string
|
||||||
|
|
||||||
def send_discord_to_mattermost(message, channel_to):
|
def send_discord_to_mattermost(message, channel_to):
|
||||||
bearer_token = "g9rpuzcpkpgyddozad6j5nui9r"
|
channel_id = mattermostDriver.channels.get_channel_by_name_and_team_name(channel_to[1], channel_to[0])['id']
|
||||||
url = "http://chat.treehousefullofstars.com/api/v4/posts"
|
|
||||||
webhook_url = "https://chat.treehousefullofstars.com/hooks/qjxs466x1pr63y71bszze56x4c"
|
|
||||||
|
|
||||||
channel_id = mattermostDriver.channels.get_channel_by_name_and_team_name('Treehousefullofstars', channel_to)['id']
|
|
||||||
|
|
||||||
new_contents = ""
|
new_contents = ""
|
||||||
count = 0
|
count = 0
|
||||||
file_ids = []
|
file_ids = []
|
||||||
"""if message.embeds:
|
|
||||||
print(message.embeds)
|
|
||||||
for embed in message.embeds:
|
|
||||||
new_contents += f"\n{embed.url}"
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
file_url = embed.url
|
|
||||||
|
|
||||||
response = requests.get(file_url)
|
|
||||||
file_data = response.content
|
|
||||||
|
|
||||||
file_id = mattermostDriver.files.upload_file(
|
|
||||||
channel_id=channel_id,
|
|
||||||
files={'files': (embed.filename, file_data)}
|
|
||||||
)['file_infos'][0]['id']
|
|
||||||
file_ids.append(file_id)"""
|
|
||||||
|
|
||||||
if message.attachments:
|
if message.attachments:
|
||||||
print(message.attachments)
|
|
||||||
for attachment in message.attachments:
|
for attachment in message.attachments:
|
||||||
file_url = attachment.url
|
file_url = attachment.url
|
||||||
|
|
||||||
@ -122,17 +108,6 @@ def send_discord_to_mattermost(message, channel_to):
|
|||||||
)['file_infos'][0]['id']
|
)['file_infos'][0]['id']
|
||||||
file_ids.append(file_id)
|
file_ids.append(file_id)
|
||||||
|
|
||||||
|
|
||||||
"""payload = {
|
|
||||||
"channel": channel_to,
|
|
||||||
"text": message.content,
|
|
||||||
"username": message.author.nick,
|
|
||||||
"icon_url": message.author.display_avatar.url,
|
|
||||||
"file_ids": file_ids
|
|
||||||
}
|
|
||||||
response = requests.post(webhook_url, json=payload)
|
|
||||||
print(response.content)"""
|
|
||||||
|
|
||||||
text = message.content
|
text = message.content
|
||||||
if count>0:
|
if count>0:
|
||||||
text += new_contents
|
text += new_contents
|
||||||
@ -187,3 +162,30 @@ def send_misskey_to_mattermost(event, channel_id):
|
|||||||
},
|
},
|
||||||
'message': note_content,
|
'message': note_content,
|
||||||
'file_ids': file_ids})
|
'file_ids': file_ids})
|
||||||
|
|
||||||
|
|
||||||
|
owncast_webhook = "https://chat.treehousefullofstars.com/hooks/3o9tozzipi8e3rtkcns63pesfr"
|
||||||
|
def send_owncast_to_mattermost(event):
|
||||||
|
print(event)
|
||||||
|
data = event['eventData']
|
||||||
|
type = event['type']
|
||||||
|
post = False
|
||||||
|
text = ""
|
||||||
|
|
||||||
|
if type == "STREAM_STARTED":
|
||||||
|
text = f"Stream Starting... {data['streamTitle']}\n<http://184.83.153.182:5000/stream>"
|
||||||
|
post = True
|
||||||
|
elif type == "STREAM_STOPPED":
|
||||||
|
text = f"Stream Stopping..."
|
||||||
|
post = True
|
||||||
|
elif type == "STREAM_TITLE_UPDATED":
|
||||||
|
text = f"Stream title changed! We are now streaming... {data['streamTitle']}"
|
||||||
|
post = True
|
||||||
|
|
||||||
|
if post:
|
||||||
|
payload = {
|
||||||
|
"text": text,
|
||||||
|
"username": f"{data['name']} - Stream",
|
||||||
|
"icon_url": "http://192.168.1.67:8086/logo"
|
||||||
|
}
|
||||||
|
requests.post(owncast_webhook, json=payload)
|
||||||
@ -70,13 +70,14 @@ async def on_ready():
|
|||||||
|
|
||||||
|
|
||||||
channels = {
|
channels = {
|
||||||
1125968295967850559: "our-comforter", #comforter
|
1125968295967850559: ("our-comforter", 'final-fantasy-14'), #comforter
|
||||||
954201387770736754: "lounge", #lounge
|
954201387770736754: ("lounge", 'final-fantasy-14'), #lounge
|
||||||
1119502004721557554: "town-square", # kweh
|
1119502004721557554: ("town-square", 'Treehousefullofstars'), # kweh
|
||||||
1367978276185964584: "misskey", #misskey
|
1367978276185964584: ("misskey", 'Treehousefullofstars'), #misskey
|
||||||
1167176429797113926: "photos-from-another-star", #photos-from-another-star
|
1167176429797113926: ("photos-from-another-star", 'final-fantasy-14'), #photos-from-another-star
|
||||||
1119508652844404816: "bulletin-board", #bulletin-board
|
1119508652844404816: ("bulletin-board", 'Treehousefullofstars'), #bulletin-board
|
||||||
955394194766192690: "photos-of-the-gang" #photos-of-the-gang
|
955394194766192690: ("photos-of-the-gang", 'final-fantasy-14'), #photos-of-the-gang
|
||||||
|
1381391455574167653: ("stream", 'Treehousefullofstars'),
|
||||||
}
|
}
|
||||||
|
|
||||||
@client.event
|
@client.event
|
||||||
@ -104,36 +105,6 @@ async def send_custom_message(channel: discord.TextChannel, message, username, u
|
|||||||
await webhook.send(message, username=username, avatar_url=avatar_url)
|
await webhook.send(message, username=username, avatar_url=avatar_url)
|
||||||
await webhook.delete()
|
await webhook.delete()
|
||||||
|
|
||||||
# flask
|
|
||||||
app = flask.Flask(__name__)
|
|
||||||
|
|
||||||
@app.route('/post_message', methods=['POST'])
|
|
||||||
async def post_message():
|
|
||||||
data:dict = flask.request.json
|
|
||||||
message = data.get('message')
|
|
||||||
username = data.get('username', 'Unknown')
|
|
||||||
avatar_url = data.get('avatar_url', None)
|
|
||||||
user_id = data.get('discord_id', None)
|
|
||||||
print(data)
|
|
||||||
if message == "":
|
|
||||||
return flask.jsonify({'error': 'Message cannot be blank.'}), 400
|
|
||||||
|
|
||||||
channel = client.get_channel(data['channel_to'])
|
|
||||||
if channel:
|
|
||||||
client.loop.create_task(send_custom_message(channel, message, username, user_id, avatar_url))
|
|
||||||
#client.loop.create_task(channel.send(message))
|
|
||||||
return flask.jsonify({'status': 'Message sent successfully.'}), 200
|
|
||||||
else:
|
|
||||||
return flask.jsonify({'error': 'Channel not found.'}), 404
|
|
||||||
|
|
||||||
def run_flask_app():
|
|
||||||
app.run(port=5001)
|
|
||||||
|
|
||||||
# Run the Flask app in a separate thread
|
|
||||||
thread = Thread(target=run_flask_app)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
|
|
||||||
token = "MTMyNzcxNDM3MTEyMzgxMDMwNA.GwLjEd.quGP0FA5gHRe1xLyuYq-ANuJ5cRuRQ6dhJiojI"
|
token = "MTMyNzcxNDM3MTEyMzgxMDMwNA.GwLjEd.quGP0FA5gHRe1xLyuYq-ANuJ5cRuRQ6dhJiojI"
|
||||||
client.run(token)
|
client.run(token)
|
||||||
|
|
||||||
|
|||||||
@ -3,15 +3,23 @@ from mattermostdriver import Driver
|
|||||||
import bridges
|
import bridges
|
||||||
|
|
||||||
channels = {
|
channels = {
|
||||||
"ibfp3fskai8adgmynbfispz3se": 1125968295967850559, #comforter
|
"zc69ns9jwpbn7c3wkxdf6x8nto": 1125968295967850559, #comforter
|
||||||
"rb43iupdy7rjbjwhg9w9c1mzjy": 954201387770736754, # lounge
|
"ap61bgmm63f8irths1gk91zowh": 954201387770736754, # lounge
|
||||||
"s6muherhotfoircc1yzmwr5wty": 1119502004721557554, # kweh
|
#"s6muherhotfoircc1yzmwr5wty": 1119502004721557554, # kweh
|
||||||
"fkcqa3qj83gu3bfikcu55sfwww": 1367978276185964584, #misskey
|
"fkcqa3qj83gu3bfikcu55sfwww": 1367978276185964584 #misskey
|
||||||
"na4doo5f83ykbc45m9a5dn513a": 1167176429797113926, #photos-from-another-star
|
#"na4doo5f83ykbc45m9a5dn513a": 1167176429797113926, #photos-from-another-star
|
||||||
"9ydcz9orepbtmedncb7idh43hr": 1119508652844404816, #bulletin-board
|
#"9ydcz9orepbtmedncb7idh43hr": 1119508652844404816, #bulletin-board
|
||||||
"81qmzfzeeif7mmfhpy7hkxnjuc": 955394194766192690 #photos-of-the-gang
|
#"81qmzfzeeif7mmfhpy7hkxnjuc": 955394194766192690, #photos-of-the-gang
|
||||||
|
#"u1ffegpqj3gg7yephh6rnso74o": 1381391455574167653 #stream
|
||||||
}
|
}
|
||||||
|
|
||||||
|
webhooks = {
|
||||||
|
1125968295967850559: "https://discord.com/api/webhooks/1153490247905189909/pm_cDa8XqsTIlAsZY1uUFmSSvDZKjaI8u30mjFzmvHcMhMQFirQcWbI9rEee4OFmLNnu",
|
||||||
|
954201387770736754: "https://discord.com/api/webhooks/1127805939425235029/c4RvvRDUfDuE0-cvTzlPQPYbaTN6UdZzEPfLypOQCGSejax93Gh99E3_xgbCirrh4CqH"
|
||||||
|
}
|
||||||
|
|
||||||
|
post_webhooks_exemptions = ["u1ffegpqj3gg7yephh6rnso74o", "fkcqa3qj83gu3bfikcu55sfwww"]
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
"f3nja8t9fpy73cxeh5ykzrozaw": 407247496008433675,
|
"f3nja8t9fpy73cxeh5ykzrozaw": 407247496008433675,
|
||||||
"3byr3scix3f78xs5bpmgqzc6pc": 189202462442389514,
|
"3byr3scix3f78xs5bpmgqzc6pc": 189202462442389514,
|
||||||
@ -36,23 +44,31 @@ async def event_handler(event):
|
|||||||
if is_webhook != "true":
|
if is_webhook != "true":
|
||||||
# add file syncing means you need to get the "file_ids" key from the post and then download them into blobs and pass those along
|
# add file syncing means you need to get the "file_ids" key from the post and then download them into blobs and pass those along
|
||||||
# to the request as files, on the discord side those files would then get attached to the webhook.
|
# to the request as files, on the discord side those files would then get attached to the webhook.
|
||||||
|
discord_channel_id = channels[post['channel_id']]
|
||||||
|
print(discord_channel_id)
|
||||||
user = mattermostDriver.users.get_user(user_id=post['user_id'])
|
user = mattermostDriver.users.get_user(user_id=post['user_id'])
|
||||||
#print(user)
|
#print(user)
|
||||||
post['user'] = user
|
post['user'] = user
|
||||||
discord_id = None
|
discord_id = None
|
||||||
print("user:", user)
|
print("user:", user)
|
||||||
|
avatar_url = ""
|
||||||
if user['id'] in users.keys():
|
if user['id'] in users.keys():
|
||||||
discord_id = users[post['user']['id']]
|
discord_user_id = users[post['user']['id']]
|
||||||
|
status, username, avatar_url = bridges.getDiscordUserProfile(discord_user_id)
|
||||||
|
post['user']['nickname'] = username
|
||||||
|
print(avatar_url)
|
||||||
|
|
||||||
post['discord_id'] = discord_id
|
post['discord_id'] = discord_id
|
||||||
bridges.send_mattermost_to_discord(post, channel_to=channels[post['channel_id']])
|
print(webhooks[discord_channel_id])
|
||||||
elif post['channel_id'] == "fkcqa3qj83gu3bfikcu55sfwww" and is_webhook == "true":
|
bridges.send_mattermost_to_discord(post, webhook_url=webhooks[discord_channel_id], avatar_url=avatar_url)
|
||||||
|
elif post['channel_id'] in post_webhooks_exemptions and is_webhook == "true":
|
||||||
|
print("exempt channel!")
|
||||||
username = post['props']['override_username']
|
username = post['props']['override_username']
|
||||||
avatar_url = post['props']['override_icon_url']
|
avatar_url = post['props']['override_icon_url']
|
||||||
user = {"nickname": username}
|
user = {"nickname": username}
|
||||||
post['user'] = user
|
post['user'] = user
|
||||||
post['discord_id'] = None
|
post['discord_id'] = None
|
||||||
bridges.send_mattermost_to_discord(post, channel_to=channels[post['channel_id']], avatar_url=avatar_url)
|
bridges.send_mattermost_to_discord(post, webhook_url=webhooks[discord_channel_id], avatar_url=avatar_url)
|
||||||
|
|
||||||
mattermostDriver = Driver({
|
mattermostDriver = Driver({
|
||||||
"url": "192.168.1.67",
|
"url": "192.168.1.67",
|
||||||
|
|||||||
12
owncast.app.py
Normal file
12
owncast.app.py
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import flask
|
||||||
|
import bridges
|
||||||
|
|
||||||
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
|
@app.route('/owncast_webhook', methods=['POST'])
|
||||||
|
async def post_message():
|
||||||
|
bridges.send_owncast_to_mattermost(flask.request.get_json())
|
||||||
|
return flask.Response(status=200)
|
||||||
|
|
||||||
|
|
||||||
|
app.run(host="0.0.0.0", port=5002, debug=True)
|
||||||
129
test.json
129
test.json
@ -1,117 +1,20 @@
|
|||||||
{
|
{
|
||||||
"type": "channel",
|
"eventData": {
|
||||||
"body": {
|
"id": "YiLYhFLNRz",
|
||||||
"id": "a79jhleyz47f00j9",
|
"name": "Treehouse Full of Stars",
|
||||||
"type": "note",
|
"status": {
|
||||||
"body": {
|
"lastConnectTime": "2025-06-08T21:16:13Z",
|
||||||
"id": "a8qi6tndcsjw00uj",
|
"lastDisconnectTime": null,
|
||||||
"createdAt": "2025-06-08T00:42:51.673Z",
|
"versionNumber": "0.2.3",
|
||||||
"userId": "a78vufh1z47f000t",
|
"streamTitle": "Messing around before Maps, test! tt",
|
||||||
"user": {
|
"viewerCount": 0,
|
||||||
"id": "a78vufh1z47f000t",
|
"overallMaxViewerCount": 5,
|
||||||
"name": "Gabriella Versi",
|
"sessionMaxViewerCount": 0,
|
||||||
"username": "gabriella",
|
"online": false
|
||||||
"host": null,
|
|
||||||
"avatarUrl": "https://misskey.treehousefullofstars.com/proxy/avatar.webp?url=https%3A%2F%2Fmisskey.treehousefullofstars.com%2Ffiles%2F64e5e5cf-5d64-40f1-91ab-f7858f4b0e18&avatar=1",
|
|
||||||
"avatarBlurhash": "eOPZ7PRjpd%Mx]_Nxt?bs:xZ%gWCROWCM|%gWBRjofWXSPf5xtt7V@",
|
|
||||||
"avatarDecorations": [],
|
|
||||||
"isBot": false,
|
|
||||||
"isCat": false,
|
|
||||||
"emojis": {},
|
|
||||||
"onlineStatus": "online",
|
|
||||||
"badgeRoles": [
|
|
||||||
{
|
|
||||||
"name": "Roots",
|
|
||||||
"iconUrl": null,
|
|
||||||
"displayOrder": 0
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"text": null,
|
"streamTitle": "Messing around before Maps, test! tt",
|
||||||
"cw": null,
|
"summary": "",
|
||||||
"visibility": "public",
|
"timestamp": "2025-06-08T21:16:13.781690442Z"
|
||||||
"localOnly": false,
|
|
||||||
"reactionAcceptance": null,
|
|
||||||
"renoteCount": 0,
|
|
||||||
"repliesCount": 0,
|
|
||||||
"reactionCount": 0,
|
|
||||||
"reactions": {},
|
|
||||||
"reactionEmojis": {},
|
|
||||||
"reactionAndUserPairCache": [],
|
|
||||||
"fileIds": [],
|
|
||||||
"files": [],
|
|
||||||
"replyId": null,
|
|
||||||
"renoteId": "a8ohrbj431al069k",
|
|
||||||
"clippedCount": 0,
|
|
||||||
"renote": {
|
|
||||||
"id": "a8ohrbj431al069k",
|
|
||||||
"createdAt": "2025-06-06T14:55:16.000Z",
|
|
||||||
"userId": "a7xcts4vcsjw00at",
|
|
||||||
"user": {
|
|
||||||
"id": "a7xcts4vcsjw00at",
|
|
||||||
"name": "Information Is Beautiful",
|
|
||||||
"username": "infobeautiful",
|
|
||||||
"host": "vis.social",
|
|
||||||
"avatarUrl": "https://misskey.treehousefullofstars.com/proxy/avatar.webp?url=https%3A%2F%2Fcdn.masto.host%2Fvissocial%2Faccounts%2Favatars%2F111%2F030%2F299%2F829%2F248%2F466%2Foriginal%2F0d4bfae30dce0763.png&avatar=1",
|
|
||||||
"avatarBlurhash": "e2CY]zxI9xx@=y$%juXSa{aeD,ou~UV|bayCj@ena#t7yCRowIxo01",
|
|
||||||
"avatarDecorations": [],
|
|
||||||
"isBot": false,
|
|
||||||
"isCat": false,
|
|
||||||
"instance": {
|
|
||||||
"name": "vis.social",
|
|
||||||
"softwareName": "mastodon",
|
|
||||||
"softwareVersion": "4.3.8",
|
|
||||||
"iconUrl": "https://vis.social/packs/media/icons/android-chrome-36x36-4c61fdb42936428af85afdbf8c6a45a8.png",
|
|
||||||
"faviconUrl": "https://vis.social/packs/media/icons/favicon-48x48-c1197e9664ee6476d2715a1c4293bf61.png",
|
|
||||||
"themeColor": "#181820"
|
|
||||||
},
|
},
|
||||||
"emojis": {},
|
"type": "STREAM_STARTED"
|
||||||
"onlineStatus": "unknown"
|
|
||||||
},
|
|
||||||
"text": "Some interesting variations here...\n(by reddit user theworldmaps)",
|
|
||||||
"cw": null,
|
|
||||||
"visibility": "public",
|
|
||||||
"localOnly": false,
|
|
||||||
"reactionAcceptance": null,
|
|
||||||
"renoteCount": 1,
|
|
||||||
"repliesCount": 1,
|
|
||||||
"reactionCount": 0,
|
|
||||||
"reactions": {},
|
|
||||||
"reactionEmojis": {},
|
|
||||||
"reactionAndUserPairCache": [],
|
|
||||||
"emojis": {},
|
|
||||||
"fileIds": [
|
|
||||||
"a8ohrh7e31al069j"
|
|
||||||
],
|
|
||||||
"files": [
|
|
||||||
{
|
|
||||||
"id": "a8ohrh7e31al069j",
|
|
||||||
"createdAt": "2025-06-06T14:55:23.354Z",
|
|
||||||
"name": "b1743c8a716cf03a.png",
|
|
||||||
"type": "image/png",
|
|
||||||
"md5": "8fa3149c3db0e0d11e7b0b0007cabeeb",
|
|
||||||
"size": 0,
|
|
||||||
"isSensitive": false,
|
|
||||||
"blurhash": "eHQuZ@WdUE+^XoghOkXlw1oxLzNrmSr{t1vgQ;ogXOWZu2r;bwpaaM",
|
|
||||||
"properties": {
|
|
||||||
"width": 853,
|
|
||||||
"height": 1024
|
|
||||||
},
|
|
||||||
"url": "https://misskey.treehousefullofstars.com/files/webpublic-1225bfbb-9837-44b0-b0ac-1ed14fa1df92",
|
|
||||||
"thumbnailUrl": "https://misskey.treehousefullofstars.com/proxy/static.webp?url=https%3A%2F%2Fcdn.masto.host%2Fvissocial%2Fmedia_attachments%2Ffiles%2F114%2F636%2F994%2F245%2F080%2F080%2Foriginal%2Fb1743c8a716cf03a.png&static=1",
|
|
||||||
"comment": "A map of Europe showing the mean age of women at the birth of their first child, categorized by color from yellow (age 25) to purple (age 33). Examples include: Moldova with the youngest average age at 25.1, and Monaco with the highest at 32.5. Other notable countries: Spain at 31.6, Italy at 31.7, Germany at 29.9, and Finland at 29.9. The map includes small flags and labeled figures for precision. Data for the UK is from 2018, and other countries are from 2021 or 2022.",
|
|
||||||
"folderId": null,
|
|
||||||
"folder": null,
|
|
||||||
"userId": "a7xcts4vcsjw00at",
|
|
||||||
"user": null
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"replyId": null,
|
|
||||||
"renoteId": null,
|
|
||||||
"uri": "https://vis.social/users/infobeautiful/statuses/114636994393755424",
|
|
||||||
"url": "https://vis.social/@infobeautiful/114636994393755424",
|
|
||||||
"clippedCount": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user