From ea5d197833ad85213783d7e10297ab6aa3e1dc67 Mon Sep 17 00:00:00 2001 From: stupidcomputer Date: Sat, 9 Nov 2024 23:36:26 -0600 Subject: [PATCH] some initial ideas -- needs to be cleaned up a lot --- .gitignore | 3 + .vscode/settings.json | 3 + shell.nix | 8 ++ tfbbridge/__init__.py | 169 ++++++++++++++++++++++++++++ tfbbridge/dbutils.py | 34 ++++++ tfbbridge/groupme.py | 19 ++++ tfbbridge/schema.sql | 32 ++++++ tfbbridge/templates/add_new.html | 9 ++ tfbbridge/templates/base.html | 9 ++ tfbbridge/templates/new_org.html | 9 ++ tfbbridge/templates/what_group.html | 12 ++ 11 files changed, 307 insertions(+) create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 shell.nix create mode 100644 tfbbridge/__init__.py create mode 100644 tfbbridge/dbutils.py create mode 100644 tfbbridge/groupme.py create mode 100644 tfbbridge/schema.sql create mode 100644 tfbbridge/templates/add_new.html create mode 100644 tfbbridge/templates/base.html create mode 100644 tfbbridge/templates/new_org.html create mode 100644 tfbbridge/templates/what_group.html diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5319c96 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +tfbbridge.sqlite3 +__pycache__/ +*.pyc \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..786d4d3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "nixEnvSelector.nixFile": "${workspaceFolder}/shell.nix" +} \ No newline at end of file diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..e1a1017 --- /dev/null +++ b/shell.nix @@ -0,0 +1,8 @@ +{ pkgs ? import {} }: + pkgs.mkShell { + # nativeBuildInputs is usually what you want -- tools you need to run + nativeBuildInputs = with pkgs; [ + buildPackages.python311Packages.requests + buildPackages.python311Packages.flask + ]; +} \ No newline at end of file diff --git a/tfbbridge/__init__.py b/tfbbridge/__init__.py new file mode 100644 index 0000000..67ba970 --- /dev/null +++ b/tfbbridge/__init__.py @@ -0,0 +1,169 @@ +from flask import Flask, request, render_template, g, redirect +import requests +from .dbutils import get_and_init_db, get_db +from .groupme import group_sharing_url_to_id +import json +import uuid + +app = Flask(__name__) +app.config.from_mapping( + DATABASE_LOCATION = "tfbbridge.sqlite3", + BOT_ID = "4b841d36f2a0788192b5375335" +) + +@app.route("/") +def testing(): + return "testing" + +@app.route("/add_bot", methods=["GET", "POST"]) +def add_bot(): + if request.method == "GET": + return render_template("add_new.html") + elif request.method == "POST": + try: + url = request.form["chaturl"] + try: + group_id = group_sharing_url_to_id(url) + except: + return "Bad URL", 400 + + db = get_db() + try: + db.execute( + "INSERT INTO child_groups (group_id) VALUES (?)", + (group_id,), + ) + db.commit() + except db.IntegrityError: + return "This group is already registered." + + bot_id = app.config["BOT_ID"] + print(bot_id) + + requests.post("https://api.groupme.com/v3/bots?token={}".format(bot_id)) + + return url + + except KeyError: + return "Bad URL", 400 + else: + return "Invalid Method", 400 + +@app.route("/list_child_groups") +def list_child_groups(): + db = get_db() + cursor = db.cursor() + cursor.execute('SELECT group_id FROM child_groups') + results = cursor.fetchall() + groups = [i[0] for i in results] + return groups + +@app.route("/groupme/oauth_callback") +def oauth_callback(): + token = request.args.get("access_token") + print(token) + + r = requests.get("https://api.groupme.com/v3/groups?token={}".format(token)) + data = r.json()["response"] + data = [ + { + "group_id": i["group_id"], + "name": i["name"], + } + for i in data] + + return render_template("what_group.html", groups=data, token=token) + +@app.route("/groupme/add_group", methods=["POST"]) +def add_group(): + token = request.form["token"] + target_group = request.form["to_add"] + print(target_group) + + print(token, target_group) + + headers = { + 'Content-Type': 'application/json', + } + + params = { + 'token': token, + } + + json_data = { + 'bot': { + 'name': 'tfbbot', + 'group_id': target_group, + }, + } + + r = requests.post('https://api.groupme.com/v3/bots', params=params, headers=headers, json=json_data) + + payload = r.json() + bot_id = payload["response"]["bot_id"] + + return r.text + +@app.route("/groupme/authorize") +def authorize(): + return redirect( + "https://oauth.groupme.com/oauth/authorize?client_id=SgAPMhA6H1UuBE2QD2O5JE1BM6WnysRu6AP1ib6jQsqrH3QA", + ) + +@app.route("/groupme/create_organization", methods=["GET", "POST"]) +def create_organization(): + if request.method == "GET": + return render_template("new_org.html") + elif request.method == "POST": + name = request.form["orgname"] + + db = get_db() + try: + db.execute( + "INSERT INTO organizations (org_name, admin_url, addition_url) VALUES (?, ?, ?)", + (name, uuid.uuid4().hex, uuid.uuid4().hex,), + ) + db.commit() + + return "testing" + except db.IntegrityError: + return "This group is already registered." + +@app.route("/groupme/list_orgs") +def list_orgs(): + db = get_db() + cursor = db.cursor() + cursor.execute('SELECT * FROM organizations') + results = cursor.fetchall() + results = [(i[0], i[1], i[2], i[3]) for i in results] + print(results) + return results + +@app.route("/groupme/org_info/") +def org_info(org): + db = get_db() + cursor = db.cursor() + cursor.execute('SELECT id, org_name FROM organizations WHERE admin_url=?', (org,)) + org_id, name = cursor.fetchone() + + cursor.execute('SELECT group_name, group_id, bot_id, chan_type FROM channels WHERE organization=?', (org_id,)) + results = cursor.fetchall() + results = [ + { + "name": i[0], + "group_id": i[1], + "bot_id": i[2], + "chan_type": i[3], + } + for i in results] + return results + +@app.route("/groupme/add_chan_to_org/") +def add_chan_to_org(org): + db = get_db() + cursor = db.cursor() + cursor.execute('SELECT id, org_name FROM organizations WHERE addition_url=?', (org,)) + org_id, name = cursor.fetchone() + return redirect( + "https://oauth.groupme.com/oauth/authorize?client_id=SgAPMhA6H1UuBE2QD2O5JE1BM6WnysRu6AP1ib6jQsqrH3QA&state=" + org, + ) diff --git a/tfbbridge/dbutils.py b/tfbbridge/dbutils.py new file mode 100644 index 0000000..f4232b1 --- /dev/null +++ b/tfbbridge/dbutils.py @@ -0,0 +1,34 @@ +""" +Database shenanigans +""" + +import sqlite3 +import os +from flask import current_app, g + +def get_and_init_db(database: str): + # check if the file exists first + new_database = not os.path.exists(database) + + db = sqlite3.connect( + database, + detect_types=sqlite3.PARSE_DECLTYPES + ) + db.row_factory = sqlite3.Row + + if new_database: + with current_app.open_resource("schema.sql") as handle: + schema = handle.read().decode('utf8') + db.executescript(schema) + + print("I've initialized the database!") + + return db + +def get_db(): + # given a global object, populate it if necessary and return the db + + if 'db' not in g: + g.db = get_and_init_db(current_app.config["DATABASE_LOCATION"]) + + return g.db \ No newline at end of file diff --git a/tfbbridge/groupme.py b/tfbbridge/groupme.py new file mode 100644 index 0000000..8fdedf7 --- /dev/null +++ b/tfbbridge/groupme.py @@ -0,0 +1,19 @@ +# misc groupme utilities + +from urllib.parse import urlparse + +def group_sharing_url_to_id(url: str) -> str: + """ + Convert a GroupMe group sharing url to an ID. + + For example, given a URL https://groupme.com/join_group/91994372/901HvAj3, the + returned ID should be 91994372. + + The return type is a string because of BigNum future concerns. + """ + + parsed = urlparse(url) + splitted = parsed.path.split('/') + group_id = splitted[2] + + return group_id \ No newline at end of file diff --git a/tfbbridge/schema.sql b/tfbbridge/schema.sql new file mode 100644 index 0000000..738adca --- /dev/null +++ b/tfbbridge/schema.sql @@ -0,0 +1,32 @@ +DROP TABLE IF EXISTS child_groups; +DROP TABLE IF EXISTS parent_groups; + +CREATE TABLE organizations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + org_name TEXT NOT NULL, + admin_url TEXT UNIQUE NOT NULL, + addition_url TEXT UNIQUE NOT NULL +); + +CREATE TABLE channels ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + group_name TEXT NOT NULL, + group_id TEXT UNIQUE NOT NULL, + bot_id TEXT UNIQUE, + /* + chan_type enum + 1: a channel that recieves only announcements -- no one within may send things. + 2: a channel that can recieve annonuncements, and select people can send announcements. + 3: a channel in which everyone can send announcements. + */ + chan_type TEXT CHECK( chan_type in ('1', '2', '3') ) NOT NULL, + organization INTEGER NOT NULL, + FOREIGN KEY(organization) REFERENCES organizations(id) +); + +CREATE TABLE allowlisted_users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id TEXT NOT NULL, + channel INTEGER NOT NULL, + FOREIGN KEY(channel) REFERENCES channels(id) +); \ No newline at end of file diff --git a/tfbbridge/templates/add_new.html b/tfbbridge/templates/add_new.html new file mode 100644 index 0000000..6ec5c51 --- /dev/null +++ b/tfbbridge/templates/add_new.html @@ -0,0 +1,9 @@ +{% block title %}Add bot{% endblock %} + +{% block content %} +
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/tfbbridge/templates/base.html b/tfbbridge/templates/base.html new file mode 100644 index 0000000..c4cd244 --- /dev/null +++ b/tfbbridge/templates/base.html @@ -0,0 +1,9 @@ + + + + {% block title %}{% endblock %} + + + + {% block content %}{% endblock %} + \ No newline at end of file diff --git a/tfbbridge/templates/new_org.html b/tfbbridge/templates/new_org.html new file mode 100644 index 0000000..2764e5c --- /dev/null +++ b/tfbbridge/templates/new_org.html @@ -0,0 +1,9 @@ +{% block title %}New organization{% endblock %} + +{% block content %} +
+ + + +
+{% endblock %} \ No newline at end of file diff --git a/tfbbridge/templates/what_group.html b/tfbbridge/templates/what_group.html new file mode 100644 index 0000000..f5089e0 --- /dev/null +++ b/tfbbridge/templates/what_group.html @@ -0,0 +1,12 @@ +{% block title %}Add bot{% endblock %} + +{% block content %} +
+ {% for group in groups %} + + + {% endfor %} + + +
+{% endblock %} \ No newline at end of file