write the original user interface prototype
this is insecure, terrible, horrible code. do not use. things implemented: - add trusted users - add trusted channels and follower channels - qr codes and stuff
This commit is contained in:
parent
06a9bbe343
commit
2fceb6679f
|
@ -4,5 +4,6 @@
|
||||||
nativeBuildInputs = with pkgs; [
|
nativeBuildInputs = with pkgs; [
|
||||||
buildPackages.python311Packages.django
|
buildPackages.python311Packages.django
|
||||||
buildPackages.python311Packages.requests
|
buildPackages.python311Packages.requests
|
||||||
|
buildPackages.python311Packages.qrcode
|
||||||
];
|
];
|
||||||
}
|
}
|
|
@ -1,2 +1 @@
|
||||||
db.sqlite3
|
db.sqlite3
|
||||||
manage.py
|
|
|
@ -1,10 +1,5 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|
||||||
from .models import Organization, Group
|
from .models import Organization
|
||||||
|
|
||||||
@admin.register(Organization)
|
admin.site.register(Organization)
|
||||||
class OrganizationAdmin(admin.ModelAdmin):
|
|
||||||
exclude = ('_sender_url', '_receiver_url')
|
|
||||||
readonly_fields = ('add_sending_group', 'add_receiving_group', )
|
|
||||||
|
|
||||||
admin.site.register(Group)
|
|
||||||
|
|
|
@ -1,36 +1,9 @@
|
||||||
from django.db import models
|
from django.db import models
|
||||||
import uuid
|
|
||||||
|
|
||||||
def get_uuid():
|
|
||||||
return uuid.uuid4().hex
|
|
||||||
|
|
||||||
class Organization(models.Model):
|
class Organization(models.Model):
|
||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=256,
|
max_length=256,
|
||||||
)
|
)
|
||||||
_sender_url = models.CharField(
|
owner = models.CharField(max_length=256)
|
||||||
max_length=256,
|
trusted_users = models.TextField() # either json or empty
|
||||||
default = get_uuid
|
channel_info = models.TextField() # either json or empty
|
||||||
)
|
|
||||||
_receiver_url = models.CharField(
|
|
||||||
max_length=256,
|
|
||||||
default = get_uuid
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def add_sending_group(self):
|
|
||||||
return "https://tfb.beepboop.systems/groupme/add/send/{}".format(
|
|
||||||
self._sender_url
|
|
||||||
)
|
|
||||||
@property
|
|
||||||
def add_receiving_group(self):
|
|
||||||
return "https://tfb.beepboop.systems/groupme/add/recv/{}".format(
|
|
||||||
self._receiver_url
|
|
||||||
)
|
|
||||||
|
|
||||||
class Group(models.Model):
|
|
||||||
name = models.CharField(max_length=256)
|
|
||||||
group_id = models.CharField(max_length=256)
|
|
||||||
bot_id = models.CharField(max_length=256)
|
|
||||||
can_send_notices = models.BooleanField()
|
|
||||||
belongs_to = models.ForeignKey(Organization, on_delete=models.SET_NULL, null=True)
|
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1>Add groups to "{{ org.name }}"</h1>
|
||||||
|
|
||||||
|
{% if trusted %}
|
||||||
|
<form class="form-horizontal" action="{% url 'add_trusted_channel' org.id %}" method="POST">
|
||||||
|
{% else %}
|
||||||
|
<form class="form-horizontal" action="{% url 'add_channel_to_organization' org.id %}" method="POST">
|
||||||
|
{% endif %}
|
||||||
|
<style>
|
||||||
|
.name-modified {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{% for group in groups %}
|
||||||
|
<div class="checkbox-inline">
|
||||||
|
<input type="checkbox" id="g_{{ group.id }}" value="{{ group.id }}" name="g_{{ group.id }}" />
|
||||||
|
<label for="g_{{ group.id }}" class="name-modified">{{ group.name }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Create organization</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,39 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
<h1>Add a trusted user</h1>
|
||||||
|
|
||||||
|
<form class="form-horizontal" action="{% url 'add_trusted_user' org.id %}" method="POST">
|
||||||
|
<div class="form-group-inline">
|
||||||
|
<input type="search" id="searchbox" class="form-control" placeholder="Search users..."></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for user in users %}
|
||||||
|
<div class="checkbox-inline user_sortable">
|
||||||
|
<input type="checkbox" id="u_{{ user.id }}" value="{{ user.id }}" name="u_{{ user.id }}" />
|
||||||
|
<label for="u_{{ user.id }}" class="name-modified">{{ user.name }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const usersearch = document.getElementById("searchbox");
|
||||||
|
const userchecks = document.getElementsByClassName("user_sortable");
|
||||||
|
usersearch.addEventListener("keyup", function () {
|
||||||
|
var query = usersearch.value.toLowerCase();
|
||||||
|
for (let user of userchecks) {
|
||||||
|
var name = user.children[1].innerHTML.toLowerCase()
|
||||||
|
if(name.includes(query)) {
|
||||||
|
user.style.display = null;
|
||||||
|
} else {
|
||||||
|
user.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Add user</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
{% if title %}
|
||||||
|
<title>{{ title }} - tfb-groupme-bridge</title>
|
||||||
|
{% else %}
|
||||||
|
<title>tfb-groupme-bridge</title>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<nav class="navbar navbar-inverse">
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="navbar-header">
|
||||||
|
<a class="navbar-brand" href="/groupme">tfb-groupme-bridge</a>
|
||||||
|
</div>
|
||||||
|
<ul class="nav navbar-nav navbar-right">
|
||||||
|
{% if request.session.logged_in %}
|
||||||
|
<li class="dropdown">
|
||||||
|
<a class="dropdown-toggle" data-toggle="dropdown" href="#">{{ request.session.groupme_name }} <span class="caret"></span></a>
|
||||||
|
<ul class="dropdown-menu">
|
||||||
|
<li><a href="{% url 'logout' %}">Log out</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
{% else %}
|
||||||
|
<li><a href="{% url 'login' %}"><span class="glyphicon glyphicon-log-in"></span> Login</a></li>
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<div class="container">
|
||||||
|
{% block 'body' %}
|
||||||
|
{% endblock %}
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,74 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
<h1>Create a new organization</h1>
|
||||||
|
|
||||||
|
<p><i>You will be the administrator of this organization. If you don't want to be, <b>do not create the organization under your account — use someone else's!</b> You <b>won't be able to transfer ownership later</b>, so make a wise decision now.</i></p>
|
||||||
|
<form class="form-horizontal" action="{% url 'add_organization_flow_select' %}" method="POST">
|
||||||
|
<div class="form-group-inline">
|
||||||
|
<label class="control-label" for="organization-name">Organization name:</label>
|
||||||
|
<input type="text" id="organization-name" name="name" class="form-control">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.name-modified {
|
||||||
|
font-weight: 100;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h2>Choose trusted channels</h2>
|
||||||
|
<p>
|
||||||
|
In order to have a channel appear as a choice here, you must be in that channel and you must be an admin in that channel.
|
||||||
|
Trusted users can only send messages from trusted channels.
|
||||||
|
|
||||||
|
(For technical reasons, if you're in more than 200 groups, we don't show all of them.
|
||||||
|
Also, you're in <i>200 groups?</i>)
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% for group in groups %}
|
||||||
|
<div class="checkbox-inline">
|
||||||
|
<input type="checkbox" id="g_{{ group.id }}" value="{{ group.id }}" name="g_{{ group.id }}" />
|
||||||
|
<label for="g_{{ group.id }}" class="name-modified">{{ group.name }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<h2>Choose trusted users</h2>
|
||||||
|
<p>
|
||||||
|
A user appears here if they're in one of the channels you administrate.
|
||||||
|
If someone is a trusted user, they can send messages to every group in a trusted channel.
|
||||||
|
Don't forget to add yourself!
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-group-inline">
|
||||||
|
<input type="search" id="searchbox" class="form-control" placeholder="Search users..."></input>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for user in users %}
|
||||||
|
<div class="checkbox-inline user_sortable">
|
||||||
|
<input type="checkbox" id="u_{{ user.id }}" value="{{ user.id }}" name="u_{{ user.id }}" />
|
||||||
|
<label for="u_{{ user.id }}" class="name-modified">{{ user.name }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const usersearch = document.getElementById("searchbox");
|
||||||
|
const userchecks = document.getElementsByClassName("user_sortable");
|
||||||
|
usersearch.addEventListener("keyup", function () {
|
||||||
|
var query = usersearch.value.toLowerCase();
|
||||||
|
for (let user of userchecks) {
|
||||||
|
var name = user.children[1].innerHTML.toLowerCase()
|
||||||
|
if(name.includes(query)) {
|
||||||
|
user.style.display = null;
|
||||||
|
} else {
|
||||||
|
user.style.display = "none";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<button type="submit" class="btn btn-primary">Create organization</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% csrf_token %}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1>Group added</h1>
|
||||||
|
|
||||||
|
<p>Thanks!</p>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,27 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1>Welcome, {{ request.session.groupme_name }}</h1>
|
||||||
|
|
||||||
|
<h2>Available actions</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'add_organization_flow_select' %}">Create a new organization</a></li>
|
||||||
|
<li><a href="{% url 'refresh_group_data' %}">Refresh group data</a></li>
|
||||||
|
<li><a href="{% url 'logout' %}">Log out</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% if organizations|length > 0 %}
|
||||||
|
<h2>Your organizations</h2>
|
||||||
|
<ul>
|
||||||
|
{% for org in organizations %}
|
||||||
|
<li><a href="{% url 'view_organization' org.id %}">{{ org.name }}</a></li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% else %}
|
||||||
|
<h2>Your organizations</h2>
|
||||||
|
|
||||||
|
<p>You don't seem to have any organizations — maybe <a href="{% url 'add_organization_flow_select' %}">create one</a>?</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1><code>tfb-groupme-bridge</code></h1>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1>Logged out</h1>
|
||||||
|
|
||||||
|
You've been logged out.
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li><a href="{% url 'index' %}">Return to the homepage</a></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -0,0 +1,79 @@
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% block 'body' %}
|
||||||
|
|
||||||
|
<h1>{{ org.name }}</h1>
|
||||||
|
|
||||||
|
<h2>Channels</h2>
|
||||||
|
<table class="table table-hover table-responseive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Level</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for channel in channels %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ channel.name }}</td>
|
||||||
|
<td>{{ channel.role }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{% url 'delete_channel' org.id channel.gid %}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||||
|
{% if channel.role == "trusted" %}
|
||||||
|
<a href="{% url 'demote_channel' org.id channel.gid %}" class="btn btn-primary"><span class="glyphicon glyphicon-arrow-down"></span></a>
|
||||||
|
{% else %}
|
||||||
|
<a href="#" class="btn btn-primary disabled"><span class="glyphicon glyphicon-arrow-down"></span></a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{% url 'add_trusted_channel' org.id %}" class="btn btn-primary">Add new trusted channel</a>
|
||||||
|
<a href="{{ scheme }}://{{ hostname }}{% url 'add_channel_to_organization' org.id %}" class="btn btn-primary">Add new follower channel</a>
|
||||||
|
<button id="sharebutton" type="button" class="btn btn-primary">Copy share link</button>
|
||||||
|
<a href="{% url 'qr_code' org.id %}" class="btn btn-primary">View QR Code</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Trusted Users</h2>
|
||||||
|
|
||||||
|
<table class="table table-hover table-responseive">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Sent messages</th>
|
||||||
|
<th>Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for user in users %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ user.name }}</td>
|
||||||
|
<td>{{ user.number_messages }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{% url 'remove_user' org.id user.uid %}" class="btn btn-danger"><span class="glyphicon glyphicon-trash"></span></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div class="btn-group">
|
||||||
|
<a href="{% url 'add_trusted_user' org.id %}" class="btn btn-primary">Add new trusted user</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const sharelink = document.getElementById("sharelink");
|
||||||
|
const sharebutton = document.getElementById("sharebutton");
|
||||||
|
document.getElementById("sharebutton").addEventListener("click", () => {
|
||||||
|
navigator.clipboard.writeText(sharelink.href);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -3,6 +3,17 @@ from . import views
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("", views.index, name="index"),
|
path("", views.index, name="index"),
|
||||||
|
path("logout", views.logout, name="logout"),
|
||||||
|
path("login", views.login, name="login"),
|
||||||
path("oauth_callback", views.handle_oauth, name="handle_oauth"),
|
path("oauth_callback", views.handle_oauth, name="handle_oauth"),
|
||||||
path("add/send/<str:url_id>", views.add_sending_group, name="add_sending_group"),
|
path("refresh_group_data", views.refresh_group_data, name="refresh_group_data"),
|
||||||
|
path("add/organization", views.add_organization_flow_select, name="add_organization_flow_select"),
|
||||||
|
path("organization/<int:org_id>", views.view_organization, name="view_organization"),
|
||||||
|
path("organization/<int:org_id>/add_channel", views.add_channel_to_organization, name="add_channel_to_organization"),
|
||||||
|
path("organization/<int:org_id>/add_trusted_channel", views.add_trusted_channel, name="add_trusted_channel"),
|
||||||
|
path("organization/<int:org_id>/add_trusted_user", views.add_trusted_user, name="add_trusted_user"),
|
||||||
|
path("organization/<int:org_id>/qr_code", views.qr_code, name="qr_code"),
|
||||||
|
path("organization/<int:org_id>/delete_channel/<str:chan>", views.delete_channel, name="delete_channel"),
|
||||||
|
path("organization/<int:org_id>/demote_channel/<str:chan>", views.demote_channel, name="demote_channel"),
|
||||||
|
path("organization/<int:org_id>/remove_user/<str:user>", views.remove_user, name="remove_user"),
|
||||||
]
|
]
|
|
@ -1,35 +1,443 @@
|
||||||
from django.http import HttpResponse, HttpResponseBadRequest
|
from django.http import HttpResponse, HttpResponseBadRequest
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect, render
|
||||||
|
from django.urls import reverse
|
||||||
from .models import Organization
|
from .models import Organization
|
||||||
import requests
|
import requests
|
||||||
|
import pickle
|
||||||
|
import qrcode
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
|
||||||
auth_link = "https://oauth.groupme.com/oauth/authorize?client_id=qAAAc8GSUlTA8C3Ypo9CMiFQwQCQnU8zPU5KGEtz3FYHDqP5"
|
bot_name = "tfbbot"
|
||||||
|
auth_link = "https://oauth.groupme.com/oauth/authorize?client_id=KbStJZciPGivvLuTN9XX6aXf6NHVcxDSiVR0wRC0cqVDuDfG"
|
||||||
|
|
||||||
def index(request):
|
def login(request):
|
||||||
return HttpResponse("Hello, world. You're at the polls index.")
|
return redirect(auth_link)
|
||||||
|
|
||||||
def handle_send_requests(token, org_id):
|
def add_organization_flow_select(request):
|
||||||
# firstly, check what organization the org_id belongs to
|
user_group_data = pickle.loads(
|
||||||
matching = Organization.objects.filter(_sender_url = org_id)
|
bytes.fromhex(
|
||||||
if matching == []:
|
request.session["groupme_groups_pickle"]
|
||||||
return HttpResponseBadRequest # this is a fake org_id
|
))
|
||||||
|
user_group_data = user_group_data["response"]
|
||||||
|
if request.method == "GET":
|
||||||
|
rendered_groups = []
|
||||||
|
rendered_users = []
|
||||||
|
for group in user_group_data:
|
||||||
|
our_id = request.session["groupme_id"]
|
||||||
|
for user in group["members"]: # check if we're an admin
|
||||||
|
if user["user_id"] == our_id:
|
||||||
|
if "admin" in user["roles"]: # we are an admin
|
||||||
|
rendered_groups.append({
|
||||||
|
"name": group["name"],
|
||||||
|
"id": group["group_id"]
|
||||||
|
})
|
||||||
|
|
||||||
|
for user in group["members"]:
|
||||||
|
rendered_users.append({
|
||||||
|
"name": user["name"],
|
||||||
|
"id": user["user_id"]
|
||||||
|
})
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# remove duplicate users
|
||||||
|
# https://stackoverflow.com/questions/9427163/remove-duplicate-dict-in-list-in-python
|
||||||
|
rendered_users = [i for n, i in enumerate(rendered_users) if i not in rendered_users[n + 1:]]
|
||||||
|
|
||||||
|
# sort users by name
|
||||||
|
rendered_users = sorted(rendered_users, key=lambda x: x["name"].lower())
|
||||||
|
|
||||||
|
context = {
|
||||||
|
"request": request,
|
||||||
|
"title": "Add organization",
|
||||||
|
"groups": rendered_groups,
|
||||||
|
"users": rendered_users
|
||||||
|
}
|
||||||
|
|
||||||
|
return render(request, "create_organization.html", context)
|
||||||
|
|
||||||
|
elif request.method == "POST":
|
||||||
|
name = request.POST["name"]
|
||||||
|
users = [i[2:] for i in request.POST.keys() if i.startswith("u_")]
|
||||||
|
groups = [i[2:] for i in request.POST.keys() if i.startswith("g_")]
|
||||||
|
group_name_mapping = pickle.loads(
|
||||||
|
bytes.fromhex(
|
||||||
|
request.session["group_name_mapping"]
|
||||||
|
))
|
||||||
|
|
||||||
|
org = Organization()
|
||||||
|
org.owner = request.session["groupme_id"]
|
||||||
|
org.name = name
|
||||||
|
org.trusted_users = ""
|
||||||
|
org.channel_info = ""
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
group_data_collected = []
|
||||||
|
for group in groups:
|
||||||
|
group_data = { "gid": group }
|
||||||
|
data = {
|
||||||
|
"bot": {
|
||||||
|
"name": bot_name,
|
||||||
|
"group_id": group,
|
||||||
|
"active": True,
|
||||||
|
"callback_url": "https://marching.beepboop.systems/groupme/organization/{}/callback/{}".format(
|
||||||
|
org.id,
|
||||||
|
str(random.randint(1, 100000))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
"https://api.groupme.com/v3/bots?token={}".format(
|
||||||
|
request.session["groupme_access_token"]
|
||||||
|
),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
print(r.url)
|
||||||
|
group_data["bot_id"] = data["response"]["bot"]["bot_id"]
|
||||||
|
group_data["name"] = group_name_mapping[group_data["gid"]]
|
||||||
|
group_data["role"] = "trusted"
|
||||||
|
|
||||||
|
group_data_collected.append(group_data)
|
||||||
|
|
||||||
|
user_data_collected = []
|
||||||
|
user_name_mappings = pickle.loads(bytes.fromhex(request.session["user_name_mapping"]))
|
||||||
|
for user in users:
|
||||||
|
try:
|
||||||
|
username = user_name_mappings[user]
|
||||||
|
except KeyError:
|
||||||
|
username = "unknown"
|
||||||
|
user_data = { "uid": user, "role": "trusted", "name": username, "number_messages": 0 }
|
||||||
|
user_data_collected.append(user_data)
|
||||||
|
|
||||||
|
org.trusted_users = pickle.dumps(user_data_collected).hex()
|
||||||
|
org.channel_info = pickle.dumps(group_data_collected).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
return redirect(reverse("view_organization", kwargs={"org_id": org.id}))
|
||||||
|
|
||||||
def handle_oauth(request):
|
def handle_oauth(request):
|
||||||
token = request.GET.get('access_token')
|
request.session["groupme_access_token"] = request.GET.get('access_token')
|
||||||
|
|
||||||
|
r = requests.get("https://api.groupme.com/v3/users/me?token={}".format(
|
||||||
|
request.session["groupme_access_token"]
|
||||||
|
))
|
||||||
|
|
||||||
|
data = r.json()
|
||||||
|
request.session["groupme_id"] = data["response"]["id"]
|
||||||
|
request.session["groupme_name"] = data["response"]["name"]
|
||||||
|
|
||||||
|
r = requests.get("https://api.groupme.com/v3/groups?token={}&per_page=200".format(
|
||||||
|
request.session["groupme_access_token"]
|
||||||
|
))
|
||||||
|
|
||||||
|
request.session["groupme_groups_pickle"] = pickle.dumps(r.json()).hex()
|
||||||
|
request.session["logged_in"] = "yes"
|
||||||
|
|
||||||
|
group_name_mapping = {}
|
||||||
|
for group in r.json()["response"]:
|
||||||
|
group_name_mapping[group["id"]] = group["name"]
|
||||||
|
request.session["group_name_mapping"] = pickle.dumps(group_name_mapping).hex()
|
||||||
|
|
||||||
|
user_name_mapping = {}
|
||||||
|
for group in r.json()["response"]:
|
||||||
|
for user in group["members"]:
|
||||||
|
user_name_mapping[user["id"]] = user["name"]
|
||||||
|
user_name_mapping[data["response"]["id"]] = data["response"]["name"]
|
||||||
|
request.session["user_name_mapping"] = pickle.dumps(user_name_mapping).hex()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
request_type = request.session["type"]
|
if request.session["needs_redirect"]:
|
||||||
organization_id = request.session["id"]
|
response = redirect(reverse(request.session["needs_redirect"]))
|
||||||
|
del request.session["needs_redirect"]
|
||||||
|
return response
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return HttpResponseBadRequest
|
pass
|
||||||
|
|
||||||
if request_type == "send":
|
return redirect(reverse("index"))
|
||||||
return handle_send_requests(token, organization_id)
|
|
||||||
|
|
||||||
return HttpResponse(token)
|
def index(request):
|
||||||
|
if "logged_in" in request.session:
|
||||||
|
orgs = Organization.objects.filter(owner=request.session["groupme_id"])
|
||||||
|
return render(request, "index_auth.html", {
|
||||||
|
"title": "Home",
|
||||||
|
"request": request,
|
||||||
|
"organizations": orgs,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
return render(request, "index_unauth.html", {
|
||||||
|
"title": "Home",
|
||||||
|
})
|
||||||
|
|
||||||
def add_sending_group(request, url_id):
|
def refresh_group_data(request):
|
||||||
# first part -- authenticate with the GroupMe api
|
return redirect(reverse("handle_oauth") + "?access_token={}".format(
|
||||||
request.session['type'] = "send"
|
request.session["groupme_access_token"]
|
||||||
request.session['id'] = url_id
|
))
|
||||||
|
|
||||||
return redirect(auth_link)
|
def logout(request):
|
||||||
|
if "logged_in" in request.session:
|
||||||
|
request.session.flush()
|
||||||
|
return render(request, "logged_out.html")
|
||||||
|
else:
|
||||||
|
return redirect(index)
|
||||||
|
|
||||||
|
def view_organization(request, org_id):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
scheme = 'https' if request.is_secure() else 'http'
|
||||||
|
hostname = request.get_host()
|
||||||
|
|
||||||
|
if request.session["groupme_id"] == org.owner:
|
||||||
|
org_channel_info = pickle.loads(bytes.fromhex(org.channel_info))
|
||||||
|
org_trusted_users = pickle.loads(bytes.fromhex(org.trusted_users))
|
||||||
|
return render(request, "view_organization.html", {
|
||||||
|
"request": request,
|
||||||
|
"org": org,
|
||||||
|
"hostname": hostname,
|
||||||
|
"scheme": scheme,
|
||||||
|
"channels": org_channel_info,
|
||||||
|
"users": org_trusted_users,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
pass # blow up
|
||||||
|
|
||||||
|
def qr_code(request, org_id):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
scheme = 'https' if request.is_secure() else 'http'
|
||||||
|
hostname = request.get_host()
|
||||||
|
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
box_size=10,
|
||||||
|
border=4,
|
||||||
|
)
|
||||||
|
qr.add_data("{}://{}{}".format(
|
||||||
|
scheme,
|
||||||
|
hostname,
|
||||||
|
reverse('add_channel_to_organization', args=[org.id]),
|
||||||
|
))
|
||||||
|
qr.make(fit=True)
|
||||||
|
img = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
|
||||||
|
resp = HttpResponse(
|
||||||
|
headers={
|
||||||
|
"Content-Type": "image/png",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
img.save(resp)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
def add_channel_to_organization(request, org_id, trusted=False):
|
||||||
|
if not request.session["groupme_access_token"]:
|
||||||
|
request.session["needs_redirect"] = "add_channel_to_organization"
|
||||||
|
return redirect(reverse("login"))
|
||||||
|
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
user_group_data = pickle.loads(
|
||||||
|
bytes.fromhex(
|
||||||
|
request.session["groupme_groups_pickle"]
|
||||||
|
))["response"]
|
||||||
|
our_id = request.session["groupme_id"]
|
||||||
|
groups_we_admin = []
|
||||||
|
|
||||||
|
for group in user_group_data:
|
||||||
|
for user in group["members"]: # check if we're an admin
|
||||||
|
if user["user_id"] == our_id:
|
||||||
|
if "admin" in user["roles"]: # we are an admin
|
||||||
|
groups_we_admin.append(group)
|
||||||
|
|
||||||
|
return render(request, "add_channel_to_organization.html", {
|
||||||
|
"groups": groups_we_admin,
|
||||||
|
"org": org,
|
||||||
|
"request": request,
|
||||||
|
"trusted": trusted,
|
||||||
|
})
|
||||||
|
|
||||||
|
elif request.method == "POST":
|
||||||
|
group_name_mapping = pickle.loads(
|
||||||
|
bytes.fromhex(
|
||||||
|
request.session["group_name_mapping"]
|
||||||
|
))
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
groups = [i[2:] for i in request.POST.keys() if i.startswith("g_")]
|
||||||
|
org_group_info = pickle.loads(bytes.fromhex(org.channel_info))
|
||||||
|
|
||||||
|
if not trusted:
|
||||||
|
for group in org_group_info:
|
||||||
|
if group["gid"] in groups and group["role"] != "trusted":
|
||||||
|
# prune the groups
|
||||||
|
groups.remove(group["gid"])
|
||||||
|
else:
|
||||||
|
for index, group in enumerate(org_group_info):
|
||||||
|
if group["gid"] in groups:
|
||||||
|
org_group_info[index]["role"] = "trusted"
|
||||||
|
|
||||||
|
to_add = []
|
||||||
|
# these are the unique groups
|
||||||
|
for group in groups:
|
||||||
|
if not trusted:
|
||||||
|
data = {
|
||||||
|
"bot": {
|
||||||
|
"name": bot_name,
|
||||||
|
"group_id": group,
|
||||||
|
"active": True,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
data = {
|
||||||
|
"bot": {
|
||||||
|
"name": bot_name,
|
||||||
|
"group_id": group,
|
||||||
|
"active": True,
|
||||||
|
"callback_url": "https://marching.beepboop.systems/groupme/organization/{}/callback/{}".format(
|
||||||
|
org.id,
|
||||||
|
str(random.randint(1, 100000))
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = requests.post(
|
||||||
|
"https://api.groupme.com/v3/bots?token={}".format(
|
||||||
|
request.session["groupme_access_token"]
|
||||||
|
),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers={"Content-Type": "application/json"},
|
||||||
|
)
|
||||||
|
|
||||||
|
print(r)
|
||||||
|
data = r.json()
|
||||||
|
|
||||||
|
if not trusted:
|
||||||
|
to_add.append({
|
||||||
|
"gid": group,
|
||||||
|
"bot_id": data["response"]["bot"]["bot_id"],
|
||||||
|
"name": group_name_mapping[group],
|
||||||
|
"role": "following",
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
to_add.append({
|
||||||
|
"gid": group,
|
||||||
|
"bot_id": data["response"]["bot"]["bot_id"],
|
||||||
|
"name": group_name_mapping[group],
|
||||||
|
"role": "trusted",
|
||||||
|
})
|
||||||
|
|
||||||
|
org_group_info += to_add
|
||||||
|
org.channel_info = pickle.dumps(org_group_info).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
if request.session["groupme_id"] == org.owner:
|
||||||
|
return redirect(reverse('view_organization', args=[org.id]))
|
||||||
|
else:
|
||||||
|
return render(request, "group_add_succeed.html", {
|
||||||
|
"request": request
|
||||||
|
})
|
||||||
|
|
||||||
|
def add_trusted_channel(request, org_id):
|
||||||
|
return add_channel_to_organization(request, org_id, trusted=True)
|
||||||
|
|
||||||
|
def delete_channel(request, org_id, chan):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
org_group_info = pickle.loads(bytes.fromhex(org.channel_info))
|
||||||
|
|
||||||
|
print(org_group_info)
|
||||||
|
|
||||||
|
for index, channel in enumerate(org_group_info):
|
||||||
|
print(index, channel)
|
||||||
|
if channel["gid"] == chan:
|
||||||
|
org_group_info.pop(index)
|
||||||
|
break
|
||||||
|
|
||||||
|
org.channel_info = pickle.dumps(org_group_info).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
return redirect(reverse('view_organization', args=[org.id]))
|
||||||
|
|
||||||
|
def demote_channel(request, org_id, chan):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
org_group_info = pickle.loads(bytes.fromhex(org.channel_info))
|
||||||
|
|
||||||
|
for index, channel in enumerate(org_group_info):
|
||||||
|
print(index, channel)
|
||||||
|
if channel["gid"] == chan:
|
||||||
|
org_group_info[index]["role"] = "following"
|
||||||
|
break
|
||||||
|
|
||||||
|
org.channel_info = pickle.dumps(org_group_info).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
return redirect(reverse('view_organization', args=[org.id]))
|
||||||
|
|
||||||
|
def add_trusted_user(request, org_id):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
org_trusted_users = pickle.loads(bytes.fromhex(org.trusted_users))
|
||||||
|
user_group_data = pickle.loads(
|
||||||
|
bytes.fromhex(
|
||||||
|
request.session["groupme_groups_pickle"]
|
||||||
|
))
|
||||||
|
user_group_data = user_group_data["response"]
|
||||||
|
|
||||||
|
rendered_users = []
|
||||||
|
for group in user_group_data:
|
||||||
|
our_id = request.session["groupme_id"]
|
||||||
|
for user in group["members"]: # check if we're an admin
|
||||||
|
if user["user_id"] == our_id:
|
||||||
|
if "admin" in user["roles"]: # we are an admin
|
||||||
|
for user in group["members"]:
|
||||||
|
rendered_users.append({
|
||||||
|
"name": user["name"],
|
||||||
|
"id": user["user_id"]
|
||||||
|
})
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
# remove duplicate users
|
||||||
|
# https://stackoverflow.com/questions/9427163/remove-duplicate-dict-in-list-in-python
|
||||||
|
rendered_users = [i for n, i in enumerate(rendered_users) if i not in rendered_users[n + 1:]]
|
||||||
|
|
||||||
|
if request.method == "GET":
|
||||||
|
return render(request, "add_user.html", {
|
||||||
|
"request": request,
|
||||||
|
"users": rendered_users,
|
||||||
|
"org": org,
|
||||||
|
})
|
||||||
|
|
||||||
|
elif request.method == "POST":
|
||||||
|
users = [i[2:] for i in request.POST.keys() if i.startswith("u_")]
|
||||||
|
|
||||||
|
user_name_mappings = pickle.loads(bytes.fromhex(request.session["user_name_mapping"]))
|
||||||
|
for user in users:
|
||||||
|
try:
|
||||||
|
username = user_name_mappings[user]
|
||||||
|
except KeyError:
|
||||||
|
username = "unknown"
|
||||||
|
user_data = { "uid": user, "role": "trusted", "name": username, "number_messages": 0 }
|
||||||
|
org_trusted_users.append(user_data)
|
||||||
|
|
||||||
|
org.trusted_users = pickle.dumps(org_trusted_users).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
return redirect(reverse('view_organization', args=[org.id]))
|
||||||
|
|
||||||
|
def remove_user(request, org_id, user):
|
||||||
|
org = Organization.objects.filter(id__exact=org_id)[0]
|
||||||
|
org_trusted_users = pickle.loads(bytes.fromhex(org.trusted_users))
|
||||||
|
|
||||||
|
for index, tuser in enumerate(org_trusted_users):
|
||||||
|
print(index, tuser)
|
||||||
|
if tuser["uid"] == user:
|
||||||
|
|
||||||
|
org_trusted_users.pop(index)
|
||||||
|
break
|
||||||
|
|
||||||
|
org.trusted_users = pickle.dumps(org_trusted_users).hex()
|
||||||
|
org.save()
|
||||||
|
|
||||||
|
return redirect(reverse('view_organization', args=[org.id]))
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tfb.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
|
@ -25,8 +25,8 @@ SECRET_KEY = 'django-insecure-%4qs)a3%&$ykwuu5d$0%^=8u1)b=t=t+vgo!rxvrq1z9+7)w1z
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["10.100.0.2"]
|
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
|
||||||
CSRF_TRUSTED_ORIGINS = ["https://tfb.beepboop.systems"]
|
CSRF_TRUSTED_ORIGINS = ["http://127.0.0.1:8000"]
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
Loading…
Reference in New Issue