stop using pusher (#37)

Co-authored-by: Aevann1 <randomname42029@gmail.com>
Co-authored-by: Snakes <duolsm@outlook.com>
Reviewed-on: #37
Co-authored-by: Aevann <aevann@noreply.fsdfsd.net>
Co-committed-by: Aevann <aevann@noreply.fsdfsd.net>
pull/38/head
Aevann 2022-12-02 22:21:18 +00:00 committed by Snakes
parent 3542703ced
commit 5c2dab73c7
20 changed files with 240 additions and 1552 deletions

4
env
View File

@ -12,8 +12,8 @@ export DISCORD_BOT_TOKEN="blahblahblah"
export TURNSTILE_SITEKEY="blahblahblah"
export TURNSTILE_SECRET="blahblahblah"
export YOUTUBE_KEY="blahblahblah"
export PUSHER_ID="blahblahblah"
export PUSHER_KEY="blahblahblah"
export VAPID_PUBLIC_KEY="blahblahblah"
export VAPID_PRIVATE_KEY="blahblahblah"
export IMGUR_KEY="blahblahblah"
export SPAM_SIMILARITY_THRESHOLD="0.5"
export SPAM_URL_SIMILARITY_THRESHOLD="0.1"

View File

@ -0,0 +1,70 @@
'use strict';
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function updateSubscriptionOnServer(subscription, apiEndpoint) {
var formData = new FormData();
formData.append("subscription_json", JSON.stringify(subscription));
const xhr = createXhrWithFormKey(
apiEndpoint,
'POST',
formData
);
xhr[0].send(xhr[1]);
}
function subscribeUser(swRegistration, applicationServerPublicKey, apiEndpoint) {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
.then(function(subscription) {
return updateSubscriptionOnServer(subscription, apiEndpoint);
})
.then(function(response) {
if (!response.ok) {
throw new Error('Bad status code from server.');
}
return response.json();
})
.then(function(responseData) {
if (responseData.status!=="success") {
throw new Error('Bad response from server.');
}
})
.catch(function() {
});
}
function registerServiceWorker(serviceWorkerUrl, applicationServerPublicKey, apiEndpoint){
let swRegistration = null;
if ('serviceWorker' in navigator && 'PushManager' in window) {
navigator.serviceWorker.register(serviceWorkerUrl)
.then(function(swReg) {
subscribeUser(swReg, applicationServerPublicKey, apiEndpoint);
swRegistration = swReg;
})
.catch(function() {
});
} else {
}
return swRegistration;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
'use strict';
const CACHE_NAME = "offlineCache-v1";
const OFFLINE_URL = "/assets/offline.html";
self.addEventListener("install", () => {
const cacheOfflinePage = async () => {
const cache = await caches.open(CACHE_NAME);
await cache.add(new Request(OFFLINE_URL, {cache: "reload"}));
};
cacheOfflinePage().then(() => {
this.skipWaiting();
});
});
self.addEventListener("activate", (event) => {
const expectedCaches = [CACHE_NAME];
event.waitUntil(
caches.keys().then(keys => Promise.all(
keys.map(key => {
if (!expectedCaches.includes(key)) {
return caches.delete(key);
}
})
))
);
});
self.addEventListener("fetch", (event) => {
if (event.request.mode === "navigate") {
event.respondWith((async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) return preloadResponse;
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
const cachedResponse = await caches.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});
self.addEventListener('push', function(event) {
const pushData = event.data.text();
let data, title, body, url, icon;
try {
data = JSON.parse(pushData);
title = data.title;
body = data.body;
url = data.url;
icon = data.icon;
} catch(e) {
title = "Untitled";
body = pushData;
}
const options = {
body: body,
data: {url: url},
icon: icon
};
event.waitUntil(
self.registration.showNotification(title, options)
);
});
self.addEventListener('notificationclick', (e) => {
if (e.notification.data.url)
e.waitUntil(clients.openWindow(e.notification.data.url));
e.notification.close();
});

File diff suppressed because one or more lines are too long

View File

@ -35,3 +35,4 @@ from .sub_logs import *
from .media import *
if FEATURES['STREAMERS']:
from .streamers import *
from .push_subscriptions import *

View File

@ -0,0 +1,19 @@
import time
from sqlalchemy import Column, ForeignKey
from sqlalchemy.sql.sqltypes import *
from files.classes import Base
class PushSubscription(Base):
__tablename__ = "push_subscriptions"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
subscription_json = Column(String, primary_key=True)
created_utc = Column(Integer)
def __init__(self, *args, **kwargs):
if "created_utc" not in kwargs: kwargs["created_utc"] = int(time.time())
super().__init__(*args, **kwargs)
def __repr__(self):
return f"<{self.__class__.__name__}(id={self.id})>"

View File

@ -1,9 +1,10 @@
import json
from sys import stdout
from flask import g
from pusher_push_notifications import PushNotifications
from pywebpush import webpush
from files.classes import Comment, Notification
from files.classes import Comment, Notification, PushSubscription
from .const import *
from .regex import *
@ -103,35 +104,26 @@ def NOTIFY_USERS(text, v):
return notify_users - bots
if PUSHER_ID != DEFAULT_CONFIG_VALUE:
beams_client = PushNotifications(instance_id=PUSHER_ID, secret_key=PUSHER_KEY)
def pusher_thread(interests, title, notifbody, url):
title = censor_slurs(title, None)
notifbody = censor_slurs(notifbody, None)
if len(notifbody) > PUSHER_LIMIT:
notifbody = notifbody[:PUSHER_LIMIT] + "..."
def push_notif(uid, title, body, url):
if VAPID_PUBLIC_KEY == DEFAULT_CONFIG_VALUE:
return
beams_client.publish_to_interests(
interests=[interests],
publish_body={
'web': {
'notification': {
'title': title,
'body': notifbody,
'deep_link': url,
'icon': f'{SITE_FULL}/icon.webp?v=1',
}
},
'fcm': {
'notification': {
'title': title,
'body': notifbody,
},
'data': {
'url': url,
}
}
},
)
stdout.flush()
if len(body) > PUSH_NOTIF_LIMIT:
body = body[:PUSH_NOTIF_LIMIT] + "..."
subscriptions = g.db.query(PushSubscription).filter_by(user_id=uid).all()
for subscription in subscriptions:
try:
response = webpush(
subscription_info=json.loads(subscription.subscription_json),
data=json.dumps({
"title": title,
"body": body,
'url': url,
'icon': f'{SITE_FULL}/icon.webp?v=1',
}),
vapid_private_key=VAPID_PRIVATE_KEY,
vapid_claims={"sub": f"mailto:{EMAIL}"}
)
except: continue

View File

@ -16,8 +16,8 @@ DISCORD_BOT_TOKEN = environ.get("DISCORD_BOT_TOKEN", DEFAULT_CONFIG_VALUE).strip
TURNSTILE_SITEKEY = environ.get("TURNSTILE_SITEKEY", DEFAULT_CONFIG_VALUE).strip()
TURNSTILE_SECRET = environ.get("TURNSTILE_SECRET", DEFAULT_CONFIG_VALUE).strip()
YOUTUBE_KEY = environ.get("YOUTUBE_KEY", DEFAULT_CONFIG_VALUE).strip()
PUSHER_ID = environ.get("PUSHER_ID", DEFAULT_CONFIG_VALUE).strip()
PUSHER_KEY = environ.get("PUSHER_KEY", DEFAULT_CONFIG_VALUE).strip()
VAPID_PUBLIC_KEY = environ.get("VAPID_PUBLIC_KEY", DEFAULT_CONFIG_VALUE).strip()
VAPID_PRIVATE_KEY = environ.get("VAPID_PRIVATE_KEY", DEFAULT_CONFIG_VALUE).strip()
IMGUR_KEY = environ.get("IMGUR_KEY", DEFAULT_CONFIG_VALUE).strip()
SPAM_SIMILARITY_THRESHOLD = float(environ.get("SPAM_SIMILARITY_THRESHOLD", "0.5").strip())
SPAM_URL_SIMILARITY_THRESHOLD = float(environ.get("SPAM_URL_SIMILARITY_THRESHOLD", "0.1").strip())
@ -55,7 +55,7 @@ DEFAULT_RATELIMIT = "3/second;30/minute;200/hour;1000/day"
DEFAULT_RATELIMIT_SLOWER = "1/second;30/minute;200/hour;1000/day"
DEFAULT_RATELIMIT_USER = DEFAULT_RATELIMIT_SLOWER
PUSHER_LIMIT = 1000 # API allows 10 KB but better safe than sorry
PUSH_NOTIF_LIMIT = 1000 # API allows 10 KB but better safe than sorry
IS_LOCALHOST = SITE == "localhost" or SITE == "127.0.0.1" or SITE.startswith("192.168.") or SITE.endswith(".local")

View File

@ -48,3 +48,4 @@ if FEATURES['ASSET_SUBMISSIONS']:
if FEATURES['STREAMERS']:
from .streamers import *
from .special import *
from .push_notifs import *

View File

@ -313,9 +313,7 @@ def comment(v):
n = Notification(comment_id=c.id, user_id=x)
g.db.add(n)
if parent.author.id != v.id and PUSHER_ID != DEFAULT_CONFIG_VALUE and not v.shadowbanned:
interests = f'{SITE}{parent.author.id}'
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and parent.author.id != v.id and not v.shadowbanned:
title = f'New reply by @{c.author_name}'
if len(c.body) > 500: notifbody = c.body[:500] + '...'
@ -323,7 +321,7 @@ def comment(v):
url = f'{SITE_FULL}/comment/{c.id}?context=8&read=true#context'
gevent.spawn(pusher_thread, interests, title, notifbody, url)
push_notif(parent.author.id, title, notifbody, url)

View File

@ -76,7 +76,7 @@ def calc_users():
@app.context_processor
def inject_constants():
return {"environ":environ, "SITE":SITE, "SITE_NAME":SITE_NAME, "SITE_FULL":SITE_FULL,
"AUTOJANNY_ID":AUTOJANNY_ID, "MODMAIL_ID":MODMAIL_ID, "PUSHER_ID":PUSHER_ID,
"AUTOJANNY_ID":AUTOJANNY_ID, "MODMAIL_ID":MODMAIL_ID, "VAPID_PUBLIC_KEY":VAPID_PUBLIC_KEY,
"CC":CC, "CC_TITLE":CC_TITLE, "listdir":listdir, "os_path":path, "AEVANN_ID":AEVANN_ID,
"PIZZASHILL_ID":PIZZASHILL_ID, "DEFAULT_COLOR":DEFAULT_COLOR,
"COLORS":COLORS, "time":time, "PERMS":PERMS, "FEATURES":FEATURES,

View File

@ -0,0 +1,23 @@
from files.routes.wrappers import *
from files.__main__ import app
from flask import request, g
from files.classes.push_subscriptions import PushSubscription
@app.post("/push_subscribe")
@auth_required
def push_subscribe(v):
subscription_json = request.values.get("subscription_json")
subscription = g.db.query(PushSubscription).filter_by(
user_id=v.id,
subscription_json=subscription_json,
).one_or_none()
if not subscription:
subscription = PushSubscription(
user_id=v.id,
subscription_json=subscription_json,
)
g.db.add(subscription)
return ''

View File

@ -471,9 +471,7 @@ def message2(v, username):
g.db.add(notif)
if PUSHER_ID != DEFAULT_CONFIG_VALUE and not v.shadowbanned:
interests = f'{SITE}{user.id}'
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and not v.shadowbanned:
title = f'New message from @{username}'
if len(message) > 500: notifbody = message[:500] + '...'
@ -481,7 +479,7 @@ def message2(v, username):
url = f'{SITE_FULL}/notifications/messages'
gevent.spawn(pusher_thread, interests, title, notifbody, url)
push_notif(user.id, title, notifbody, url)
return {"message": "Message sent!"}
@ -545,9 +543,7 @@ def messagereply(v):
notif = Notification(comment_id=c.id, user_id=user_id)
g.db.add(notif)
if PUSHER_ID != DEFAULT_CONFIG_VALUE and not v.shadowbanned:
interests = f'{SITE}{user_id}'
if VAPID_PUBLIC_KEY != DEFAULT_CONFIG_VALUE and not v.shadowbanned:
title = f'New message from @{v.username}'
if len(body) > 500: notifbody = body[:500] + '...'
@ -555,7 +551,7 @@ def messagereply(v):
url = f'{SITE_FULL}/notifications/messages'
gevent.spawn(pusher_thread, interests, title, notifbody, url)
push_notif(user_id, title, notifbody, url)
top_comment = c.top_comment(g.db)

View File

@ -167,14 +167,15 @@
</nav>
{% endif %}
{% if request.path == '/' and PUSHER_ID != DEFAULT_CONFIG_VALUE and v %}
<div class="d-none" id="strid">{{SITE}}{{v.id}}</div>
<div class="d-none" id="pusherid">{{PUSHER_ID}}</div>
<script defer src="{{'js/vendor/pusher.js' | asset}}"></script>
{% if request.path == '/' and v %}
<script defer src="/assets/js/register_service_worker.js"></script>
<script>
if (typeof Android != 'undefined') {
Android.Subscribe('{{SITE}}{{v.id}}');
}
document.addEventListener('DOMContentLoaded', function () {
registerServiceWorker(
"/assets/js/service_worker.js",
"{{VAPID_PUBLIC_KEY}}",
"/push_subscribe")
});
</script>
{% endif %}

View File

@ -5,7 +5,7 @@ add_header Referrer-Policy "same-origin";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options "deny";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io 00bb6d59-7b11-4339-b1ae-b1f1259d1316.pushnotifications.pusher.com; object-src 'none';";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com; connect-src 'self' tls-use1.fpapi.io api.fpjs.io; object-src 'none';";
sendfile on;
sendfile_max_chunk 1m;

View File

@ -8,7 +8,7 @@ server {
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload";
add_header X-Frame-Options "deny";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com rdrama.net; connect-src 'self' tls-use1.fpapi.io api.fpjs.io 00bb6d59-7b11-4339-b1ae-b1f1259d1316.pushnotifications.pusher.com; object-src 'none';";
add_header Content-Security-Policy "script-src 'self' 'unsafe-inline' challenges.cloudflare.com rdrama.net; connect-src 'self' tls-use1.fpapi.io api.fpjs.io; object-src 'none';";
location / {
proxy_pass http://localhost:5000/;

View File

@ -19,6 +19,7 @@ matplotlib
owoify-py
Pillow
pyotp
pywebpush
qrcode
redis
requests
@ -27,7 +28,6 @@ tinycss2
tldextract
user-agents
psycopg2-binary
pusher_push_notifications
youtube-dl
yattag
webptools

View File

@ -1,4 +1,4 @@
. ./env
. ./.env
export DATABASE_URL="postgresql://postgres@postgres:5432"
export REDIS_URL="redis://redis:6379"
export PROXY_URL="http://opera-proxy:18080"

View File

@ -5,8 +5,8 @@ apt -y install git redis-server python3-pip ffmpeg tmux nginx snapd ufw gpg-agen
git config --global credential.helper store
cd /rDrama
git config branch.frost.rebase true
cp ./env /env
. /env
cp ./env ./.env
. ./.env
mkdir /scripts
cp ./startup.sh /scripts/s