mirror of
https://github.com/onionshare/onionshare.git
synced 2025-01-27 23:07:14 -05:00
Create web UI and socket code for the chat interface
This commit is contained in:
parent
a217e54d28
commit
2c938fd777
@ -63,6 +63,9 @@ def main(cwd=None):
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--website", action="store_true", dest="website", help="Publish website"
|
"--website", action="store_true", dest="website", help="Publish website"
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--chat", action="store_true", dest="chat", help="Start chat server"
|
||||||
|
)
|
||||||
# Tor connection-related args
|
# Tor connection-related args
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--local-only",
|
"--local-only",
|
||||||
@ -172,6 +175,7 @@ def main(cwd=None):
|
|||||||
|
|
||||||
receive = bool(args.receive)
|
receive = bool(args.receive)
|
||||||
website = bool(args.website)
|
website = bool(args.website)
|
||||||
|
chat = bool(args.chat)
|
||||||
local_only = bool(args.local_only)
|
local_only = bool(args.local_only)
|
||||||
connect_timeout = int(args.connect_timeout)
|
connect_timeout = int(args.connect_timeout)
|
||||||
config_filename = args.config
|
config_filename = args.config
|
||||||
@ -190,6 +194,8 @@ def main(cwd=None):
|
|||||||
mode = "receive"
|
mode = "receive"
|
||||||
elif website:
|
elif website:
|
||||||
mode = "website"
|
mode = "website"
|
||||||
|
elif chat:
|
||||||
|
mode = "chat"
|
||||||
else:
|
else:
|
||||||
mode = "share"
|
mode = "share"
|
||||||
|
|
||||||
|
@ -215,6 +215,16 @@ class Common:
|
|||||||
r = random.SystemRandom()
|
r = random.SystemRandom()
|
||||||
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||||
|
|
||||||
|
def build_username(self, word_count=2):
|
||||||
|
"""
|
||||||
|
Returns a random string made of words from the wordlist, such as "deter-trig".
|
||||||
|
"""
|
||||||
|
with open(self.get_resource_path("wordlist.txt")) as f:
|
||||||
|
wordlist = f.read().split()
|
||||||
|
|
||||||
|
r = random.SystemRandom()
|
||||||
|
return "-".join(r.choice(wordlist) for _ in range(word_count))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def random_string(num_bytes, output_len=None):
|
def random_string(num_bytes, output_len=None):
|
||||||
"""
|
"""
|
||||||
|
@ -49,6 +49,7 @@ class ModeSettings:
|
|||||||
"share": {"autostop_sharing": True, "filenames": []},
|
"share": {"autostop_sharing": True, "filenames": []},
|
||||||
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
||||||
"website": {"disable_csp": False, "filenames": []},
|
"website": {"disable_csp": False, "filenames": []},
|
||||||
|
"chat": {"room": "default"},
|
||||||
}
|
}
|
||||||
self._settings = {}
|
self._settings = {}
|
||||||
|
|
||||||
|
75
onionshare/web/chat_mode.py
Normal file
75
onionshare/web/chat_mode.py
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import json
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import Request, request, render_template, make_response, flash, redirect, session
|
||||||
|
from werkzeug.utils import secure_filename
|
||||||
|
from flask_socketio import emit, join_room, leave_room
|
||||||
|
|
||||||
|
from .. import strings
|
||||||
|
|
||||||
|
|
||||||
|
class ChatModeWeb:
|
||||||
|
"""
|
||||||
|
All of the web logic for chat mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, common, web):
|
||||||
|
self.common = common
|
||||||
|
self.common.log("ChatModeWeb", "__init__")
|
||||||
|
|
||||||
|
self.web = web
|
||||||
|
|
||||||
|
self.can_upload = True
|
||||||
|
self.uploads_in_progress = []
|
||||||
|
|
||||||
|
# This tracks the history id
|
||||||
|
self.cur_history_id = 0
|
||||||
|
|
||||||
|
self.define_routes()
|
||||||
|
|
||||||
|
def define_routes(self):
|
||||||
|
"""
|
||||||
|
The web app routes for chatting
|
||||||
|
"""
|
||||||
|
|
||||||
|
@self.web.app.route("/")
|
||||||
|
def index():
|
||||||
|
history_id = self.cur_history_id
|
||||||
|
self.cur_history_id += 1
|
||||||
|
self.web.add_request(
|
||||||
|
self.web.REQUEST_INDIVIDUAL_FILE_STARTED,
|
||||||
|
request.path,
|
||||||
|
{"id": history_id, "status_code": 200},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||||
|
r = make_response(
|
||||||
|
render_template(
|
||||||
|
"chat.html", static_url_path=self.web.static_url_path
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self.web.add_security_headers(r)
|
||||||
|
|
||||||
|
@self.web.socketio.on("joined", namespace="/chat")
|
||||||
|
def joined(message):
|
||||||
|
"""Sent by clients when they enter a room.
|
||||||
|
A status message is broadcast to all people in the room."""
|
||||||
|
session["name"] = self.common.build_username()
|
||||||
|
session["room"] = self.web.settings.default_settings["chat"]["room"]
|
||||||
|
join_room(session.get("room"))
|
||||||
|
emit(
|
||||||
|
"status",
|
||||||
|
{"msg": session.get("name") + " has entered the room."},
|
||||||
|
room=session.get("room")
|
||||||
|
)
|
||||||
|
|
||||||
|
@self.web.socketio.on("text", namespace="/chat")
|
||||||
|
def text(message):
|
||||||
|
"""Sent by a client when the user entered a new message.
|
||||||
|
The message is sent to all people in the room."""
|
||||||
|
emit(
|
||||||
|
"message",
|
||||||
|
{"msg": session.get("name") + ": " + message["msg"]},
|
||||||
|
room=session.get("room")
|
||||||
|
)
|
@ -20,12 +20,14 @@ from flask import (
|
|||||||
__version__ as flask_version,
|
__version__ as flask_version,
|
||||||
)
|
)
|
||||||
from flask_httpauth import HTTPBasicAuth
|
from flask_httpauth import HTTPBasicAuth
|
||||||
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
from .. import strings
|
from .. import strings
|
||||||
|
|
||||||
from .share_mode import ShareModeWeb
|
from .share_mode import ShareModeWeb
|
||||||
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
||||||
from .website_mode import WebsiteModeWeb
|
from .website_mode import WebsiteModeWeb
|
||||||
|
from .chat_mode import ChatModeWeb
|
||||||
|
|
||||||
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||||
# are not applicable to OnionShare
|
# are not applicable to OnionShare
|
||||||
@ -134,12 +136,17 @@ class Web:
|
|||||||
self.share_mode = None
|
self.share_mode = None
|
||||||
self.receive_mode = None
|
self.receive_mode = None
|
||||||
self.website_mode = None
|
self.website_mode = None
|
||||||
|
self.chat_mode = None
|
||||||
if self.mode == "share":
|
if self.mode == "share":
|
||||||
self.share_mode = ShareModeWeb(self.common, self)
|
self.share_mode = ShareModeWeb(self.common, self)
|
||||||
elif self.mode == "receive":
|
elif self.mode == "receive":
|
||||||
self.receive_mode = ReceiveModeWeb(self.common, self)
|
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||||
elif self.mode == "website":
|
elif self.mode == "website":
|
||||||
self.website_mode = WebsiteModeWeb(self.common, self)
|
self.website_mode = WebsiteModeWeb(self.common, self)
|
||||||
|
elif self.mode == "chat":
|
||||||
|
self.socketio = SocketIO()
|
||||||
|
self.socketio.init_app(self.app)
|
||||||
|
self.chat_mode = ChatModeWeb(self.common, self)
|
||||||
|
|
||||||
def get_mode(self):
|
def get_mode(self):
|
||||||
if self.mode == "share":
|
if self.mode == "share":
|
||||||
@ -148,6 +155,8 @@ class Web:
|
|||||||
return self.receive_mode
|
return self.receive_mode
|
||||||
elif self.mode == "website":
|
elif self.mode == "website":
|
||||||
return self.website_mode
|
return self.website_mode
|
||||||
|
elif self.mode == "chat":
|
||||||
|
return self.chat_mode
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -366,6 +375,9 @@ class Web:
|
|||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
|
|
||||||
self.running = True
|
self.running = True
|
||||||
|
if self.mode == "chat":
|
||||||
|
self.socketio.run(self.app, host=host, port=port)
|
||||||
|
else:
|
||||||
self.app.run(host=host, port=port, threaded=True)
|
self.app.run(host=host, port=port, threaded=True)
|
||||||
|
|
||||||
def stop(self, port):
|
def stop(self, port):
|
||||||
|
31
share/static/js/chat.js
Normal file
31
share/static/js/chat.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
$(function(){
|
||||||
|
var socket;
|
||||||
|
$(document).ready(function(){
|
||||||
|
socket = io.connect('http://' + document.domain + ':' + location.port + '/chat');
|
||||||
|
socket.on('connect', function() {
|
||||||
|
socket.emit('joined', {});
|
||||||
|
});
|
||||||
|
socket.on('status', function(data) {
|
||||||
|
console.log("received")
|
||||||
|
$('#chat').append('<p><small><i>' + data.msg + '</i></small></p>');
|
||||||
|
$('#chat').scrollTop($('#chat')[0].scrollHeight);
|
||||||
|
});
|
||||||
|
socket.on('message', function(data) {
|
||||||
|
$('#chat').append('<p>' + data.msg + '</p>');
|
||||||
|
$('#chat').scrollTop($('#chat')[0].scrollHeight);
|
||||||
|
});
|
||||||
|
$('#new-message').on('keypress', function(e) {
|
||||||
|
var code = e.keyCode || e.which;
|
||||||
|
if (code == 13) {
|
||||||
|
emitMessage(socket);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('#send-button').on('click', emitMessage);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function emitMessage(socket) {
|
||||||
|
text = $('#new-message').val();
|
||||||
|
$('#new-message').val('');
|
||||||
|
socket.emit('text', {msg: text});
|
||||||
|
}
|
3
share/static/js/socket.io.min.js
vendored
Normal file
3
share/static/js/socket.io.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
47
share/templates/chat.html
Normal file
47
share/templates/chat.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>OnionShare</title>
|
||||||
|
<link href="{{ static_url_path }}/img/favicon.ico" rel="icon" type="image/x-icon">
|
||||||
|
<link rel="stylesheet" rel="subresource" type="text/css" href="{{ static_url_path }}/css/style.css" media="all">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header class="clearfix">
|
||||||
|
<img class="logo" src="{{ static_url_path }}/img/logo.png" title="OnionShare">
|
||||||
|
<h1>OnionShare</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<ul id="flashes" class="flashes">
|
||||||
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||||
|
{% if messages %}
|
||||||
|
{% for category, message in messages %}
|
||||||
|
<li class="{{ category }}">{{ message }}</li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="chat-container">
|
||||||
|
<div class="chat-users">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="chat-wrapper">
|
||||||
|
<p class="chat-header">Chat Messages</p>
|
||||||
|
|
||||||
|
<div id="chat"></div>
|
||||||
|
|
||||||
|
<div class="chat-form">
|
||||||
|
<p><input type="text" id="new-message" name="new-message" placeholder="Type your message"/></p>
|
||||||
|
<p><button type="button" id="send-button" class="button">Send Message</button></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="{{ static_url_path }}/js/jquery-3.4.0.min.js"></script>
|
||||||
|
<script src="{{ static_url_path }}/js/socket.io.min.js"></script>
|
||||||
|
<script async src="{{ static_url_path }}/js/chat.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
x
Reference in New Issue
Block a user