a whole bunch of changes
1
.python-version
Normal file
|
@ -0,0 +1 @@
|
|||
3.8.0
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Lightweight ActivityPub server written in Python
|
||||
|
||||
Here's a list of all the ideas I plan on implementing: https://git.barkshark.tk/izaliamae/social/wiki/Ideas
|
||||
Here's a list of all the ideas I plan on implementing: https://git.barkshark.xyz/izaliamae/social/wiki/Ideas
|
||||
|
||||
Note: 'social' is a placeholder name and will be changed in the future
|
||||
|
||||
|
|
21
dist/database.sql
vendored
|
@ -10,6 +10,7 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
settings TEXT,
|
||||
privkey TEXT,
|
||||
pubkey TEXT NOT NULL,
|
||||
domain_id INT,
|
||||
timestamp INT NOT NULL
|
||||
);
|
||||
|
||||
|
@ -21,9 +22,19 @@ CREATE TABLE IF NOT EXISTS statuses (
|
|||
warning TEXT,
|
||||
visibility TEXT NOT NULL,
|
||||
mentions TEXT,
|
||||
replies TEXT,
|
||||
domain_id INT,
|
||||
timestamp FLOAT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_codes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT NOT NULL,
|
||||
userid INT NOT NULL,
|
||||
appid INT NOT NULL,
|
||||
timestamp INT NOT NULL
|
||||
)
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_tokens (
|
||||
id SERIAL PRIMARY KEY,
|
||||
userid INT NOT NULL,
|
||||
|
@ -33,6 +44,16 @@ CREATE TABLE IF NOT EXISTS auth_tokens (
|
|||
access INT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS auth_apps (
|
||||
id SERIAL PRIMARY KEY,
|
||||
client_id TEXT NOT NULL,
|
||||
client_secret TEXT NOT NULL,
|
||||
redirect_uri TEXT NOT NULL,
|
||||
scope TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
url TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS login_cookies (
|
||||
id SERIAL PRIMARY KEY,
|
||||
userid INT NOT NULL,
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import aiohttp, aiohttp_jinja2
|
||||
|
||||
|
||||
# Redirect /user/[user] to /@[user]
|
||||
async def user_get(request):
|
||||
user = request.match_info['user']
|
||||
|
||||
return aiohttp.web.HTTPFound(f'/@{user}')
|
||||
|
||||
|
||||
# Redirect /status/[status] to /:[status]
|
||||
async def post_get(request):
|
||||
status = request.match_info['status']
|
||||
|
||||
return aiohttp.web.HTTPFound(f'/:{post}')
|
||||
|
||||
|
||||
# PATPAT
|
||||
async def headpats(request):
|
||||
return aiohttp.web.HTTPFound('https://static.barkshark.tk/mastodon/main/custom_emojis/images/000/000/590/original/blobpatpat.png')
|
||||
|
||||
|
||||
#socks
|
||||
async def socks(request):
|
||||
return aiohttp.web.HTTPFound('https://glaceon.social/@monorail/102496098954435127')
|
|
@ -1,42 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% set page = profile.name %}
|
||||
|
||||
{% block content %}
|
||||
<div id="user_header">
|
||||
<div id="bio">
|
||||
<a href="https://{{domain}}/@{{profile.handle}}">{{profile.name}}</a><br>
|
||||
{{profile.handle}}@{{domain}}<br>
|
||||
{{profile.count}} toots<br>
|
||||
<br>
|
||||
{% if profile.bio != None %}
|
||||
{% for line in profile.bio %}
|
||||
{{line}}<br>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
This bitch empty YEET
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if profile.info_table != None %}
|
||||
<div id="user_info">
|
||||
<table>
|
||||
{% for line in profile.info_table %}
|
||||
<tr><td class="col1">{{line}}</td><td class="col2">{{profile.info_table[line]}}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div id="user_content">
|
||||
<div id="posts">
|
||||
{% if post != [] %}
|
||||
{% for post in post %}
|
||||
{% include "components/post.html" %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if last_id != None %}
|
||||
<div id="new_posts"><center>{{profile.last_id}}<a href="https://{{domain}}/@{{profile.handle}}?id={{last_id}}">[More Posts]</a></center></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
6
reload.cfg
Normal file
|
@ -0,0 +1,6 @@
|
|||
## Config file for Process Reloader (https://git.barkshark.xyz/barkshark/reload)
|
||||
exec = ./server.py
|
||||
watch_ext = py, env
|
||||
ignore_dirs = webapp/js, bin, dist, misc, test
|
||||
ignore_files = reload.py, test.py
|
||||
log_level = INFO
|
74
reload.py
|
@ -1,74 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
import time
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from watchdog.observers import Observer
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
os.environ['PYENV'] = 'dev'
|
||||
from config import logging
|
||||
|
||||
|
||||
pid = ''
|
||||
|
||||
|
||||
def run(restart=False):
|
||||
global pid
|
||||
|
||||
if restart == True and pid != '':
|
||||
logging.info('Terminating process...')
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
logging.info('Starting process...')
|
||||
proc = subprocess.Popen(['nohup', './server.py'], preexec_fn=os.setpgrp)
|
||||
pid = proc.pid
|
||||
|
||||
logging.info(f'Process PID: {pid}')
|
||||
|
||||
|
||||
class MyHandler(FileSystemEventHandler):
|
||||
def on_modified(self, event):
|
||||
filename, ext = os.path.splitext(os.path.relpath(event.src_path))
|
||||
|
||||
if event.event_type == 'modified' and ext == '.py' and re.search(ignore_paths, filename) == None and filename not in ignore_filenames:
|
||||
logging.info('Restarting server...')
|
||||
run(restart=True)
|
||||
|
||||
|
||||
def safe_stop():
|
||||
logging.info('Stopping process watcher')
|
||||
observer.stop()
|
||||
observer.join()
|
||||
|
||||
if pid != '':
|
||||
logging.info(f'Killing process: {pid}')
|
||||
os.kill(pid, signal.SIGTERM)
|
||||
|
||||
sys.exit()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
observer = Observer()
|
||||
observer.schedule(MyHandler(), path, recursive=True)
|
||||
|
||||
ignore_paths = 'webapp/js|bin|dist|misc|data'
|
||||
ignore_filenames = ['reload']
|
||||
|
||||
logging.info('Starting process watcher')
|
||||
observer.start()
|
||||
|
||||
try:
|
||||
while True:
|
||||
time.sleep(100)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
safe_stop()
|
|
@ -1,9 +1,16 @@
|
|||
psycopg2==2.8.3
|
||||
pygresql==5.1
|
||||
dbutils==1.3
|
||||
envbash==1.1.2
|
||||
pycryptodome==3.8.2
|
||||
pycryptodome==3.9.0
|
||||
colour==0.1.5
|
||||
jinja2==2.10.1
|
||||
aiohttp==3.6.0
|
||||
aiohttp-cors==0.7.0
|
||||
git+https://git.barkshark.tk/izaliamae/aiohttp-jinja2.git
|
||||
git+https://git.barkshark.xyz/izaliamae/aiohttp-jinja2.git
|
||||
dramatiq[redis, watch]==1.7.0
|
||||
tinydb==3.15.0
|
||||
tinyrecord==0.1.5
|
||||
tinydb_smartcache==1.0.2
|
||||
watchdog==0.8.3
|
||||
validators==0.14.0
|
||||
pyyaml==5.1.2
|
||||
|
|
156
routes.py
|
@ -1,156 +0,0 @@
|
|||
import asyncio
|
||||
import aiohttp
|
||||
import jinja2
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from jinja2 import FileSystemLoader, select_autoescape
|
||||
|
||||
from config import config, logging
|
||||
|
||||
from backend import mastodon_api, api, middleware, wellknown
|
||||
from backend.database import SETTINGS, newtrans, get, update_timestamps
|
||||
from frontend import views, resources, user, redirects
|
||||
from functions import color, todate, themes
|
||||
from frontend.template_loader import CustomLoader
|
||||
|
||||
|
||||
async def glob_vars(request):
|
||||
return {
|
||||
'name': SETTINGS['name'],
|
||||
'domain': config['web_domain'],
|
||||
'settings': SETTINGS,
|
||||
'get_cookie': get.login_cookie,
|
||||
'get_user': get.user,
|
||||
'newtrans': newtrans,
|
||||
'lighten': color().lighten,
|
||||
'darken': color().darken,
|
||||
'saturate': color().saturate,
|
||||
'desaturate': color().desaturate,
|
||||
'rgba': color().rgba,
|
||||
'todate': todate,
|
||||
'themes': themes,
|
||||
'json': json
|
||||
}
|
||||
|
||||
|
||||
web = aiohttp.web.Application(middlewares=[
|
||||
#middleware.http_signatures,
|
||||
middleware.http_auth,
|
||||
middleware.http_filter,
|
||||
middleware.http_trailing_slash
|
||||
])
|
||||
|
||||
aiohttp_jinja2.setup(web,
|
||||
loader=CustomLoader('frontend/templates'),
|
||||
autoescape=select_autoescape(['html', 'xml', 'css']),
|
||||
context_processors=[glob_vars],
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True
|
||||
)
|
||||
|
||||
env = aiohttp_jinja2.get_env(web)
|
||||
|
||||
|
||||
# Public pages
|
||||
web.router.add_get('/', views.home_get)
|
||||
web.router.add_get('/register', views.register_get)
|
||||
web.router.add_get('/login', views.login_get)
|
||||
web.router.add_get('/logout', views.logout_get)
|
||||
web.router.add_post('/register', views.register_post)
|
||||
web.router.add_post('/login', views.login_post)
|
||||
|
||||
# Semi-public pages
|
||||
web.router.add_get('/@{user}', views.user_get)
|
||||
web.router.add_get('/:{status}', views.post_get)
|
||||
web.router.add_get('/user/{user}', redirects.user_get)
|
||||
web.router.add_get('/status/{status}', redirects.post_get)
|
||||
web.router.add_post('/:{status}', user.post_delete_post)
|
||||
|
||||
# Frontend
|
||||
web.router.add_get('/welcome', user.welcome_get)
|
||||
web.router.add_get('/settings', user.settings_get)
|
||||
web.router.add_get('/admin', user.admin_get)
|
||||
web.router.add_post('/settings', user.settings_post)
|
||||
web.router.add_post('/admin', user.admin_post)
|
||||
web.router.add_post('/poast', user.poast_post)
|
||||
|
||||
# CSS, JS, etc
|
||||
#web.router.add_get('/layout.css', resources.layout_get)
|
||||
web.router.add_get('/style.css', resources.color_get)
|
||||
web.router.add_get('/manifest.json', resources.manifest_get)
|
||||
web.router.add_get('/favicon.ico', resources.favicon_get)
|
||||
web.router.add_get('/robots.txt', resources.robots_txt)
|
||||
|
||||
# Media
|
||||
web.router.add_static('/static', path='frontend/static', name='static')
|
||||
web.router.add_static('/media', path='data/media', name='media')
|
||||
|
||||
# Native API
|
||||
web.router.add_get('/api/native/{name}', api.handle_get)
|
||||
web.router.add_post('/api/native/{name}', api.handle_post)
|
||||
|
||||
# Mastodon API
|
||||
#web.router.add_post('/api/v1/{name}', mastodon_api.handle_post)
|
||||
#web.router.add_get('/api/v1/{name}', mastodon_api.handle_get)
|
||||
#web.router.add_get('/api/v1/streaming/{name}', mastodon_api.streaming_get)
|
||||
|
||||
# Various info endpoints
|
||||
#web.router.add_get('/nodeinfo/2.0.json', wellknown.nodeinfo_json)
|
||||
#web.router.add_get('/.well-known/nodeinfo', wellknown.nodeinfo_get)
|
||||
#web.router.add_get('/.well-known/host-meta', wellknown.hostmeta_get)
|
||||
#web.router.add_get('/.well-known/webfinger', wellknown.webfinger_get)
|
||||
|
||||
# Shitpost
|
||||
web.router.add_get('/headpats', redirects.headpats)
|
||||
web.router.add_get('/socks', redirects.socks)
|
||||
|
||||
|
||||
async def start_web():
|
||||
runner = aiohttp.web.AppRunner(web, access_log_format='%{X-Real-Ip}i "%r" %s %b "%{User-Agent}i"')
|
||||
await runner.setup()
|
||||
|
||||
listen = config['listen']
|
||||
port = config['port']
|
||||
|
||||
logging.info('Starting web server at {listen}:{port}'.format(listen=listen,port=port))
|
||||
|
||||
site = aiohttp.web.TCPSite(runner, listen, port)
|
||||
|
||||
await site.start()
|
||||
|
||||
|
||||
async def save_tokens():
|
||||
while True:
|
||||
logging.debug('Saving updated timestamps for login and auth tokens to the database')
|
||||
update_timestamps()
|
||||
|
||||
await asyncio.sleep(600)
|
||||
|
||||
|
||||
def safe_stop(*args):
|
||||
logging.debug('Saving updated timestamps')
|
||||
update_timestamps()
|
||||
|
||||
logging.info('Bye')
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main():
|
||||
signal.signal(signal.SIGHUP, safe_stop)
|
||||
signal.signal(signal.SIGINT, safe_stop)
|
||||
signal.signal(signal.SIGQUIT, safe_stop)
|
||||
signal.signal(signal.SIGTERM, safe_stop)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(start_web())
|
||||
asyncio.ensure_future(save_tokens())
|
||||
loop.run_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
safe_stop()
|
15
server.py
|
@ -3,10 +3,9 @@ from watchdog.observers import Observer
|
|||
from watchdog.events import FileSystemEventHandler
|
||||
from os import environ as ENV
|
||||
|
||||
import routes
|
||||
|
||||
from config import logging
|
||||
from webapp import precompile
|
||||
from social.config import logging
|
||||
from social.web_server import main
|
||||
|
||||
|
||||
class MyHandler(FileSystemEventHandler):
|
||||
|
@ -17,15 +16,7 @@ class MyHandler(FileSystemEventHandler):
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if ENV.get('PYENV', 'default').lower() in ['dev', 'default']:
|
||||
path = 'webapp/js'
|
||||
observer = Observer()
|
||||
observer.schedule(MyHandler(), path, recursive=True)
|
||||
|
||||
logging.info('Starting javascript watcher')
|
||||
observer.start()
|
||||
|
||||
routes.main()
|
||||
main()
|
||||
|
||||
if ENV.get('PYENV', 'default').lower() in ['dev', 'default']:
|
||||
logging.info('Stopping javascript watcher')
|
||||
|
|
|
@ -7,7 +7,7 @@ import re
|
|||
from datetime import datetime
|
||||
from collections import OrderedDict
|
||||
|
||||
from config import logging
|
||||
from social.config import logging
|
||||
|
||||
|
||||
def parse_ttl(ttl):
|
||||
|
@ -46,52 +46,46 @@ class TTLCache:
|
|||
self.maxsize = maxsize
|
||||
|
||||
|
||||
def __contains__(self, key):
|
||||
if key in self.items:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
|
||||
if timestamp >= self.items[key]['timestamp']:
|
||||
del self.items[key]
|
||||
|
||||
else:
|
||||
self.items[key]['timestamp'] = timestamp + self.ttl
|
||||
self.items.move_to_end(key)
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def invalidate(self, key):
|
||||
if key in self.items:
|
||||
del self.items[key]
|
||||
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def store(self, key, value):
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
item = self.items.get(key)
|
||||
|
||||
while len(self.items) >= self.maxsize and self.maxsize != 0:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
self.items.popitem(last=False)
|
||||
|
||||
if (key in self.items) == False:
|
||||
data = {'data': value, 'timestamp': timestamp}
|
||||
if item == None:
|
||||
logging.debug(f'adding to cache during store {key}')
|
||||
data = {'data': value}
|
||||
self.items[key] = data
|
||||
|
||||
if self.items[key]['timestamp'] + self.ttl < timestamp:
|
||||
elif self.items[key]['timestamp'] + self.ttl < timestamp:
|
||||
logging.debug(f'deleting from cache during store {key}')
|
||||
del self.items[key]
|
||||
|
||||
self.items[key]['timestamp'] = timestamp
|
||||
self.items[key]['timestamp'] = timestamp + self.ttl
|
||||
self.items.move_to_end(key)
|
||||
|
||||
|
||||
def fetch(self, key):
|
||||
if key in self:
|
||||
return self.items[key]['data']
|
||||
item = self.items.get(key)
|
||||
|
||||
return None
|
||||
if item != None:
|
||||
timestamp = int(datetime.timestamp(datetime.now()))
|
||||
|
||||
if timestamp >= self.items[key]['timestamp']:
|
||||
logging.debug(f'removing from cache during fetch {key}')
|
||||
del self.items[key]
|
||||
|
||||
else:
|
||||
logging.debug(f'updating timestamp during fetch {key}')
|
||||
self.items[key]['timestamp'] = timestamp + self.ttl
|
||||
self.items.move_to_end(key)
|
||||
return self.items[key]['data']
|
||||
|
||||
|
||||
class LRUCache:
|
||||
|
@ -123,4 +117,4 @@ class LRUCache:
|
|||
if key in self.items:
|
||||
return self.items[key]
|
||||
|
||||
return None
|
||||
return {}
|
||||
|
|
|
@ -1,28 +1,49 @@
|
|||
import aiohttp, json, re, sys, os, asyncio
|
||||
import aiohttp, json, sys, os, asyncio
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from config import config
|
||||
from json.decoder import *
|
||||
|
||||
from .database import *
|
||||
from functions import json_error
|
||||
from .misc import sanitize
|
||||
|
||||
from ..config import config
|
||||
from ..database import *
|
||||
from ..functions import json_error
|
||||
from .. import oauth
|
||||
|
||||
|
||||
class post_cmd:
|
||||
def apps():
|
||||
return 'heck'
|
||||
def apps(data):
|
||||
fields = ['redirect_uris', 'scopes', 'client_name', 'website']
|
||||
for line in fields:
|
||||
if line not in data:
|
||||
data[line] = None
|
||||
|
||||
retdata = oauth.create.app(data['redirect_uris'], data['scopes'], data['client_name'], data['website'])
|
||||
|
||||
if type(retdata) == str:
|
||||
return json_error(400, 'Something fucked up')
|
||||
|
||||
return retdata
|
||||
|
||||
|
||||
class get_cmd:
|
||||
def instance():
|
||||
stats = get.server_stats()
|
||||
settings = get.settings
|
||||
data = {
|
||||
'version': '2.9.0 (compatible; Social {})'.format(config['version']),
|
||||
'version': f'2.9.0 (compatible; Social {config["version"]})',
|
||||
'uri': config['domain'],
|
||||
'title': config['name'],
|
||||
'title': settings('name'),
|
||||
'description': 'OwO',
|
||||
'urls': {
|
||||
'streaming_api': 'wss://'+config['domain']
|
||||
'streaming_api': f'wss://{config["web_domain"]}'
|
||||
},
|
||||
'max_toot_chars': config['vars']['max_chars'],
|
||||
'stats': {
|
||||
'user_count': stats['user_count'],
|
||||
'status_count': stats['status_count'],
|
||||
'domain_count': stats['domain_count']
|
||||
},
|
||||
'max_toot_chars': settings('char_limit'),
|
||||
'contact_account': {
|
||||
'id': '',
|
||||
'username': '',
|
||||
|
@ -45,32 +66,19 @@ class stream_cmd:
|
|||
return 'im gay'
|
||||
|
||||
|
||||
def sanitize(data, endpoint):
|
||||
def regex_clean(target_string, spaces):
|
||||
if spaces == True:
|
||||
return re.sub(r'([^a-zA-Z@_.\-\d\s]+)', '', target_string).strip()
|
||||
|
||||
else:
|
||||
return re.sub(r'([^a-zA-Z@_.\-\d]+)', '', target_string).strip()
|
||||
|
||||
target = []
|
||||
|
||||
for line in data:
|
||||
if data[0] == 'name':
|
||||
target.append(regex_clean(line, True))
|
||||
|
||||
else:
|
||||
target.append(regex_clean(line, False))
|
||||
|
||||
return target
|
||||
|
||||
|
||||
async def handle_post(request):
|
||||
command = request.match_info['name']
|
||||
data = sanitize(await request.json(), command)
|
||||
|
||||
try:
|
||||
post_data = await request.json()
|
||||
|
||||
except JSONDecodeError:
|
||||
post_data = await request.post()
|
||||
|
||||
data = sanitize(post_data, command)
|
||||
|
||||
if callable(getattr(post_cmd, command, True)):
|
||||
return_msg = eval('post_cmd.'+command+'(data)')
|
||||
msg = eval('post_cmd.'+command+'(data)')
|
||||
|
||||
else:
|
||||
json_error(404, 'InvalidCommand')
|
28
social/api/misc.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
import re
|
||||
|
||||
|
||||
def sanitize(data, endpoint):
|
||||
def regex_clean(target_string, spaces):
|
||||
if spaces == True:
|
||||
space = '\s'
|
||||
|
||||
else:
|
||||
space = ''
|
||||
|
||||
return re.sub(r'([^a-zA-Z@_.,\-\d/\[\]\'\:{}]+)'.format(space), '', target_string).strip()
|
||||
|
||||
target = {}
|
||||
|
||||
for line in data:
|
||||
if line == 'name':
|
||||
target[line] = regex_clean(data[line], True)
|
||||
|
||||
if line in ['scope', 'scopes']:
|
||||
target[line] = []
|
||||
for scope in data[line].split(' '):
|
||||
target[line].append(regex_clean(scope, False))
|
||||
|
||||
else:
|
||||
target[line] = regex_clean(data[line], False)
|
||||
|
||||
return target
|
|
@ -7,9 +7,9 @@ import re
|
|||
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from .database import cache, newtrans, get, put, update, delete
|
||||
from functions import json_error
|
||||
from config import config, logging
|
||||
from ..database import newtrans, get, put, update, delete
|
||||
from ..functions import json_error
|
||||
from ..config import config, logging
|
||||
|
||||
|
||||
class post_cmd:
|
||||
|
@ -121,8 +121,6 @@ class post_cmd:
|
|||
def getuser(data):
|
||||
user = get_profile(data['user'])
|
||||
|
||||
print(json.dumps(user))
|
||||
|
||||
if user == False:
|
||||
return {'err': 'UserNotExist'}
|
||||
|
||||
|
@ -159,12 +157,6 @@ class post_cmd:
|
|||
else:
|
||||
return {'msg': 'TablesUpdated'}
|
||||
|
||||
|
||||
def cache(data):
|
||||
#return {'tokens': str(cache.token.items), 'cookies': str(cache.cookie.items)}
|
||||
return {'cookies': cache.cookie.items, 'tokens': cache.token.items, 'users': cache.user.items}
|
||||
|
||||
|
||||
#-----------------------
|
||||
|
||||
class get_cmd:
|
88
social/api/oauth.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import aiohttp, json, sys, os, asyncio
|
||||
|
||||
from urllib.parse import urlparse
|
||||
from json.decoder import *
|
||||
|
||||
from .misc import sanitize
|
||||
|
||||
from ..config import config
|
||||
from ..database import *
|
||||
from ..functions import json_error, http_error
|
||||
from .. import oauth
|
||||
|
||||
|
||||
class post_cmd:
|
||||
def token(data):
|
||||
if data.get('grant_type') == 'password':
|
||||
pass
|
||||
|
||||
elif data.get('grant_type') == 'auth':
|
||||
pass
|
||||
|
||||
else:
|
||||
print(data.get('grant_type'))
|
||||
return json_error(400, f'Invalid grant_type: {data.get("grant_type")}')
|
||||
|
||||
|
||||
class get_cmd:
|
||||
def authorize(data):
|
||||
login_token = data['request'].cookies.get('login_token')
|
||||
client_id = data.get('client_id')
|
||||
redirect_uri = data.get('redirect_uri')
|
||||
|
||||
if None in [client_id, redirect_uri, login_token]:
|
||||
return http_error(400, data['request'], msg='Missing client_id or redirect_uri')
|
||||
|
||||
if data.get('response_type') == 'code':
|
||||
pass
|
||||
|
||||
return 'UwU'
|
||||
|
||||
|
||||
async def handle_post(request):
|
||||
command = request.match_info['name']
|
||||
|
||||
try:
|
||||
post_data = await request.json()
|
||||
|
||||
except JSONDecodeError:
|
||||
post_data = await request.post()
|
||||
|
||||
data = sanitize(post_data, command)
|
||||
|
||||
if callable(getattr(post_cmd, command, True)):
|
||||
msg = eval('post_cmd.'+command+'(data)')
|
||||
|
||||
else:
|
||||
json_error(404, 'InvalidCommand')
|
||||
|
||||
return aiohttp.web.Response(
|
||||
status=200,
|
||||
content_type="application/json",
|
||||
charset="utf-8",
|
||||
text=json.dumps(msg)
|
||||
)
|
||||
|
||||
|
||||
async def handle_get(request):
|
||||
command = request.match_info['name']
|
||||
post_data = request.query
|
||||
|
||||
data = sanitize(post_data, command)
|
||||
data['request'] = request
|
||||
|
||||
if callable(getattr(get_cmd, command, True)):
|
||||
msg = eval('get_cmd.'+command+'(data)')
|
||||
|
||||
else:
|
||||
http_error(404, 'InvalidCommand')
|
||||
|
||||
if type(msg) == aiohttp.web_response.Response:
|
||||
return msg
|
||||
|
||||
return aiohttp.web.Response(
|
||||
status=200,
|
||||
content_type="text/html",
|
||||
charset="utf-8",
|
||||
text=msg
|
||||
)
|
|
@ -1,12 +1,13 @@
|
|||
import os
|
||||
import sys
|
||||
import aiohttp
|
||||
import logging as logger
|
||||
|
||||
from envbash import load_envbash
|
||||
from os import environ as env
|
||||
from os.path import abspath, dirname
|
||||
|
||||
from functions import boolean
|
||||
from .functions import boolean
|
||||
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
|
@ -15,7 +16,7 @@ if getattr(sys, 'frozen', False):
|
|||
else:
|
||||
script_path = dirname(abspath(__file__))
|
||||
|
||||
stor_path = script_path+'/data'
|
||||
stor_path = script_path+'/../data'
|
||||
os.makedirs(stor_path+'/media', exist_ok=True)
|
||||
|
||||
pyenv = env.get('PYENV', 'default').lower()
|
||||
|
@ -41,7 +42,7 @@ listen = env.get('LISTEN', '127.0.0.1')
|
|||
port = int(env.get('PORT', 8020))
|
||||
|
||||
config = {
|
||||
'version': '0.1',
|
||||
'version': '0.1+pre-alpha',
|
||||
'listen': listen,
|
||||
'port': port,
|
||||
'stream_listen': env.get('STREAM_LISTEN', listen),
|
||||
|
@ -53,6 +54,7 @@ config = {
|
|||
'log_errors': boolean(env.get('LOG_ERRORS')),
|
||||
'log_date': boolean(env.get('LOG_DATE', True)),
|
||||
'salt': env.get('PASS_SALT'),
|
||||
'db_type': 'pg' if env.get('DB_TYPE').lower() == 'pg' else 'tiny',
|
||||
'vars': {
|
||||
'max_chars': env.get('MAX_CHARS', 69420),
|
||||
'posts': env.get('PROFILE_POSTS', 20),
|
||||
|
@ -63,7 +65,7 @@ config = {
|
|||
'port': int(env.get('REDIS_PORT', 6379)),
|
||||
},
|
||||
|
||||
'db': {
|
||||
'pg': {
|
||||
'host': env.get('DB_HOST', None),
|
||||
'port': int(env.get('DB_PORT', 5432)),
|
||||
'user': env.get('DB_USER', env.get('USER', 'social')),
|
||||
|
@ -81,31 +83,30 @@ else:
|
|||
log_date = '[%(asctime)s] '
|
||||
|
||||
|
||||
## 50 Critical
|
||||
## 40 Error
|
||||
## 30 Warning
|
||||
## 20 Info
|
||||
## 10 Debug
|
||||
|
||||
logging = logger.getLogger()
|
||||
|
||||
log_format = '{}%(levelname)s: %(message)s'
|
||||
log_format = '%(levelname)s: %(message)s'
|
||||
date_format = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
logging.setLevel(logger.DEBUG)
|
||||
|
||||
if config['log_errors'] == True:
|
||||
logfile = logger.FileHandler(stor_path+'/errors.log')
|
||||
logfile.name = 'Error Log'
|
||||
logfile.level = logger.WARNING
|
||||
logfile.formatter = logger.Formatter(log_format.format('[%(asctime)s] '))
|
||||
logfile.formatter = logger.Formatter('[%(asctime)s] {log_format}', date_format)
|
||||
logging.addHandler(logfile)
|
||||
|
||||
console = logger.StreamHandler()
|
||||
console.name = 'Console Log'
|
||||
console.level = eval('logger.'+config['log_level'])
|
||||
console.formatter = logger.Formatter(log_format.format(log_date))
|
||||
console.formatter = logger.Formatter(f'{log_date}{log_format}', date_format)
|
||||
logging.addHandler(console)
|
||||
|
||||
|
||||
header_string = f'aiohttp/{aiohttp.__version__} (Barkshark-Social/{config["version"]}; +https://{config["web_domain"]}/)'
|
||||
|
||||
|
||||
if pyenv in ['prod', 'default']:
|
||||
if pyenv == 'default':
|
||||
logging.warning('No environment specified. Assuming development')
|
12
social/database.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from .config import config, logging
|
||||
|
||||
if config['db_type'] == 'tiny':
|
||||
from .db_tiny import get, put, update, delete, newtrans, query
|
||||
|
||||
elif config['db_type'] == 'pg':
|
||||
from .db_pg import get, put, update, delete, newtrans, cache, update_timestamps
|
||||
|
||||
else:
|
||||
logging.error('Invalid database type. Please use "pg" or "tinydb"')
|
||||
|
||||
__all__ = ['get', 'put', 'update', 'delete', 'newtrans']
|
|
@ -5,17 +5,19 @@ import json
|
|||
|
||||
from DBUtils.PooledPg import PooledPg
|
||||
|
||||
from config import config, script_path, logging
|
||||
from simplecache import TTLCache
|
||||
from ..config import config, script_path, logging
|
||||
from ..functions import genkey
|
||||
from simplecache import TTLCache, LRUCache
|
||||
|
||||
|
||||
class cache:
|
||||
token = TTLCache(maxsize=4096, ttl='1h')
|
||||
cookie = TTLCache(maxsize=4096, ttl='1h')
|
||||
user = TTLCache(maxsize=4096, ttl='1h')
|
||||
token = TTLCache(maxsize=4096, ttl='6h')
|
||||
cookie = TTLCache(maxsize=4096, ttl='6h')
|
||||
user = TTLCache(maxsize=4096, ttl='6h')
|
||||
post = TTLCache(maxsize=4096, ttl='1d')
|
||||
misc = LRUCache(maxsize=64)
|
||||
|
||||
DB_CONFIG = config['db']
|
||||
DB_CONFIG = config['pg']
|
||||
|
||||
|
||||
def dbconn(database, pooled=True):
|
||||
|
@ -45,8 +47,6 @@ def db_check():
|
|||
db_setup.query(database)
|
||||
db_setup.close()
|
||||
|
||||
logging.info('Done :3')
|
||||
|
||||
pre_db.close()
|
||||
|
||||
|
||||
|
@ -85,32 +85,15 @@ def first_setup():
|
|||
for key in settings:
|
||||
db.insert('settings', setting=key, val=settings[key])
|
||||
|
||||
logging.info('Database setup')
|
||||
|
||||
|
||||
def settings():
|
||||
setresults = db.query('SELECT * FROM settings').dictresult()
|
||||
|
||||
if setresults == []:
|
||||
logging.warning('Can\'t find settings in the database')
|
||||
return
|
||||
|
||||
set_dict = {}
|
||||
|
||||
for line in setresults:
|
||||
if line['setting'] not in ['pubkey', 'privkey']:
|
||||
set_dict.update({line['setting']: line['val']})
|
||||
|
||||
return set_dict
|
||||
logging.info('Database setup finished :3')
|
||||
|
||||
|
||||
def update_timestamps():
|
||||
for token in cache.token.items:
|
||||
db.update('auth_tokens', {'id': cache.token.items[token]['id']}, access=cache.token.items[token]['timestamp'])
|
||||
db.update('auth_tokens', {'id': cache.token.items[token]['data']['id']}, access=cache.token.items[token]['timestamp'])
|
||||
|
||||
for token in cache.cookie.items:
|
||||
db.update('login_cookies', {'id': cache.cookie.items[token]['id']}, access=cache.cookie.items[token]['timestamp'])
|
||||
db.update('login_cookies', {'id': cache.cookie.items[token]['data']['id']}, access=cache.cookie.items[token]['timestamp'])
|
||||
|
||||
|
||||
newtrans(first_setup())
|
||||
SETTINGS = settings()
|
|
@ -1,6 +1,4 @@
|
|||
from . import db, cache, get
|
||||
|
||||
from config import logging
|
||||
from . import db, cache, get, logging
|
||||
|
||||
|
||||
def login_cookie(cid, cookie):
|
|
@ -1,8 +1,29 @@
|
|||
import json
|
||||
|
||||
from . import db, cache
|
||||
from . import db, cache, logging, config
|
||||
from ..functions import timestamp
|
||||
|
||||
from config import logging, config
|
||||
|
||||
def handle_to_userid(handle):
|
||||
fetch = cache.user.fetch(handle)
|
||||
|
||||
if fetch != None:
|
||||
logging.debug('Returning cached user id')
|
||||
|
||||
return fetch
|
||||
|
||||
user = db.query(f'SELECT * FROM users WHERE handle = \'{handle}\'').dictresult()
|
||||
|
||||
if user == []:
|
||||
return None
|
||||
|
||||
else:
|
||||
userid = user[0]['id']
|
||||
|
||||
logging.debug('Saving userid to the cache')
|
||||
cache.user.store(handle, userid)
|
||||
|
||||
return userid
|
||||
|
||||
|
||||
def api_token(token):
|
||||
|
@ -12,7 +33,7 @@ def api_token(token):
|
|||
logging.debug('Returning cached token')
|
||||
return fetch
|
||||
|
||||
raw_token = db.query(f'SELECT * FROM tokens WHERE token = \'{token}\'').dictresult()
|
||||
raw_token = db.query(f'SELECT * FROM auth_tokens WHERE token = \'{token}\'').dictresult()
|
||||
|
||||
if raw_token == []:
|
||||
return
|
||||
|
@ -101,51 +122,19 @@ def posts(username, postid, newtrans=False):
|
|||
return posts
|
||||
|
||||
|
||||
def userid_to_handle(userid):
|
||||
fetch = cache.user.fetch(userid)
|
||||
def user(user, filters=None):
|
||||
if user == None:
|
||||
return
|
||||
|
||||
if fetch != None:
|
||||
logging.debug('Returning cached user handle')
|
||||
|
||||
return fetch
|
||||
|
||||
user = db.query(f'SELECT handle FROM users WHERE id = {userid}').dictresult()
|
||||
|
||||
if user == []:
|
||||
return None
|
||||
if isinstance(user, str):
|
||||
userid = handle_to_userid(user.lower())
|
||||
|
||||
else:
|
||||
handle = user[0]['handle']
|
||||
userid = user
|
||||
|
||||
logging.debug('Saving user handle to the cache')
|
||||
cache.user.store(userid, handle)
|
||||
if userid == None:
|
||||
return
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def handle_to_userid(handle):
|
||||
fetch = cache.user.fetch(handle)
|
||||
|
||||
if fetch != None:
|
||||
logging.debug('Returning cached user id')
|
||||
|
||||
return fetch
|
||||
|
||||
user = db.query(f'SELECT id FROM users WHERE handle = {userid}').dictresult()
|
||||
|
||||
if user == []:
|
||||
return None
|
||||
|
||||
else:
|
||||
userid = user[0]['handle']
|
||||
|
||||
logging.debug('Saving userid to the cache')
|
||||
cache.user.store(userid, handle)
|
||||
|
||||
return handle
|
||||
|
||||
|
||||
def user(username, filters=None):
|
||||
def filter_data(data, fields):
|
||||
if data == None:
|
||||
logging.warning('Missing data for filtering.')
|
||||
|
@ -163,17 +152,14 @@ def user(username, filters=None):
|
|||
else:
|
||||
return data
|
||||
|
||||
if isinstance(username, int):
|
||||
username = userid_to_handle(username)
|
||||
|
||||
fetch = cache.user.fetch(username)
|
||||
fetch = cache.user.fetch(userid)
|
||||
|
||||
if fetch != None:
|
||||
logging.debug('Returning cached user data')
|
||||
|
||||
return filter_data(fetch, filters)
|
||||
|
||||
raw_user_data = db.query(f'SELECT * FROM users WHERE handle = \'{username}\'').dictresult()
|
||||
raw_user_data = db.query(f'SELECT * FROM users WHERE id = \'{userid}\'').dictresult()
|
||||
|
||||
if raw_user_data == []:
|
||||
return None
|
||||
|
@ -186,9 +172,10 @@ def user(username, filters=None):
|
|||
user_data['info_table'] = json.loads(table)
|
||||
|
||||
logging.debug('Saving user data to cache')
|
||||
cache.user.store(user_data['handle'], user_data)
|
||||
cache.user.store(user_data['id'], user_data)
|
||||
user_data = filter_data(cache.user.fetch(user_data['id']), filters)
|
||||
|
||||
return filter_data(cache.user.fetch(user_data['handle']), filters)
|
||||
return user_data
|
||||
|
||||
|
||||
def profile(handle, postid=None):
|
||||
|
@ -206,3 +193,43 @@ def profile(handle, postid=None):
|
|||
user_data.update(post_count[0])
|
||||
|
||||
return user_data
|
||||
|
||||
|
||||
def server_stats():
|
||||
ts = timestamp()
|
||||
cached_stats = cache.misc.fetch('stats')
|
||||
|
||||
if cached_stats.get('timestamp') == None or cached_stats.get('timestamp') + 3600 < ts:
|
||||
cache.misc.invalidate('stats')
|
||||
stats = {
|
||||
'user_count': db.query('SELECT COUNT(*) FROM users WHERE domain_id is NULL;').dictresult()[0]['count'],
|
||||
'status_count': db.query('SELECT COUNT(*) FROM statuses WHERE id is not NULL;').dictresult()[0]['count'],
|
||||
'domain_count': db.query('SELECT COUNT(*) FROM domains WHERE id is not NULL;').dictresult()[0]['count'],
|
||||
'timestamp': ts
|
||||
}
|
||||
|
||||
cache.misc.store('stats', stats)
|
||||
|
||||
return cache.misc.fetch('stats')
|
||||
|
||||
|
||||
def settings(name):
|
||||
if type(name) != str:
|
||||
logging.error('get.settings only accepts a string, dingus!')
|
||||
|
||||
if name != 'all':
|
||||
setresults = db.query(f'SELECT * FROM settings WHERE setting = \'{name}\'').dictresult()
|
||||
|
||||
if setresults == []:
|
||||
logging.warning('Can\'t find settings in the database')
|
||||
return
|
||||
|
||||
return setresults[0]['val']
|
||||
|
||||
setresults = db.query(f'SELECT * FROM settings').dictresult()
|
||||
setret = {}
|
||||
|
||||
for line in setresults:
|
||||
setret.update({line['setting']: line['val']})
|
||||
|
||||
return setret
|
|
@ -1,11 +1,9 @@
|
|||
import pg
|
||||
|
||||
import time
|
||||
import json
|
||||
|
||||
from . import db
|
||||
|
||||
from config import logging, config
|
||||
from functions import mkhash, genkey, timestamp
|
||||
from . import db, logging, config
|
||||
from ..functions import mkhash, genkey, timestamp
|
||||
|
||||
|
||||
# Why the fuck did I think validating users via token on the db level was a good idea!?
|
||||
|
@ -21,7 +19,7 @@ def user(handle, email, password, display, bio, table, sig):
|
|||
user = db.insert('users',
|
||||
handle=handle.lower(), name=display, bio=bio,
|
||||
info_table=json.dumps(table), email=email, password=pass_hash,
|
||||
permissions=4, timestamp=timestamp, forum_sig=sig,
|
||||
permissions=4, timestamp=ts, forum_sig=sig,
|
||||
pubkey=keys['pubkey'], privkey=keys['privkey']
|
||||
)
|
||||
|
||||
|
@ -32,9 +30,9 @@ def user(handle, email, password, display, bio, table, sig):
|
|||
if user['id'] == 1:
|
||||
db.update('users', {'id': 1}, perms=0)
|
||||
|
||||
db.insert('tokens', userid=user['id'], appid=0, token=token)
|
||||
db.insert('auth_tokens', userid=user['id'], appid=0, token=token, timestamp=ts)
|
||||
|
||||
return {'id': user['id'], 'token': token, 'password': pass_hash, 'username': user['handle'], 'name': user['name']}
|
||||
return {'id': user['id'], 'token': token, 'password': pass_hash, 'username': user['handle'], 'name': user['name'], 'timestamp': ts}
|
||||
|
||||
|
||||
def login_cookie(userid, password, address, agent):
|
||||
|
@ -100,3 +98,13 @@ def local_post2(userid, posts):
|
|||
db.end()
|
||||
|
||||
return 'Done'
|
||||
|
||||
class oauth:
|
||||
def app(client_id, client_secret, redirect_uri, scope, name, url):
|
||||
if None in [client_id, client_secret, redirect_uri, scope, name]:
|
||||
return
|
||||
|
||||
db.insert('auth_apps',
|
||||
client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri,
|
||||
scope=scope, name=name, url=url
|
||||
)
|
|
@ -1,8 +1,5 @@
|
|||
from . import db, cache
|
||||
from . import get
|
||||
|
||||
from config import logging, config
|
||||
from functions import mkhash
|
||||
from . import db, cache, get, logging, config
|
||||
from ..functions import mkhash
|
||||
|
||||
|
||||
def profile(handle, data):
|
26
social/db_tiny/__init__.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from tinydb import TinyDB, Query
|
||||
from tinydb_smartcache import SmartCacheTable
|
||||
|
||||
from ..config import stor_path, config
|
||||
|
||||
db_path = f'{stor_path}'
|
||||
TinyDB.table_class = SmartCacheTable
|
||||
|
||||
db = TinyDB(f'{db_path}/db.json', create_dirs=True)
|
||||
post_db = TinyDB(f'{db_path}/db.json', create_dirs=True)
|
||||
|
||||
query = Query()
|
||||
|
||||
users = db.table('users')
|
||||
logins = db.table('logins')
|
||||
tokens = db.table('tokens')
|
||||
posts = post_db.table('posts')
|
||||
settings = db.table('settings')
|
||||
|
||||
|
||||
def newtrans(funct):
|
||||
# postgresql needs this, but tinydb doesn't, so just pass the function alone
|
||||
return funct
|
||||
|
||||
|
||||
__all__ = ['db', 'query', 'newtrans', 'users', 'logins', 'tokens', 'posts']
|
3
social/db_tiny/delete.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from tinyrecord import transaction
|
||||
|
||||
from . import *
|
122
social/db_tiny/get.py
Normal file
|
@ -0,0 +1,122 @@
|
|||
import json
|
||||
|
||||
from . import *
|
||||
from ..config import logging, config
|
||||
|
||||
|
||||
def handle_to_userid(handle):
|
||||
user = users.get(query.handle == handle)
|
||||
|
||||
return user.get('id')
|
||||
|
||||
|
||||
def api_token(token):
|
||||
raw_token = db.query(f'SELECT * FROM auth_tokens WHERE token = \'{token}\'').dictresult()
|
||||
|
||||
if raw_token == []:
|
||||
return
|
||||
|
||||
token_data = raw_token[0]
|
||||
logging.debug('Caching new token')
|
||||
cache.token.store(token_data['token'], token_data)
|
||||
|
||||
return token_data
|
||||
|
||||
|
||||
def login_cookie(cookie):
|
||||
login = logins.get(query.cookie == cookie)
|
||||
|
||||
if login == []:
|
||||
return None
|
||||
|
||||
print(login)
|
||||
|
||||
return login
|
||||
|
||||
|
||||
def post(postid):
|
||||
try:
|
||||
int(postid)
|
||||
|
||||
except ValueError:
|
||||
return
|
||||
|
||||
post = posts.get(id == postid)
|
||||
|
||||
if post == []:
|
||||
return None
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def posts(username, postid):
|
||||
if postid != None:
|
||||
page = f'and id < {postid}'
|
||||
|
||||
else:
|
||||
page = ''
|
||||
|
||||
user_data = user(handle_to_userid(username))
|
||||
postlimit = config['vars']['posts']
|
||||
|
||||
raw_posts = posts.get((query.id >= postid-postlimit) & (query.id < postid))
|
||||
|
||||
posts = []
|
||||
|
||||
for post in raw_posts:
|
||||
posts.append(post)
|
||||
posts[post]['user'] = user(post['userid'])
|
||||
|
||||
return posts
|
||||
|
||||
|
||||
def user(userid, filters=None):
|
||||
if user == None:
|
||||
return
|
||||
|
||||
def filter_data(data, fields):
|
||||
if data == None:
|
||||
logging.warning('Missing data for filtering.')
|
||||
return None
|
||||
|
||||
if fields != None:
|
||||
new_data = {}
|
||||
|
||||
for item in data:
|
||||
if item not in fields.split(','):
|
||||
new_data[item] = data[item]
|
||||
|
||||
return new_data
|
||||
|
||||
else:
|
||||
return data
|
||||
|
||||
user = db.get(query.id == userid)
|
||||
|
||||
if user == []:
|
||||
return None
|
||||
|
||||
user_data = user.copy()
|
||||
table = user['info_table']
|
||||
|
||||
if table != None:
|
||||
user_data['info_table'] = json.loads(table)
|
||||
|
||||
return filter_data(user_data, filters)
|
||||
|
||||
|
||||
def profile(handle, postid=None):
|
||||
user = user(handle_to_userid(handle))
|
||||
|
||||
if user == None:
|
||||
return None
|
||||
|
||||
user_data = {'profile': user.copy()}
|
||||
userid = user['id']
|
||||
|
||||
post_count = db.query(f'SELECT COUNT(*) FROM statuses WHERE userid={userid};').dictresult()
|
||||
|
||||
user_data['post'] = posts(handle, postid)
|
||||
user_data.update(post_count[0])
|
||||
|
||||
return user_data
|
108
social/db_tiny/put.py
Normal file
|
@ -0,0 +1,108 @@
|
|||
import time
|
||||
import json
|
||||
|
||||
from tinyrecord import transaction
|
||||
|
||||
from . import *
|
||||
from ..config import logging, config
|
||||
from ..functions import mkhash, genkey, timestamp
|
||||
|
||||
|
||||
def user(handle, email, password, display, bio, table, sig):
|
||||
keys = genkey()
|
||||
|
||||
ts = timestamp()
|
||||
token_string = str(ts) + email
|
||||
pass_hash = mkhash(password+config['salt'])
|
||||
token = mkhash(token_string)
|
||||
|
||||
with transation(users) as tr:
|
||||
user_id = tr.insert({
|
||||
'handle': handle.lower(),
|
||||
'name': display,
|
||||
'bio': bio,
|
||||
'info_table': json.dumps(table),
|
||||
'email': email,
|
||||
'password': pass_hash,
|
||||
'permissions': 4,
|
||||
'creation': ts,
|
||||
'forum_sig': sig,
|
||||
'pubkey': keys['pubkey'],
|
||||
'privkey': keys['privkey']
|
||||
})
|
||||
|
||||
print(user)
|
||||
|
||||
if user_id == 1:
|
||||
tr.update({'perms': 0}, id=1)
|
||||
|
||||
return {'id': user_id}
|
||||
|
||||
|
||||
def login_cookie(userid, password, address, agent):
|
||||
ts = timestamp()
|
||||
cookie = mkhash(password+config['salt']+str(ts))
|
||||
|
||||
with transaction(logins) as tr:
|
||||
login_id = tr.insert({
|
||||
'userid': userid,
|
||||
'cookie': cookie,
|
||||
'timestamp': ts,
|
||||
'address': address,
|
||||
'agent': agent
|
||||
})
|
||||
|
||||
return logins.get(doc_id=login_id)
|
||||
|
||||
|
||||
def api_token(username, password):
|
||||
pass_hash = mkhash(password)
|
||||
|
||||
|
||||
def local_post(userid, data):
|
||||
post_data = {
|
||||
'text': data.get('text'),
|
||||
'warning': data.get('warning'),
|
||||
'privacy': data.get('privacy', 'public'),
|
||||
'token': data.get('token'),
|
||||
'media_id': data.get('media_id'),
|
||||
'reply_id': data.get('reply_id')
|
||||
}
|
||||
|
||||
ts = timestamp(integer=False)
|
||||
post_hash = int(mkhash(str(ts) + post_data['text'], alg='md5'), 16)
|
||||
|
||||
if post_data['warning'] == '':
|
||||
post_data['warning'] = None
|
||||
|
||||
db.begin()
|
||||
|
||||
post = db.insert('statuses',
|
||||
hash=post_hash, userid=userid, timestamp=ts,
|
||||
content=post_data['text'], warning=post_data['warning'], visibility=post_data['privacy']
|
||||
)
|
||||
|
||||
db.end()
|
||||
|
||||
return post
|
||||
|
||||
|
||||
def local_post2(userid, posts):
|
||||
db.begin()
|
||||
|
||||
for post in posts:
|
||||
ts = timestamp(integer=False)
|
||||
post_hash = int(mkhash(str(ts) + post['text'], alg='sha256'), 16)
|
||||
|
||||
try:
|
||||
db.insert('statuses',
|
||||
hash=post_hash, userid=userid, timestamp=ts,
|
||||
content=post['text'], warning=post['warning'], visibility=post['privacy']
|
||||
)
|
||||
|
||||
except pg.IntegrityError as e:
|
||||
logging.error(f'Failed to insert post: {e}')
|
||||
|
||||
db.end()
|
||||
|
||||
return 'Done'
|
3
social/db_tiny/update.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from tinyrecord import transaction
|
||||
|
||||
from . import *
|
|
@ -5,6 +5,7 @@ import json
|
|||
import yaml
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from colour import Color
|
||||
from datetime import datetime
|
||||
|
@ -14,6 +15,20 @@ from Crypto.PublicKey import RSA
|
|||
from hashlib import md5
|
||||
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
script_path = os.path.dirname(abspath(sys.executable))
|
||||
|
||||
else:
|
||||
script_path = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
css_check = lambda css_file : int(os.path.getmtime(f'{script_path}/templates/{css_file}.css'))
|
||||
|
||||
cssts = {
|
||||
'color': css_check('color'),
|
||||
'layout': css_check('layout')
|
||||
}
|
||||
|
||||
|
||||
error_codes = {
|
||||
400: 'BadRequest',
|
||||
404: 'NotFound',
|
||||
|
@ -67,7 +82,7 @@ def http_error(code, request, msg=None):
|
|||
cont_type = 'text/html'
|
||||
data = {'login_token': request.cookies.get('login_token')}
|
||||
|
||||
if str(code)+'.html' not in os.listdir('frontend/templates/errors'):
|
||||
if str(code)+'.html' not in os.listdir('social/templates/errors'):
|
||||
logging.error(f'Hey! You specified a wrong error code: {code} {msg}')
|
||||
|
||||
body = aiohttp_jinja2.render_template('errors/500.html', request, {'data': data, 'msg': 'A wrong error template was specified'}, status=500)
|
||||
|
@ -78,6 +93,19 @@ def http_error(code, request, msg=None):
|
|||
return body
|
||||
|
||||
|
||||
def css_ts():
|
||||
color = css_check('color')
|
||||
layout = css_check('layout')
|
||||
|
||||
if cssts['color'] != color or cssts['layout'] != layout:
|
||||
cssts.update({
|
||||
'color': color,
|
||||
'layout': layout
|
||||
})
|
||||
|
||||
return cssts['color'] + cssts['layout']
|
||||
|
||||
|
||||
def mkhash(string, alg='sha512'):
|
||||
if alg == 'sha512':
|
||||
return SHA512.new(string.encode('UTF-8')).hexdigest()
|
||||
|
@ -103,10 +131,14 @@ def todate(ts):
|
|||
return datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M')
|
||||
|
||||
|
||||
def ap_date(ts):
|
||||
return datetime.fromtimestamp(ts).strftime('%Y-%m-%dT%H:%M:%SZ')
|
||||
|
||||
|
||||
def themes():
|
||||
theme_list = []
|
||||
|
||||
for theme in os.listdir('frontend/themes'):
|
||||
for theme in os.listdir(f'{script_path}/themes'):
|
||||
theme_list.append(theme.replace('.yml', ''))
|
||||
|
||||
theme_list.sort()
|
||||
|
@ -123,7 +155,7 @@ def timestamp(integer=True):
|
|||
# Generate css file for color styling
|
||||
def color_css(theme):
|
||||
try:
|
||||
data = yaml.load(open('frontend/themes/'+theme+'.yml', 'r'), Loader=yaml.FullLoader)
|
||||
data = yaml.load(open(f'{os.path.dirname(__file__)}/themes/'+theme+'.yml', 'r'), Loader=yaml.FullLoader)
|
||||
|
||||
except FileNotFoundError:
|
||||
data = {}
|
|
@ -3,20 +3,16 @@ import aiohttp
|
|||
import binascii
|
||||
import base64
|
||||
import json
|
||||
import aioredis
|
||||
|
||||
#from aiohttp.http_exceptions import *
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Hash import SHA, SHA256, SHA512
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
from cachetools import LFUCache
|
||||
from async_lru import alru_cache
|
||||
from aiohttp_session import session_middleware
|
||||
from aiohttp_session.redis_storage import RedisStorage
|
||||
from simplecache import LRUCache
|
||||
from urllib.parse import urlparse, quote_plus
|
||||
|
||||
from config import config, logging
|
||||
from functions import json_error
|
||||
from .database import SETTINGS, newtrans, get
|
||||
from .config import config, logging, pyenv, header_string
|
||||
from .functions import json_error
|
||||
from .database import newtrans, get
|
||||
|
||||
|
||||
def fetch_actor(actor):
|
||||
|
@ -48,7 +44,7 @@ def build_signing_string(headers, used_headers):
|
|||
return '\n'.join(map(lambda x: ': '.join([x.lower(), headers[x]]), used_headers))
|
||||
|
||||
|
||||
SIGSTRING_CACHE = LFUCache(1024)
|
||||
SIGSTRING_CACHE = LRUCache(maxsize=1024)
|
||||
|
||||
def sign_signing_string(sigstring, key):
|
||||
if sigstring in SIGSTRING_CACHE:
|
||||
|
@ -85,9 +81,14 @@ def pass_hash():
|
|||
return password_hash.hexdigest()
|
||||
|
||||
|
||||
@alru_cache(maxsize=16384)
|
||||
keys = LRUCache(maxsize=1024)
|
||||
|
||||
async def fetch_actor_key(actor):
|
||||
actor_data = await fetch_actor(actor)
|
||||
if actor not in keys.items:
|
||||
pre_actor_data = await fetch_actor(actor)
|
||||
keys.store(actor, pre_actor_data)
|
||||
|
||||
actor_data = keys.fetch(actor)
|
||||
|
||||
if not actor_data:
|
||||
return None
|
||||
|
@ -138,7 +139,6 @@ async def http_signatures(app, handler):
|
|||
request['validated'] = False
|
||||
|
||||
if request.method == 'POST' and '/api' not in request.path:
|
||||
pass
|
||||
|
||||
if 'signature' in request.headers:
|
||||
data = await request.json()
|
||||
|
@ -180,14 +180,17 @@ async def http_auth(app, handler):
|
|||
async def http_auth_handler(request):
|
||||
api_exclude_paths = [
|
||||
'/api/native/register',
|
||||
'/api/native/token'
|
||||
'/api/native/token',
|
||||
'/api/v1/apps'
|
||||
]
|
||||
|
||||
cookie_include_paths = [
|
||||
'/@',
|
||||
'/:',
|
||||
'/user',
|
||||
'/welcome'
|
||||
'/status',
|
||||
'/welcome',
|
||||
'/oauth/authorize'
|
||||
]
|
||||
|
||||
if '/api' in request.path and request.path not in api_exclude_paths and request.method == 'POST':
|
||||
|
@ -210,10 +213,14 @@ async def http_auth(app, handler):
|
|||
|
||||
if any(map(request.path.startswith, cookie_include_paths)):
|
||||
if login_token == None:
|
||||
return aiohttp.web.HTTPFound('/login')
|
||||
if request.headers.get('Accept') == 'application/activity+json':
|
||||
return json_error(401, 'NoToken')
|
||||
|
||||
else:
|
||||
return aiohttp.web.HTTPFound(f'/login?redir={quote_plus(request.path)}&query={quote_plus(request.query_string)}')
|
||||
|
||||
elif login_token_val == None:
|
||||
return aiohttp.web.HTTPFound('/login?msg=InvalidToken')
|
||||
return aiohttp.web.HTTPFound(f'/login?redir={quote_plus(request.path)}&msg=InvalidToken')
|
||||
|
||||
elif any(map(request.path.startswith, ['/login', '/register'])) and login_token_val != None:
|
||||
if login_token == login_token_val['cookie']:
|
||||
|
@ -243,5 +250,27 @@ async def http_trailing_slash(app, handler):
|
|||
return (await handler(request))
|
||||
return http_trailing_slash_handler
|
||||
|
||||
async def http_file_cache(request, response):
|
||||
cache_ext = ['png', 'js', 'svg', 'ogg', 'flac', 'py']
|
||||
always_cache = ['ico', 'css']
|
||||
|
||||
__all__ = ['http_signatures_middleware', 'http_auth_middleware', 'http_error_override', 'http_filter_middleware']
|
||||
response.headers['server'] = header_string
|
||||
raw_ext = request.path.split('.')[-1:]
|
||||
ext = raw_ext[0] if len(raw_ext) > 0 else None
|
||||
|
||||
if ext in cache_ext and response.headers.get('Cache-Control') == None:
|
||||
if pyenv == 'prod':
|
||||
logging.debug('Returning "cacheable"')
|
||||
response.headers['Cache-Control'] = 'public,max-age=2628000,immutable'
|
||||
|
||||
elif pyenv in ['dev', 'default']:
|
||||
logging.debug('Returning "non-cacheable"')
|
||||
response.headers['Cache-Control'] = 'no-store'
|
||||
|
||||
elif ext in always_cache:
|
||||
logging.debug('Returning "cacheable"')
|
||||
response.headers['Cache-Control'] = 'public,max-age=2628000,immutable'
|
||||
|
||||
else:
|
||||
logging.debug('Returning "non-cacheable"')
|
||||
response.headers['Cache-Control'] = 'no-store'
|
72
social/oauth.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
import secrets
|
||||
import validators
|
||||
|
||||
from .database import *
|
||||
from .config import logging
|
||||
|
||||
|
||||
def scope_check(scopes):
|
||||
read_write = ['follows', 'accounts', 'lists', 'blocks', 'mutes', 'bookmarks', 'notifications', 'favourites', 'search', 'filters', 'statuses']
|
||||
admin = ['read', 'write']
|
||||
admin_secc = ['accounts', 'reports']
|
||||
new_scopes = []
|
||||
|
||||
for line in scopes:
|
||||
scope = line.split(':')
|
||||
if len(scope) < 2:
|
||||
scope[1] == None
|
||||
|
||||
if len(scope) < 3:
|
||||
scope[2] == None
|
||||
|
||||
if (scope[0] in ['read', 'write'] and scope[1] in read_write) or scope[0] in ['follow', 'push'] or (scope[0] == 'admin' and scope[1] in admin and scope[2]):
|
||||
new_scopes.append(line)
|
||||
|
||||
else:
|
||||
logging.warning(f'Invalid scope: {line}')
|
||||
|
||||
if len(new_scopes) < 1:
|
||||
return
|
||||
|
||||
else:
|
||||
return new_scopes
|
||||
|
||||
|
||||
class create:
|
||||
def app(redirect_uri, scope, name, url):
|
||||
if None in [scope, name]:
|
||||
logging.debug('Missing scope or name for app')
|
||||
logging.debug(f'scope: {scope}, name: {name}')
|
||||
return 'MissingData'
|
||||
|
||||
scopes = scope_check(scope)
|
||||
|
||||
if scopes == None:
|
||||
logging.debug(f'Invalid scopes: {scope}')
|
||||
return 'InvalidScope'
|
||||
|
||||
if not validators.url(redirect_uri):
|
||||
logging.debug(f'Invalid redirect URL: {redirect_uri}')
|
||||
redirect_uri = 'urn:ietf:wg:oauth:2.0:oob'
|
||||
|
||||
if not validators.url(url):
|
||||
logging.debug(f'Invalid app URL: {url}')
|
||||
return 'InvalidURL'
|
||||
|
||||
client_id = secrets.token_hex(20)
|
||||
client_secret = secrets.token_hex(20)
|
||||
|
||||
put.oauth.app(client_id, client_secret, redirect_uri, scopes, name, url)
|
||||
|
||||
return {'client_id': client_id, 'client_secret': client_secret, 'redirect_uris': redirect_uri, 'scopes': scopes}
|
||||
|
||||
|
||||
def authorize(client_id, client_secret, redirect_uri, *args):
|
||||
if None in [client_id, client_secret]:
|
||||
logging.debug(f'Invalid secrets: {client_id}, {client_secret}')
|
||||
return 'InvalidCredentials'
|
||||
|
||||
return
|
||||
|
||||
def auth_code(client_id, login_token):
|
||||
pass
|
36
social/redirects.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
import aiohttp, aiohttp_jinja2
|
||||
|
||||
from .web_functions import json_check, json_user
|
||||
|
||||
|
||||
# Redirect /user/[user] to /@[user]
|
||||
async def user_get(request):
|
||||
user = request.match_info['user']
|
||||
|
||||
if json_check(request.headers):
|
||||
return json_user(request, user)
|
||||
|
||||
return aiohttp.web.HTTPFound(f'/@{user}')
|
||||
|
||||
|
||||
# Redirect /status/[status] to /:[status]
|
||||
async def post_get(request):
|
||||
status = request.match_info['status']
|
||||
|
||||
if json_check(request.headers):
|
||||
return json_user(request, status)
|
||||
|
||||
return aiohttp.web.HTTPFound(f'/:{status}')
|
||||
|
||||
|
||||
# PATPAT
|
||||
async def headpats(request):
|
||||
return aiohttp.web.HTTPFound('https://static.barkshark.xyz/mastodon/main/custom_emojis/images/000/000/797/original/blobpatpat.png')
|
||||
|
||||
|
||||
#socks
|
||||
async def socks(request):
|
||||
return aiohttp.web.HTTPFound('https://barkshark.xyz/@izalia/103155447990974282')
|
||||
|
||||
async def socks_old(request):
|
||||
return aiohttp.web.HTTPFound('https://glaceon.social/@monorail/102496098954435127')
|
|
@ -1,11 +1,16 @@
|
|||
import aiohttp, aiohttp_jinja2, json
|
||||
import aiohttp
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
|
||||
from config import config, logging
|
||||
from functions import color_css, http_error
|
||||
from backend.database import SETTINGS
|
||||
from os.path import getmtime
|
||||
from datetime import datetime
|
||||
|
||||
from .config import config, logging, script_path
|
||||
from .functions import color_css, http_error, css_ts
|
||||
from .database import get
|
||||
|
||||
|
||||
# layout.css
|
||||
# layout.css (currently unused)
|
||||
async def layout_get(request):
|
||||
response = aiohttp_jinja2.render_template('layout.css', request, {})
|
||||
response.headers['Content-type'] = 'text/css'
|
||||
|
@ -18,17 +23,18 @@ async def color_get(request):
|
|||
theme = request.cookies.get('theme')
|
||||
|
||||
if theme == None:
|
||||
theme = SETTINGS['theme']
|
||||
theme = get.settings('theme')
|
||||
|
||||
response = aiohttp_jinja2.render_template('color.css', request, color_css(theme))
|
||||
response.headers['Content-type'] = 'text/css'
|
||||
response.headers['Last-Modified'] = datetime.utcfromtimestamp(css_ts()).strftime('%a, %d %b %Y %H:%M:%S GMT')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
async def manifest_get(request):
|
||||
data = {
|
||||
'name': SETTINGS['name'],
|
||||
'name': get.settings('name'),
|
||||
'short_name': 'BarkShark',
|
||||
'description': '',
|
||||
'icons': [
|
||||
|
@ -47,26 +53,31 @@ async def manifest_get(request):
|
|||
"background_color": "#11111",
|
||||
"display":"standalone",
|
||||
"start_url":"/",
|
||||
'scope': 'https://'+config['web_domain'],
|
||||
"scope": f"https://{config.get('web_domain')}",
|
||||
}
|
||||
|
||||
return web.json_response(data, content_type='application/manifest+json', charset='utf-8')
|
||||
response = aiohttp.web.json_response(data, content_type='application/manifest+json')
|
||||
|
||||
return response
|
||||
|
||||
async def favicon_get(request):
|
||||
return aiohttp.web.Response(
|
||||
response = aiohttp.web.Response(
|
||||
status=200,
|
||||
content_type='image/png',
|
||||
charset='utf-8',
|
||||
body=open('frontend/static/icon-64.png', 'rb').read()
|
||||
body=open(f'{script_path}/static/icon-64.png', 'rb').read()
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# say 'fuck you' to crawlers that respect robots.txt
|
||||
async def robots_txt(request):
|
||||
return aiohttp.web.Response(
|
||||
response = aiohttp.web.Response(
|
||||
status=200,
|
||||
content_type='text/plain',
|
||||
charset='utf-8',
|
||||
body='User-agent: *\nDisallow: /'
|
||||
)
|
||||
|
||||
return response
|
74
social/routes.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from webapp import webapp, modules
|
||||
|
||||
from . import views, resources, user, redirects, wellknown
|
||||
from .api import native, mastodon, oauth
|
||||
from .web_server import web
|
||||
from .config import script_path, stor_path
|
||||
|
||||
|
||||
# Public pages
|
||||
web.router.add_get('/', views.home_get)
|
||||
web.router.add_get('/register', views.register_get)
|
||||
web.router.add_get('/login', views.login_get)
|
||||
web.router.add_get('/logout', views.logout_get)
|
||||
web.router.add_post('/register', views.register_post)
|
||||
web.router.add_post('/login', views.login_post)
|
||||
|
||||
# Semi-public json
|
||||
web.router.add_get('/user/{user}.json', views.user_json_get)
|
||||
web.router.add_get('/status/{status}.json', views.post_json_get)
|
||||
web.router.add_get('/@{user}.json', views.user_json_get)
|
||||
web.router.add_get('/:{status}.json', views.post_json_get)
|
||||
|
||||
# Semi-public pages
|
||||
web.router.add_get('/@{user}', views.user_get)
|
||||
web.router.add_get('/:{status}', views.post_get)
|
||||
web.router.add_get('/user/{user}', redirects.user_get)
|
||||
web.router.add_get('/status/{status}', redirects.post_get)
|
||||
web.router.add_post('/:{status}', user.post_delete_post)
|
||||
|
||||
# Frontend
|
||||
web.router.add_get('/welcome', user.welcome_get)
|
||||
web.router.add_get('/settings', user.settings_get)
|
||||
web.router.add_get('/admin', user.admin_get)
|
||||
web.router.add_post('/settings', user.settings_post)
|
||||
web.router.add_post('/admin', user.admin_post)
|
||||
web.router.add_post('/poast', user.poast_post)
|
||||
|
||||
# CSS, JS, etc
|
||||
#web.router.add_get('/layout.css', resources.layout_get)
|
||||
web.router.add_get('/style-{timestamp}.css', resources.color_get)
|
||||
web.router.add_get('/manifest.json', resources.manifest_get)
|
||||
web.router.add_get('/favicon.ico', resources.favicon_get)
|
||||
web.router.add_get('/robots.txt', resources.robots_txt)
|
||||
|
||||
# Media
|
||||
web.router.add_static('/static', path=f'{script_path}/static', name='static')
|
||||
web.router.add_static('/media', path=f'{stor_path}/media', name='media')
|
||||
|
||||
# Native API
|
||||
web.router.add_get('/api/native/{name}', native.handle_get)
|
||||
web.router.add_post('/api/native/{name}', native.handle_post)
|
||||
|
||||
# Mastodon API
|
||||
web.router.add_post('/api/v1/{name}', mastodon.handle_post)
|
||||
web.router.add_get('/api/v1/{name}', mastodon.handle_get)
|
||||
#web.router.add_get('/api/v1/streaming/{name}', mastodon.streaming_get)
|
||||
|
||||
# OAuth
|
||||
web.router.add_get('/oauth/{name}', oauth.handle_get)
|
||||
web.router.add_post('/oauth/{name}', oauth.handle_post)
|
||||
|
||||
# Various info endpoints
|
||||
web.router.add_get('/nodeinfo/2.0.json', wellknown.nodeinfo_json)
|
||||
#web.router.add_get('/.well-known/nodeinfo', wellknown.nodeinfo_get)
|
||||
#web.router.add_get('/.well-known/host-meta', wellknown.hostmeta_get)
|
||||
web.router.add_get('/.well-known/webfinger', wellknown.webfinger_get)
|
||||
|
||||
# Shitpost
|
||||
web.router.add_get('/headpats', redirects.headpats)
|
||||
web.router.add_get('/socks', redirects.socks)
|
||||
|
||||
# WebUI
|
||||
#web.add_subapp('/web', webapp)
|
||||
#web.router.add_get('/{module}.py', modules)
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 236 B |
|
@ -2,7 +2,7 @@ from jinja2 import BaseLoader, TemplateNotFound
|
|||
from os.path import join, exists, getmtime, isfile
|
||||
from os import environ as env
|
||||
|
||||
from config import stor_path, script_path, logging
|
||||
from .config import stor_path, script_path, logging
|
||||
from simplecache import LRUCache
|
||||
|
||||
|
||||
|
@ -22,11 +22,11 @@ class CustomLoader(BaseLoader):
|
|||
|
||||
else:
|
||||
logging.debug('Can\'t find custom template file: '+custom_path+template)
|
||||
path = join(self.path, template)
|
||||
path = join(f'{script_path}/{self.path}', template)
|
||||
|
||||
if isfile(path) == False:
|
||||
logging.error('Can\'t find template file: '+path)
|
||||
path = join(self.path, 'missing.html')
|
||||
path = join(f'{script_path}/{self.path}', 'missing.html')
|
||||
|
||||
mtime = getmtime(path)
|
||||
|
|
@ -1,15 +1,25 @@
|
|||
{% if cookies.login_token != None %}
|
||||
{% set cookie = newtrans(get_cookie(cookies.login_token)) %}
|
||||
{% set user = newtrans(get_user(cookie.userid, filters='pubkey,privkey,password')) %}
|
||||
{% set cookie = newtrans(get_cookie(cookies.login_token)) %}
|
||||
{% if cookie != None %}
|
||||
{% set user = newtrans(get_user(cookie.userid, filters='pubkey,privkey,password')) %}
|
||||
{% else %}
|
||||
{% set user = None %}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% set user = None %}
|
||||
{% endif %}
|
||||
|
||||
{% if cookies.get('theme') == None %}
|
||||
{% set theme = blue %}
|
||||
{% else %}
|
||||
{% set user = None %}
|
||||
{% set theme = cookies.theme %}
|
||||
{% endif %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>{{name}}: {{page}}</title>
|
||||
<link rel="stylesheet" type="text/css" href="https://{{domain}}/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="https://{{domain}}/style-{{theme}}-{{css_ts()}}.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<script type="text/javascript">
|
||||
|
@ -34,18 +44,18 @@
|
|||
</head>
|
||||
<body>
|
||||
{% include "components/menu.html" %}
|
||||
<div id="header">
|
||||
<h1 class="title"><a href="https://{{domain}}">{{name}}</a></h1>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div id="header">
|
||||
<h1 class="title"><a href="https://{{domain}}">{{name}}</a></h1>
|
||||
</div>
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
<div id="footer">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="col1"></td>
|
||||
<td class="col1">UvU</td>
|
||||
<td class="col2">
|
||||
<a href="https://git.barkshark.tk/izaliamae/social">Barkshark Social</a>
|
||||
<a href="https://git.barkshark.xyz/izaliamae/social">Barkshark Social</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
|
@ -38,7 +38,6 @@ input, textarea {
|
|||
background-color:var(--primary-ui-element-background);
|
||||
color: var(--primary-ui-lighter);
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
input:hover, textarea:hover {
|
||||
|
@ -69,7 +68,10 @@ input:invalid {
|
|||
#content {
|
||||
background: {{background}};
|
||||
border-color: transparent;
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
#header {
|
||||
border-bottom: 1px solid {{desaturate(primary, 0.06)}};
|
||||
}
|
||||
|
||||
#footer {
|
||||
|
@ -80,7 +82,6 @@ input:invalid {
|
|||
/* Dropdown menus */
|
||||
#user_panel {
|
||||
background-color: {{desaturate(darken(primary, 0.90), 0.85)}};
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
.submenu details[open] {
|
||||
|
@ -101,11 +102,7 @@ input:invalid {
|
|||
background-color: {{desaturate(darken(primary, 0.85), 0.80)}};
|
||||
}
|
||||
|
||||
.post, #bio, #user_info {
|
||||
box-shadow: 0 4px 8px 0 var(--shadow-color), 0 6px 20px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
.post, #bio, #user_info {
|
||||
.section {
|
||||
background-color: {{desaturate(darken(primary, 0.90), 0.85)}};
|
||||
}
|
||||
|
||||
|
@ -128,8 +125,3 @@ input:invalid {
|
|||
#nav {
|
||||
background-color: {{desaturate(darken(primary, 0.90), 0.85)}};
|
||||
}
|
||||
|
||||
#settings_page .section {
|
||||
box-shadow: 0 4px 8px 0 var(--shadow-color), 0 6px 20px 0 var(--shadow-color);
|
||||
background-color: {{desaturate(darken(primary, 0.90), 0.85)}};
|
||||
}
|
Before Width: | Height: | Size: 269 B After Width: | Height: | Size: 269 B |
|
@ -3,8 +3,8 @@
|
|||
<details>
|
||||
<summary id="menu_title"><a class="text">{{user.name}}</a></summary>
|
||||
<div class="item"><a href="https://{{domain}}/">Home</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/welcome">User Panel</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/@{{user.handle}}">Profile</a></div>
|
||||
<div class="item"><a href="https://{{domain}}/welcome">User Panel</a></div>
|
||||
<div class="submenu">
|
||||
<details>
|
||||
<summary class="item"><a class="text">Settings</a></summary>
|
|
@ -71,12 +71,18 @@ input, textarea {
|
|||
margin: 15px 0;
|
||||
font-size: 14pt;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
summary:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr:first-child .col1 {
|
||||
border-radius: 5px 0 0 0;
|
||||
}
|
||||
|
@ -94,7 +100,7 @@ tr:last-child .col2 {
|
|||
}
|
||||
|
||||
|
||||
/* i dont know rn tbh */
|
||||
/* Main page sections */
|
||||
#header {
|
||||
margin: 0 auto;
|
||||
width: var(--page-width);
|
||||
|
@ -106,39 +112,57 @@ tr:last-child .col2 {
|
|||
}
|
||||
|
||||
#content {
|
||||
padding: 10px 10px 0 10px;
|
||||
padding: 0 10px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 10px;
|
||||
width: var(--page-width);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
#title {
|
||||
|
||||
/* Custom page elements */
|
||||
.section {
|
||||
margin: 20px 10px;
|
||||
box-shadow: 0 4px 8px 0 var(--shadow-color), 0 6px 20px 0 var(--shadow-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.section:not(#posts), .post {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.section .title {
|
||||
font-size: 36pt;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
/* Profile pages */
|
||||
#user_info table {
|
||||
width: 100%;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.post {
|
||||
border-bottom: 1px solid transparent;
|
||||
border-bottom: 1px solid {{desaturate(darken(primary, 0.8), 0.8)}};
|
||||
margin-bottom: 10px;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 50% auto;
|
||||
grid-gap: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
|
||||
/* Post elements */
|
||||
.post_text summary {
|
||||
display: block;
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.post hr {
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.post .grid-container {
|
||||
grid-template-columns: auto 50px;;
|
||||
}
|
||||
|
@ -161,57 +185,12 @@ tr:last-child .col2 {
|
|||
float: right;
|
||||
}
|
||||
|
||||
#user_content, #user_info, #footer {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.post, #bio, #user_info, input, textarea {
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.post, #bio, #user_info, #user_info td, #footer {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
#logreg_form .title {
|
||||
font-size: 28pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#logreg_form input, #logreg_form textarea {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#logreg_form textarea, #profile textarea {
|
||||
height: 4em;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#logreg_form .error {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Grids */
|
||||
.grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 50% auto;
|
||||
grid-gap: 0;
|
||||
width: 100;
|
||||
}
|
||||
|
||||
.grid-item {
|
||||
display: inline-grid;
|
||||
}
|
||||
|
||||
|
||||
/* footer */
|
||||
/* Footer */
|
||||
#footer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#footer table {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#footer .col2 {
|
||||
|
@ -226,12 +205,10 @@ tr:last-child .col2 {
|
|||
right: 0;
|
||||
padding: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#user_panel {
|
||||
z-index: 10;
|
||||
top: 0;
|
||||
border-radius: 0 0 0 5px;
|
||||
box-shadow: 0 4px 4px 0 var(--shadow-color), 0 6px 10px 0 var(--shadow-color);
|
||||
}
|
||||
|
||||
.menu #menu_title {
|
||||
|
@ -240,10 +217,6 @@ tr:last-child .col2 {
|
|||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#menu_title .text {
|
||||
|
||||
}
|
||||
|
||||
#user_panel .item {
|
||||
padding: 5px 0;
|
||||
text-transform: uppercase;
|
||||
|
@ -284,17 +257,28 @@ summary:hover {
|
|||
}
|
||||
|
||||
|
||||
/* Login/Register forms */
|
||||
#logreg_form .title {
|
||||
font-size: 36pt;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#logreg_form input, #logreg_form textarea {
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#logreg_form textarea, #profile textarea {
|
||||
height: 4em;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
#logreg_form .error {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* Settings Page */
|
||||
#settings_page {
|
||||
display: block;
|
||||
}
|
||||
|
||||
#settings_page h2 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#settings_page textarea {
|
||||
/* width: 80%; */
|
||||
width: 90%;
|
||||
height: 8em;
|
||||
}
|
||||
|
@ -307,17 +291,9 @@ summary:hover {
|
|||
width: 44%
|
||||
}
|
||||
|
||||
#settings_page .section {
|
||||
text-transform: uppercase;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 25px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
|
||||
/* General */
|
||||
@media (max-width : 1014px) {
|
||||
/* responsive design */
|
||||
@media (max-width : 1000px) {
|
||||
#content, #header {
|
||||
margin: inherit 0;
|
||||
width: auto;
|
|
@ -1,11 +1,10 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% set page = 'Login' %}
|
||||
|
||||
{% block content %}
|
||||
<br>
|
||||
<center><form action="/login" method="post" id="logreg_form" onsubmit="javascript:delete_cookie('login_token')">
|
||||
<div id="title">{{page}}</div>
|
||||
<div class="title">{{page}}</div>
|
||||
{% if msg != None %}
|
||||
<div class="error message">{{msg}}</div>
|
||||
{% else %}
|
||||
|
@ -13,6 +12,8 @@
|
|||
{% endif %}
|
||||
<input type="text" name="username" placeholder="Username"><br>
|
||||
<input type="password" name="password" placeholder="Password"></br>
|
||||
<input type="hidden" name="redir" value="{{redir.path}}">
|
||||
<input type="hidden" name="redir_data" value="{{redir.data}}">
|
||||
<td><input type="submit" value="Login">
|
||||
</form></center>
|
||||
{% endblock %}
|
|
@ -3,5 +3,7 @@
|
|||
|
||||
|
||||
{% block content %}
|
||||
{% include "components/post.html" %}
|
||||
<div class="section single-post" id="posts">
|
||||
{% include "components/post.html" %}
|
||||
</div>
|
||||
{% endblock %}
|
38
social/templates/pages/public/profile.html
Normal file
|
@ -0,0 +1,38 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% set page = profile.name %}
|
||||
|
||||
{% block content %}
|
||||
<div id="bio" class="section">
|
||||
<a href="https://{{domain}}/@{{profile.handle}}">{{profile.name}}</a><br>
|
||||
{{profile.handle}}@{{domain}}<br>
|
||||
{{profile.count}} toots<br>
|
||||
<br>
|
||||
{% if profile.bio != None %}
|
||||
{% for line in profile.bio %}
|
||||
{{line}}<br>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
This bitch empty YEET
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if profile.info_table != None %}
|
||||
<div id="user_info" class="section">
|
||||
<table>
|
||||
{% for line in profile.info_table %}
|
||||
<tr><td class="col1">{{line}}</td><td class="col2">{{profile.info_table[line]}}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div id="posts" class="section">
|
||||
{% if post != [] %}
|
||||
{% for post in post %}
|
||||
{% include "components/post.html" %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if last_id != None %}
|
||||
<div id="new_posts"><center>{{profile.last_id}}<a href="https://{{domain}}/@{{profile.handle}}?id={{last_id}}">[More Posts]</a></center></div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -5,7 +5,7 @@
|
|||
{% block content %}
|
||||
<br>
|
||||
<center><form action="/register" method="post" id="logreg_form" autocomplete="new-password">
|
||||
<div id="title">{{page}}</div>
|
||||
<div class="title">{{page}}</div>
|
||||
{% if msg != None %}
|
||||
<div class="error message">{{msg}}</div>
|
||||
{% else %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% set page = 'Weclome' %}
|
||||
{% set page = 'Admin' %}
|
||||
|
||||
{% block content %}
|
||||
<br><br><br>
|
|
@ -1,11 +1,11 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% set page = 'Weclome' %}
|
||||
{% set page = 'Settings' %}
|
||||
|
||||
{% block content %}
|
||||
<div id="settings_page">
|
||||
<div id="profile" class="section">
|
||||
<h2>Profile</h2>
|
||||
<div class="title">Profile</div>
|
||||
<form action="/settings" method="post">
|
||||
<div class="grid-container">
|
||||
<div class="grid-item"><center>
|
||||
|
@ -24,7 +24,7 @@
|
|||
</div>
|
||||
|
||||
<div id="security" class="section">
|
||||
<h2>Account</h2>
|
||||
<div class="title">Account</div>
|
||||
<div class="grid-container">
|
||||
<div class="grid-item"><center>
|
||||
<form action="/settings" method="post">
|
||||
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
|
||||
<div id="options" class="section">
|
||||
<h2>Options</h2>
|
||||
<div class="title">Options</div>
|
||||
<form action="/settings" method="post">
|
||||
<div class="grid-container">
|
||||
<div class="grid-item">
|
|
@ -1,8 +1,8 @@
|
|||
import aiohttp, aiohttp_jinja2, json
|
||||
|
||||
from config import config, logging
|
||||
from functions import http_error, json_error
|
||||
from backend.database import SETTINGS, newtrans, get, update, put, delete
|
||||
from .config import config, logging
|
||||
from .functions import http_error, json_error
|
||||
from .database import newtrans, get, update, put, delete
|
||||
|
||||
# user home page (/welcome)
|
||||
async def welcome_get(request):
|
||||
|
@ -11,14 +11,15 @@ async def welcome_get(request):
|
|||
|
||||
async def settings_get(request):
|
||||
login_token = request.cookies.get('login_token')
|
||||
settings = get.settings('all')
|
||||
|
||||
token = newtrans(get.login_cookie(login_token))
|
||||
handle = newtrans(get.userid_to_handle(token['userid']))
|
||||
handle = newtrans(get.user(token['userid']))['id']
|
||||
|
||||
if token == None:
|
||||
aiohttp.web.HTTPFound('/login?msg=InvalidToken')
|
||||
|
||||
return aiohttp_jinja2.render_template('pages/user/settings.html', request, {'settings': SETTINGS})
|
||||
return aiohttp_jinja2.render_template('pages/user/settings.html', request, {'settings': settings})
|
||||
|
||||
|
||||
async def settings_post(request):
|
|
@ -1,8 +1,13 @@
|
|||
import aiohttp, aiohttp_jinja2, json
|
||||
import aiohttp
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
|
||||
from config import config, logging
|
||||
from functions import color_css, http_error, json_error, mkhash, timestamp
|
||||
from backend.database import SETTINGS, newtrans, get, put, delete
|
||||
from urllib.parse import unquote_plus
|
||||
|
||||
from .config import config, logging
|
||||
from .functions import color_css, http_error, json_error, mkhash, timestamp
|
||||
from .web_functions import json_check, json_user, json_status
|
||||
from .database import newtrans, get, put, delete
|
||||
|
||||
|
||||
# home page (/)
|
||||
|
@ -35,9 +40,12 @@ async def register_get(request):
|
|||
async def register_post(request):
|
||||
headers = request.headers
|
||||
data = await request.post()
|
||||
|
||||
|
||||
pass1 = data.get('newpassword1')
|
||||
pass2 = data.get('newpassword2')
|
||||
|
||||
username = data.get('username')
|
||||
password = data.get('password')
|
||||
password = pass1 if None not in [pass1, pass2] and pass1 == pass2 else None
|
||||
email = data.get('email')
|
||||
name = data.get('name')
|
||||
bio = data.get('bio')
|
||||
|
@ -49,14 +57,14 @@ async def register_post(request):
|
|||
if None in [username, password]:
|
||||
aiohttp.web.HTTPFound('/register?msg=MissingData')
|
||||
|
||||
user_check = newtrans(get.user(username))
|
||||
user_check = get.user(get.handle_to_userid(username))
|
||||
|
||||
if user_check != None:
|
||||
aiohttp.web.HTTPFound('/register?msg=UserExists')
|
||||
|
||||
user = newtrans(put.user(username, email, password, name, bio, info_table, sig))
|
||||
user = put.user(username, email, password, name, bio, info_table, sig)
|
||||
|
||||
token = newtrans(put.login_cookie(user['id'], user['password'], address, agent))
|
||||
token = put.login_cookie(user['id'], user['password'], address, agent)
|
||||
|
||||
response = aiohttp.web.HTTPFound('/login')
|
||||
response.set_cookie('login_token', token, max_age=60*60*24*14)
|
||||
|
@ -83,10 +91,11 @@ async def login_get(request):
|
|||
else:
|
||||
msg = None
|
||||
|
||||
return aiohttp_jinja2.render_template('pages/login.html', request, {'msg': msg})
|
||||
return aiohttp_jinja2.render_template('pages/login.html', request, {'msg': msg, 'redir': {'path': query.get('redir'), 'data': query.get('query')}})
|
||||
|
||||
|
||||
async def login_post(request):
|
||||
query = request.query
|
||||
headers = request.headers
|
||||
data = await request.post()
|
||||
|
||||
|
@ -94,24 +103,32 @@ async def login_post(request):
|
|||
password = data.get('password')
|
||||
address = headers.get('X-Real-Ip')
|
||||
agent = headers.get('User-Agent')
|
||||
redir = data.get('redir')
|
||||
redir_data = data.get('redir_data')
|
||||
|
||||
if '' in [username, password, address] or address == None:
|
||||
return http_error(400, request)
|
||||
|
||||
userid = newtrans(get.user(username.lower()))
|
||||
user = newtrans(get.user(username.lower()))
|
||||
|
||||
pass_hash = mkhash(password+config['salt'])
|
||||
|
||||
if userid != None:
|
||||
if userid['password'] == pass_hash:
|
||||
login_token = newtrans(put.login_cookie(userid['id'], password, address, agent))
|
||||
response = aiohttp.web.HTTPFound('/welcome')
|
||||
if user != None:
|
||||
if user['password'] == pass_hash:
|
||||
login_token = newtrans(put.login_cookie(user['id'], password, address, agent))
|
||||
|
||||
if redir != 'None':
|
||||
response = aiohttp.web.HTTPFound(f'{redir}?{unquote_plus(redir_data)}')
|
||||
|
||||
else:
|
||||
response = aiohttp.web.HTTPFound('/welcome')
|
||||
|
||||
# Send login token. Lasts for 2 weeks (60 sec * 60 min * 24 hour * 14 day)
|
||||
response.set_cookie('login_token', login_token, max_age=60*60*24*14)
|
||||
|
||||
return response
|
||||
|
||||
return aiohttp_jinja2.render_template('pages/login.html', request, {'msg': 'Wrong username or password'})
|
||||
return aiohttp_jinja2.render_template('pages/login.html', request, {'msg': 'Wrong username or password', 'redir': redir})
|
||||
|
||||
|
||||
async def logout_get(request):
|
||||
|
@ -121,24 +138,30 @@ async def logout_get(request):
|
|||
response = aiohttp.web.HTTPFound('/login?msg=LoggedOut')
|
||||
|
||||
if token != None:
|
||||
newtrans(delete.login_cookie(get.login_cookie(token)['id'], token))
|
||||
cookie = get.login_cookie(token)
|
||||
|
||||
response.set_cookie('login_token', token, max_age=0)
|
||||
if cookie != None:
|
||||
newtrans(delete.login_cookie(cookie['id'], token))
|
||||
|
||||
response.set_cookie('login_token', token, max_age=0)
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# user profiles
|
||||
async def user_get(request):
|
||||
user = request.match_info['user']
|
||||
req_user = request.match_info['user']
|
||||
postid = request.rel_url.query.get('id')
|
||||
user_data = newtrans(get.profile(user, postid=postid))
|
||||
user = newtrans(get.profile(req_user, postid=postid))
|
||||
|
||||
if user_data == None:
|
||||
if user == None:
|
||||
return http_error(404, request, msg='That user doesn\'t exist.')
|
||||
|
||||
if json_check(request.headers):
|
||||
return json_user(request, req_user)
|
||||
|
||||
try:
|
||||
posts = user_data['post']
|
||||
posts = user['post']
|
||||
if len(posts) < config['vars']['posts']:
|
||||
last_id = None
|
||||
|
||||
|
@ -148,15 +171,15 @@ async def user_get(request):
|
|||
except IndexError:
|
||||
last_id = None
|
||||
|
||||
user_data['last_id'] = last_id
|
||||
user['last_id'] = last_id
|
||||
bio = []
|
||||
|
||||
for line in user_data['profile']['bio'].split('\n'):
|
||||
for line in user['profile']['bio'].split('\n'):
|
||||
bio.append(line)
|
||||
|
||||
user_data['profile']['bio'] = bio
|
||||
user['profile']['bio'] = bio
|
||||
|
||||
return aiohttp_jinja2.render_template('pages/public/profile.html', request, user_data)
|
||||
return aiohttp_jinja2.render_template('pages/public/profile.html', request, user)
|
||||
|
||||
|
||||
# single post
|
||||
|
@ -173,3 +196,15 @@ async def post_get(request):
|
|||
post_data['post']['user'] = get.user(post_data['post']['userid'])
|
||||
|
||||
return aiohttp_jinja2.render_template('pages/public/post.html', request, post_data)
|
||||
|
||||
|
||||
async def user_json_get(request):
|
||||
req_user = request.match_info['user']
|
||||
|
||||
return json_user(request, req_user)
|
||||
|
||||
|
||||
async def post_json_get(request):
|
||||
postid = request.match_info['status']
|
||||
|
||||
return json_status(request, postid)
|
152
social/web_functions.py
Normal file
|
@ -0,0 +1,152 @@
|
|||
import aiohttp
|
||||
import json
|
||||
|
||||
from .config import config
|
||||
from .database import get
|
||||
from .functions import http_error, ap_date
|
||||
|
||||
|
||||
def json_check(headers):
|
||||
accept = headers.get('Accept')
|
||||
|
||||
if accept == 'application/activity+json':
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def json_user(request, req_user):
|
||||
settings = get.settings('all')
|
||||
user = get.user(req_user)
|
||||
|
||||
if user == None:
|
||||
return http_error(404, request, msg='That user doesn\'t exist.')
|
||||
|
||||
weburl = config['web_domain']
|
||||
domain = settings['domain']
|
||||
|
||||
handle = user['handle']
|
||||
display = user['name']
|
||||
bio = user['bio']
|
||||
pubkey = user['pubkey']
|
||||
|
||||
data = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
"https://w3id.org/security/v1",
|
||||
{
|
||||
"manuallyApprovesFollowers": "as:manuallyApprovesFollowers",
|
||||
"featured": {
|
||||
"@id": "toot:featured",
|
||||
"@type": "@id"
|
||||
},
|
||||
"alsoKnownAs": {
|
||||
"@id": "as:alsoKnownAs",
|
||||
"@type": "@id"
|
||||
},
|
||||
"movedTo": {
|
||||
"@id": "as:movedTo",
|
||||
"@type": "@id"
|
||||
},
|
||||
"schema": "http://schema.org#",
|
||||
"PropertyValue": "schema:PropertyValue",
|
||||
"value": "schema:value",
|
||||
"Emoji": "toot:Emoji",
|
||||
}
|
||||
],
|
||||
"id": f"https://{weburl}/users/{handle}",
|
||||
"type": f"Person",
|
||||
"following": f"https://{weburl}/users/{handle}/following",
|
||||
"followers": f"https://{weburl}/users/{handle}/followers",
|
||||
"inbox": f"https://{weburl}/users/{handle}/inbox",
|
||||
"outbox": f"https://{weburl}/users/{handle}/outbox",
|
||||
"featured": f"https://{weburl}/users/{handle}/collections/featured",
|
||||
"preferredUsername": f"{handle}",
|
||||
"name": f"{display}",
|
||||
"summary": f"{bio}",
|
||||
"url": f"https://{weburl}/@{handle}",
|
||||
"manuallyApprovesFollowers": False,
|
||||
"publicKey": {
|
||||
"id": f"https://{weburl}/users/{handle}#main-key",
|
||||
"owner": f"https://{weburl}/users/{handle}",
|
||||
"publicKeyPem": f"{pubkey}"
|
||||
},
|
||||
"attachment": [
|
||||
{
|
||||
"type": "PropertyValue",
|
||||
"name": "Pronouns",
|
||||
"value": "She/Her, They/Them"
|
||||
}
|
||||
],
|
||||
"endpoints": {
|
||||
"sharedInbox": f"https://{weburl}/inbox"
|
||||
},
|
||||
"icon": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"url": "" #avatar
|
||||
},
|
||||
"image": {
|
||||
"type": "Image",
|
||||
"mediaType": "image/png",
|
||||
"url": "" #header
|
||||
}
|
||||
}
|
||||
|
||||
return aiohttp.web.Response(body=json.dumps(data), content_type='application/activity+json')
|
||||
|
||||
|
||||
def json_status(request, status):
|
||||
post = get.post(status)
|
||||
|
||||
if post == None:
|
||||
return http_error(404, request, msg='That post doesn\'t exist.')
|
||||
|
||||
user = get.user(post['userid'])
|
||||
|
||||
if user == None:
|
||||
return http_error(404, request, msg='Some how you found a post without a valid user')
|
||||
|
||||
settings = get.settings('all')
|
||||
weburl = config['web_domain']
|
||||
domain = settings['domain']
|
||||
|
||||
warning = post['warning']
|
||||
content = post['content']
|
||||
handle = user['handle']
|
||||
date = ap_date(post['timestamp'])
|
||||
|
||||
def visibility():
|
||||
vis = {
|
||||
'public': f'https://{weburl}/users/{handle}/followers'
|
||||
}
|
||||
|
||||
data = {
|
||||
"@context": [
|
||||
"https://www.w3.org/ns/activitystreams",
|
||||
],
|
||||
"id": f"https://{weburl}/statuses/{status}",
|
||||
"type": "Note",
|
||||
"summary": warning if warning else None,
|
||||
"inReplyTo": None,
|
||||
"published": date,
|
||||
"url": f"https://{weburl}/status/{status}",
|
||||
"attributedTo": f"https://{weburl}/users/{handle}",
|
||||
"to": [
|
||||
f"https://{weburl}/users/{handle}/followers"
|
||||
],
|
||||
"cc": [
|
||||
"https://www.w3.org/ns/activitystreams#Public"
|
||||
],
|
||||
"sensitive": True if warning else False,
|
||||
"atomUri": f"https://{weburl}/users/{handle}/statuses/{status}",
|
||||
"content": content,
|
||||
"contentMap": {
|
||||
"en": content
|
||||
},
|
||||
"attachment": [],
|
||||
"tag": [],
|
||||
}
|
||||
|
||||
return aiohttp.web.Response(body=json.dumps(data), content_type='application/activity+json')
|
||||
|
104
social/web_server.py
Normal file
|
@ -0,0 +1,104 @@
|
|||
import asyncio
|
||||
import aiohttp
|
||||
import jinja2
|
||||
import aiohttp_jinja2
|
||||
import json
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from jinja2 import FileSystemLoader, select_autoescape
|
||||
|
||||
from .config import config, logging, stor_path, script_path
|
||||
from . import middleware, wellknown
|
||||
from .database import newtrans, get
|
||||
from .functions import color, todate, themes, css_ts
|
||||
from .template_loader import CustomLoader
|
||||
|
||||
|
||||
async def glob_vars(request):
|
||||
return {
|
||||
'name': get.settings('name'),
|
||||
'domain': config['web_domain'],
|
||||
'settings': get.settings('all'),
|
||||
'get_cookie': get.login_cookie,
|
||||
'get_user': get.user,
|
||||
'newtrans': newtrans,
|
||||
'css_ts': css_ts,
|
||||
'lighten': color().lighten,
|
||||
'darken': color().darken,
|
||||
'saturate': color().saturate,
|
||||
'desaturate': color().desaturate,
|
||||
'rgba': color().rgba,
|
||||
'todate': todate,
|
||||
'themes': themes,
|
||||
'json': json
|
||||
}
|
||||
|
||||
|
||||
web = aiohttp.web.Application(middlewares=[
|
||||
#middleware.http_signatures,
|
||||
middleware.http_auth,
|
||||
middleware.http_filter,
|
||||
middleware.http_trailing_slash
|
||||
])
|
||||
|
||||
aiohttp_jinja2.setup(web,
|
||||
loader=CustomLoader('templates'),
|
||||
autoescape=select_autoescape(['html', 'xml', 'css']),
|
||||
context_processors=[glob_vars],
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True
|
||||
)
|
||||
|
||||
web.on_response_prepare.append(middleware.http_file_cache)
|
||||
|
||||
|
||||
async def start_web():
|
||||
runner = aiohttp.web.AppRunner(web, access_log_format='%{X-Real-Ip}i %s %b "%r" "%{User-Agent}i"')
|
||||
await runner.setup()
|
||||
|
||||
listen = config['listen']
|
||||
port = config['port']
|
||||
|
||||
logging.info('Starting web server at {listen}:{port}'.format(listen=listen,port=port))
|
||||
|
||||
site = aiohttp.web.TCPSite(runner, listen, port)
|
||||
|
||||
await site.start()
|
||||
|
||||
|
||||
async def save_tokens():
|
||||
while True:
|
||||
# Wait every 30 mins
|
||||
await asyncio.sleep(60 * 30)
|
||||
|
||||
logging.info('Saving updated timestamps for login and auth tokens to the database')
|
||||
#update_timestamps()
|
||||
|
||||
|
||||
def safe_stop(*args):
|
||||
logging.debug('Saving updated timestamps')
|
||||
#update_timestamps()
|
||||
|
||||
logging.info('Bye')
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main():
|
||||
from . import routes
|
||||
|
||||
signal.signal(signal.SIGHUP, safe_stop)
|
||||
signal.signal(signal.SIGINT, safe_stop)
|
||||
signal.signal(signal.SIGQUIT, safe_stop)
|
||||
signal.signal(signal.SIGTERM, safe_stop)
|
||||
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
asyncio.ensure_future(start_web())
|
||||
asyncio.ensure_future(save_tokens())
|
||||
loop.run_forever()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
|
||||
safe_stop()
|
|
@ -1,14 +1,17 @@
|
|||
import aiohttp
|
||||
|
||||
from config import config
|
||||
from .database import SETTINGS, newtrans, get
|
||||
from .config import config
|
||||
from .database import newtrans, get
|
||||
|
||||
async def nodeinfo_json(request):
|
||||
settings = get.settings
|
||||
stats = get.server_stats()
|
||||
|
||||
data = {
|
||||
'version': '2.0',
|
||||
'usage': {
|
||||
'users': {'total': 69},
|
||||
'localPosts': 420
|
||||
'users': {'total': stats['user_count']},
|
||||
'localPosts': stats['status_count']
|
||||
},
|
||||
'software': {
|
||||
'name': 'bsocial',
|
||||
|
@ -19,8 +22,8 @@ async def nodeinfo_json(request):
|
|||
'metadata': {
|
||||
'staffAccounts': [],
|
||||
'postFormats': ['text/plain', 'text/html', 'text/markdown'],
|
||||
'nodeName': SETTINGS['name'],
|
||||
'nodeDescription': SETTINGS['description']
|
||||
'nodeName': settings('name'),
|
||||
'nodeDescription': settings('description')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,32 +58,35 @@ async def hostmeta_get(request):
|
|||
|
||||
async def webfinger_get(request):
|
||||
query = request.rel_url.query
|
||||
settings = get.settings
|
||||
|
||||
if query.get('resource') != None:
|
||||
resource = query['resource'].split('@')
|
||||
user = resource[0]
|
||||
web_domain = config['web_domain']
|
||||
domain = get.settings('domain')
|
||||
|
||||
if resource[1] == SETTINGS['domain'] and newtrans(get.user(user)) != None:
|
||||
if resource[1] == settings('domain') and newtrans(get.user(user)) != None:
|
||||
data = {
|
||||
'subject': 'acct:izaliamae@barkshark.tk',
|
||||
'subject': f'acct:{user}@{domain}',
|
||||
'aliases': [
|
||||
'https://{domain}/@{user}'.format(domain=config['web_domain'], user=user),
|
||||
'https://{domain}/user/{user}'.format(domain=config['web_domain'], user=user)
|
||||
f'https://{web_domain}/@{user}',
|
||||
f'https://{web_domain}/user/{user}'
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'rel': 'http://webfinger.net/rel/profile-page',
|
||||
'type': 'text/html',
|
||||
'href': 'https://{domain}/@{user}'.format(domain=config['web_domain'], user=user)
|
||||
'href': f'https://{web_domain}/@{user}'
|
||||
},
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': 'https://{domain}/user/{user}'.format(domain=config['web_domain'], user=user)
|
||||
'href': f'https://{web_domain}/user/{user}'
|
||||
},
|
||||
{
|
||||
'rel': 'http://ostatus.org/schema/1.0/subscribe',
|
||||
'template': 'https://{domain}/interact?url={{url}}'.format(domain=config['web_domain'])
|
||||
'template': f'https://{web_domain}/interact?url={{url}}'
|
||||
}
|
||||
]
|
||||
}
|
53
tests/oauth-apps.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
#!/usr/bin/env python3
|
||||
import os, sys, json, urllib.request, getpass
|
||||
import mastodon
|
||||
from configobj import ConfigObj as config
|
||||
|
||||
stor_path = '/tmp'
|
||||
confini = f'{stor_path}/config.ini'
|
||||
|
||||
if not os.path.exists(confini):
|
||||
cfg = config()
|
||||
cfg.filename = confini
|
||||
cfg.update({
|
||||
'app_tokens': {'key': None, 'secret': None},
|
||||
'login': {'domain': None, 'token': None}
|
||||
})
|
||||
|
||||
else:
|
||||
cfg = config(confini)
|
||||
|
||||
print(cfg)
|
||||
|
||||
try:
|
||||
domain = os.environ['domain']
|
||||
username = os.environ['username']
|
||||
password = os.environ['password']
|
||||
except KeyError:
|
||||
domain = input('Domain: ')
|
||||
username = input('E-Mail: ')
|
||||
password = getpass.getpass('Pass: ')
|
||||
|
||||
cfg['login']['domain'] = domain
|
||||
scopes = ['read:notifications', 'write:notifications']
|
||||
|
||||
if None in [cfg['app_tokens']['key'], cfg['app_tokens']['secret']]:
|
||||
client = mastodon.Mastodon.create_app('App Test', website = 'https://git.barkshark.tk/izaliamae', scopes = scopes, api_base_url = domain)
|
||||
cfg['app_tokens'].update({'key': client[0], 'secret': client[1]})
|
||||
print(cfg)
|
||||
cfg.write()
|
||||
|
||||
else:
|
||||
client = (cfg['app_tokens']['key'], cfg['app_tokens']['secret'])
|
||||
|
||||
login = mastodon.Mastodon(client_id = client[0], client_secret = client[1], api_base_url = cfg['login']['domain'])
|
||||
print(login.auth_request_url(scopes=scopes))
|
||||
user_token = login.log_in(code=input('Secret:'), scopes = scopes)
|
||||
#user_token = userLogin.log_in(username, password, scopes = scopes, to_file=None)
|
||||
print(user_token)
|
||||
sys.exit()
|
||||
|
||||
cfg['login']['domain'] = domain
|
||||
cfg['login']['token'] = user_token
|
||||
|
||||
#cfg.write(open(scriptPath + '/' + confINI, 'w'))
|
|
@ -1,6 +1,64 @@
|
|||
import aiohttp
|
||||
import aiohttp_jinja2
|
||||
import os
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
|
||||
from os.path import dirname, abspath
|
||||
from jinja2 import select_autoescape
|
||||
|
||||
from social.config import logging, config
|
||||
from social.functions import color, todate, themes, css_ts
|
||||
from social import middleware
|
||||
from social.database import newtrans, get
|
||||
from social.template_loader import CustomLoader
|
||||
from .views import main, modules
|
||||
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
script_path = dirname(abspath(sys.executable))
|
||||
|
||||
else:
|
||||
script_path = dirname(abspath(__file__))
|
||||
|
||||
|
||||
async def glob_vars(request):
|
||||
return {
|
||||
'name': settings['name'],
|
||||
'domain': config['web_domain'],
|
||||
'settings': get.settings(),
|
||||
'get_cookie': get.login_cookie,
|
||||
'get_user': get.user,
|
||||
'newtrans': newtrans,
|
||||
'css_ts': css_ts,
|
||||
'lighten': color().lighten,
|
||||
'darken': color().darken,
|
||||
'saturate': color().saturate,
|
||||
'desaturate': color().desaturate,
|
||||
'rgba': color().rgba,
|
||||
'todate': todate,
|
||||
'themes': themes,
|
||||
'json': json
|
||||
}
|
||||
|
||||
|
||||
webapp = aiohttp.web.Application(middlewares=[])
|
||||
webapp.on_response_prepare.append(middleware.http_file_cache)
|
||||
|
||||
aiohttp_jinja2.setup(webapp,
|
||||
loader=CustomLoader(f'{script_path}/templates'),
|
||||
autoescape=select_autoescape(['html', 'xml', 'css']),
|
||||
context_processors=[glob_vars],
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True
|
||||
)
|
||||
|
||||
|
||||
webapp.router.add_get('', main)
|
||||
webapp.router.add_static('/js', path=f'{script_path}/js')
|
||||
|
||||
from config import script_path
|
||||
|
||||
def precompile():
|
||||
os.system(f'transcrypt --map {script_path}/webapp/js/__init__.py')
|
||||
logging.info('Compiling JS')
|
||||
subprocess.Popen(['transcrypt', '--map', '--build', '--nomin', '--parent', 'window', f'{script_path}/webapp/js/__init__.py'], preexec_fn=os.setpgrp)
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
print('OwO')
|
13100
webapp/js/brython.js
Normal file
3
webapp/js/brython_modules.js
Normal file
20
webapp/js/main.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
import owo
|
||||
from browser import document, aio
|
||||
|
||||
text = ''
|
||||
|
||||
async def read():
|
||||
global text
|
||||
|
||||
req = await aio.ajax("GET", "/static/test.txt")
|
||||
text = req.data
|
||||
|
||||
aio.run(read())
|
||||
|
||||
def write(*args):
|
||||
div = document['text']
|
||||
div.textContent = text
|
||||
|
||||
document['text'].bind('click', write)
|
||||
|
||||
owo.owo()
|
5
webapp/js/owo.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from browser import document
|
||||
|
||||
def owo(*args):
|
||||
div = document['text']
|
||||
div.textContent = 'Click Me'
|
32
webapp/templates/pages/main.html
Normal file
|
@ -0,0 +1,32 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>UwU</title>
|
||||
<style>
|
||||
body {
|
||||
background-color: #050505;
|
||||
color: #DDD;
|
||||
}
|
||||
#content {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
#text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 48pt;
|
||||
width: 400px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body onload="brython({debug: 1, indexedDB: false})">
|
||||
<div id="content">
|
||||
<div id="text"></div>
|
||||
</div>
|
||||
<script src="/web/js/brython.js"></script>
|
||||
<script src="/web/js/brython_modules.js"></script>
|
||||
<script src="/web/js/main.py" type="text/python"></script>
|
||||
</body>
|
||||
</html>
|
11
webapp/views.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
import aiohttp
|
||||
import aiohttp_jinja2
|
||||
|
||||
from social.config import script_path
|
||||
|
||||
async def main(request):
|
||||
return aiohttp_jinja2.render_template('pages/main.html', request, None)
|
||||
|
||||
async def modules(request):
|
||||
module = request.match_info['module']
|
||||
return aiohttp.web.FileResponse(f'{script_path}/webapp/js/{module}.py')
|