mirror of
https://github.com/onionshare/onionshare.git
synced 2024-10-01 01:35:40 -04:00
Merge branch 'chat' of https://github.com/SaptakS/onionshare into SaptakS-chat
This commit is contained in:
commit
8e1b34ce13
@ -87,6 +87,9 @@ def main(cwd=None):
|
||||
parser.add_argument(
|
||||
"--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
|
||||
parser.add_argument(
|
||||
"--local-only",
|
||||
@ -196,6 +199,7 @@ def main(cwd=None):
|
||||
|
||||
receive = bool(args.receive)
|
||||
website = bool(args.website)
|
||||
chat = bool(args.chat)
|
||||
local_only = bool(args.local_only)
|
||||
connect_timeout = int(args.connect_timeout)
|
||||
config_filename = args.config
|
||||
@ -214,6 +218,8 @@ def main(cwd=None):
|
||||
mode = "receive"
|
||||
elif website:
|
||||
mode = "website"
|
||||
elif chat:
|
||||
mode = "chat"
|
||||
else:
|
||||
mode = "share"
|
||||
|
||||
|
@ -221,6 +221,16 @@ class Common:
|
||||
r = random.SystemRandom()
|
||||
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
|
||||
def random_string(num_bytes, output_len=None):
|
||||
"""
|
||||
|
@ -49,6 +49,7 @@ class ModeSettings:
|
||||
"share": {"autostop_sharing": True, "filenames": []},
|
||||
"receive": {"data_dir": self.build_default_receive_data_dir()},
|
||||
"website": {"disable_csp": False, "filenames": []},
|
||||
"chat": {"room": "default"},
|
||||
}
|
||||
self._settings = {}
|
||||
|
||||
|
141
onionshare/web/chat_mode.py
Normal file
141
onionshare/web/chat_mode.py
Normal file
@ -0,0 +1,141 @@
|
||||
from flask import (
|
||||
Request,
|
||||
request,
|
||||
render_template,
|
||||
make_response,
|
||||
jsonify,
|
||||
redirect,
|
||||
session,
|
||||
)
|
||||
from werkzeug.utils import secure_filename
|
||||
from flask_socketio import emit, join_room, leave_room
|
||||
|
||||
|
||||
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
|
||||
|
||||
# This tracks users in the room
|
||||
self.connected_users = []
|
||||
|
||||
# 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
|
||||
session["name"] = (
|
||||
session.get("name")
|
||||
if session.get("name")
|
||||
else self.common.build_username()
|
||||
)
|
||||
session["room"] = self.web.settings.default_settings["chat"]["room"]
|
||||
self.web.add_request(
|
||||
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,
|
||||
username=session.get("name"),
|
||||
)
|
||||
)
|
||||
return self.web.add_security_headers(r)
|
||||
|
||||
@self.web.app.route("/update-session-username", methods=["POST"])
|
||||
def update_session_username():
|
||||
history_id = self.cur_history_id
|
||||
data = request.get_json()
|
||||
session["name"] = data.get("username", session.get("name"))
|
||||
self.web.add_request(
|
||||
request.path, {"id": history_id, "status_code": 200},
|
||||
)
|
||||
|
||||
self.web.add_request(self.web.REQUEST_LOAD, request.path)
|
||||
r = make_response(
|
||||
jsonify(
|
||||
username=session.get("name"),
|
||||
success=True,
|
||||
)
|
||||
)
|
||||
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."""
|
||||
self.connected_users.append(session.get("name"))
|
||||
join_room(session.get("room"))
|
||||
emit(
|
||||
"status",
|
||||
{
|
||||
"msg": "{} has joined.".format(session.get("name")),
|
||||
"connected_users": self.connected_users,
|
||||
"user": session.get("name"),
|
||||
},
|
||||
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": "{}: {}".format(session.get("name"), message["msg"])},
|
||||
room=session.get("room"),
|
||||
)
|
||||
|
||||
@self.web.socketio.on("update_username", namespace="/chat")
|
||||
def update_username(message):
|
||||
"""Sent by a client when the user updates their username.
|
||||
The message is sent to all people in the room."""
|
||||
current_name = session.get("name")
|
||||
session["name"] = message["username"]
|
||||
self.connected_users[
|
||||
self.connected_users.index(current_name)
|
||||
] = session.get("name")
|
||||
emit(
|
||||
"status",
|
||||
{
|
||||
"msg": "{} has updated their username to: {}".format(
|
||||
current_name, session.get("name")
|
||||
),
|
||||
"connected_users": self.connected_users,
|
||||
"old_name": current_name,
|
||||
"new_name": session.get("name"),
|
||||
},
|
||||
room=session.get("room"),
|
||||
)
|
||||
|
||||
@self.web.socketio.on("disconnect", namespace="/chat")
|
||||
def disconnect():
|
||||
"""Sent by clients when they disconnect from a room.
|
||||
A status message is broadcast to all people in the room."""
|
||||
self.connected_users.remove(session.get("name"))
|
||||
leave_room(session.get("room"))
|
||||
emit(
|
||||
"status",
|
||||
{
|
||||
"msg": "{} has left the room.".format(session.get("name")),
|
||||
"connected_users": self.connected_users,
|
||||
},
|
||||
room=session.get("room"),
|
||||
)
|
@ -20,12 +20,14 @@ from flask import (
|
||||
__version__ as flask_version,
|
||||
)
|
||||
from flask_httpauth import HTTPBasicAuth
|
||||
from flask_socketio import SocketIO
|
||||
|
||||
from .. import strings
|
||||
|
||||
from .share_mode import ShareModeWeb
|
||||
from .receive_mode import ReceiveModeWeb, ReceiveModeWSGIMiddleware, ReceiveModeRequest
|
||||
from .website_mode import WebsiteModeWeb
|
||||
from .chat_mode import ChatModeWeb
|
||||
|
||||
# Stub out flask's show_server_banner function, to avoiding showing warnings that
|
||||
# are not applicable to OnionShare
|
||||
@ -134,12 +136,17 @@ class Web:
|
||||
self.share_mode = None
|
||||
self.receive_mode = None
|
||||
self.website_mode = None
|
||||
self.chat_mode = None
|
||||
if self.mode == "share":
|
||||
self.share_mode = ShareModeWeb(self.common, self)
|
||||
elif self.mode == "receive":
|
||||
self.receive_mode = ReceiveModeWeb(self.common, self)
|
||||
elif self.mode == "website":
|
||||
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):
|
||||
if self.mode == "share":
|
||||
@ -148,6 +155,8 @@ class Web:
|
||||
return self.receive_mode
|
||||
elif self.mode == "website":
|
||||
return self.website_mode
|
||||
elif self.mode == "chat":
|
||||
return self.chat_mode
|
||||
else:
|
||||
return None
|
||||
|
||||
@ -366,7 +375,10 @@ class Web:
|
||||
host = "127.0.0.1"
|
||||
|
||||
self.running = True
|
||||
self.app.run(host=host, port=port, threaded=True)
|
||||
if self.mode == "chat":
|
||||
self.socketio.run(self.app, host=host, port=port)
|
||||
else:
|
||||
self.app.run(host=host, port=port, threaded=True)
|
||||
|
||||
def stop(self, port):
|
||||
"""
|
||||
|
@ -31,6 +31,7 @@ class GuiCommon:
|
||||
MODE_SHARE = "share"
|
||||
MODE_RECEIVE = "receive"
|
||||
MODE_WEBSITE = "website"
|
||||
MODE_CHAT = "chat"
|
||||
|
||||
def __init__(self, common, qtapp, local_only):
|
||||
self.common = common
|
||||
|
133
onionshare_gui/tab/mode/chat_mode/__init__.py
Normal file
133
onionshare_gui/tab/mode/chat_mode/__init__.py
Normal file
@ -0,0 +1,133 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
OnionShare | https://onionshare.org/
|
||||
|
||||
Copyright (C) 2014-2018 Micah Lee <micah@micahflee.com>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from onionshare import strings
|
||||
from onionshare.onion import *
|
||||
from onionshare.common import Common
|
||||
from onionshare.web import Web
|
||||
|
||||
from .. import Mode
|
||||
from ....widgets import MinimumWidthWidget
|
||||
|
||||
|
||||
class ChatMode(Mode):
|
||||
"""
|
||||
Parts of the main window UI for sharing files.
|
||||
"""
|
||||
|
||||
success = QtCore.pyqtSignal()
|
||||
error = QtCore.pyqtSignal(str)
|
||||
|
||||
def init(self):
|
||||
"""
|
||||
Custom initialization for ChatMode.
|
||||
"""
|
||||
# Create the Web object
|
||||
self.web = Web(self.common, True, self.settings, "chat")
|
||||
|
||||
# Header
|
||||
self.header_label.setText(strings._("gui_new_tab_chat_button"))
|
||||
|
||||
# Server status
|
||||
self.server_status.set_mode("chat")
|
||||
self.server_status.server_started_finished.connect(self.update_primary_action)
|
||||
self.server_status.server_stopped.connect(self.update_primary_action)
|
||||
self.server_status.server_canceled.connect(self.update_primary_action)
|
||||
# Tell server_status about web, then update
|
||||
self.server_status.web = self.web
|
||||
self.server_status.update()
|
||||
|
||||
# Top bar
|
||||
top_bar_layout = QtWidgets.QHBoxLayout()
|
||||
top_bar_layout.addStretch()
|
||||
|
||||
# Main layout
|
||||
self.main_layout = QtWidgets.QVBoxLayout()
|
||||
self.main_layout.addLayout(top_bar_layout)
|
||||
self.main_layout.addWidget(self.primary_action)
|
||||
self.main_layout.addStretch()
|
||||
self.main_layout.addWidget(MinimumWidthWidget(700))
|
||||
|
||||
# Column layout
|
||||
self.column_layout = QtWidgets.QHBoxLayout()
|
||||
self.column_layout.addLayout(self.main_layout)
|
||||
|
||||
# Wrapper layout
|
||||
self.wrapper_layout = QtWidgets.QVBoxLayout()
|
||||
self.wrapper_layout.addWidget(self.header_label)
|
||||
self.wrapper_layout.addLayout(self.column_layout)
|
||||
self.setLayout(self.wrapper_layout)
|
||||
|
||||
def get_stop_server_autostop_timer_text(self):
|
||||
"""
|
||||
Return the string to put on the stop server button, if there's an auto-stop timer
|
||||
"""
|
||||
return strings._("gui_share_stop_server_autostop_timer")
|
||||
|
||||
def autostop_timer_finished_should_stop_server(self):
|
||||
"""
|
||||
The auto-stop timer expired, should we stop the server? Returns a bool
|
||||
"""
|
||||
|
||||
self.server_status.stop_server()
|
||||
self.server_status_label.setText(strings._("close_on_autostop_timer"))
|
||||
return True
|
||||
|
||||
def start_server_custom(self):
|
||||
"""
|
||||
Starting the server.
|
||||
"""
|
||||
# Reset web counters
|
||||
self.web.chat_mode.cur_history_id = 0
|
||||
self.web.reset_invalid_passwords()
|
||||
|
||||
def start_server_step2_custom(self):
|
||||
"""
|
||||
Step 2 in starting the server. Zipping up files.
|
||||
"""
|
||||
# Continue
|
||||
self.starting_server_step3.emit()
|
||||
self.start_server_finished.emit()
|
||||
|
||||
def cancel_server_custom(self):
|
||||
"""
|
||||
Log that the server has been cancelled
|
||||
"""
|
||||
self.common.log("ChatMode", "cancel_server")
|
||||
|
||||
def handle_tor_broke_custom(self):
|
||||
"""
|
||||
Connection to Tor broke.
|
||||
"""
|
||||
self.primary_action.hide()
|
||||
|
||||
def on_reload_settings(self):
|
||||
"""
|
||||
We should be ok to re-enable the 'Start Receive Mode' button now.
|
||||
"""
|
||||
self.primary_action.show()
|
||||
|
||||
def update_primary_action(self):
|
||||
self.common.log("ChatMode", "update_primary_action")
|
@ -270,6 +270,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.server_button.setText(strings._("gui_share_start_server"))
|
||||
elif self.mode == self.common.gui.MODE_WEBSITE:
|
||||
self.server_button.setText(strings._("gui_share_start_server"))
|
||||
elif self.mode == self.common.gui.MODE_CHAT:
|
||||
self.server_button.setText(strings._("gui_chat_start_server"))
|
||||
else:
|
||||
self.server_button.setText(strings._("gui_receive_start_server"))
|
||||
self.server_button.setToolTip("")
|
||||
@ -282,6 +284,8 @@ class ServerStatus(QtWidgets.QWidget):
|
||||
self.server_button.setText(strings._("gui_share_stop_server"))
|
||||
elif self.mode == self.common.gui.MODE_WEBSITE:
|
||||
self.server_button.setText(strings._("gui_share_stop_server"))
|
||||
elif self.mode == self.common.gui.MODE_CHAT:
|
||||
self.server_button.setText(strings._("gui_chat_stop_server"))
|
||||
else:
|
||||
self.server_button.setText(strings._("gui_receive_stop_server"))
|
||||
elif self.status == self.STATUS_WORKING:
|
||||
|
@ -28,6 +28,7 @@ from onionshare.mode_settings import ModeSettings
|
||||
from .mode.share_mode import ShareMode
|
||||
from .mode.receive_mode import ReceiveMode
|
||||
from .mode.website_mode import WebsiteMode
|
||||
from .mode.chat_mode import ChatMode
|
||||
|
||||
from .server_status import ServerStatus
|
||||
|
||||
@ -93,6 +94,16 @@ class Tab(QtWidgets.QWidget):
|
||||
)
|
||||
website_description.setWordWrap(True)
|
||||
|
||||
self.chat_button = QtWidgets.QPushButton(
|
||||
strings._("gui_new_tab_chat_button")
|
||||
)
|
||||
self.chat_button.setStyleSheet(self.common.gui.css["mode_new_tab_button"])
|
||||
self.chat_button.clicked.connect(self.chat_mode_clicked)
|
||||
chat_description = QtWidgets.QLabel(
|
||||
strings._("gui_new_tab_chat_description")
|
||||
)
|
||||
chat_description.setWordWrap(True)
|
||||
|
||||
new_tab_layout = QtWidgets.QVBoxLayout()
|
||||
new_tab_layout.addStretch(1)
|
||||
new_tab_layout.addWidget(self.share_button)
|
||||
@ -103,6 +114,9 @@ class Tab(QtWidgets.QWidget):
|
||||
new_tab_layout.addSpacing(50)
|
||||
new_tab_layout.addWidget(self.website_button)
|
||||
new_tab_layout.addWidget(website_description)
|
||||
new_tab_layout.addSpacing(50)
|
||||
new_tab_layout.addWidget(self.chat_button)
|
||||
new_tab_layout.addWidget(chat_description)
|
||||
new_tab_layout.addStretch(3)
|
||||
|
||||
new_tab_inner = QtWidgets.QWidget()
|
||||
@ -278,6 +292,43 @@ class Tab(QtWidgets.QWidget):
|
||||
self.update_server_status_indicator()
|
||||
self.timer.start(500)
|
||||
|
||||
def chat_mode_clicked(self):
|
||||
self.common.log("Tab", "chat_mode_clicked")
|
||||
self.mode = self.common.gui.MODE_CHAT
|
||||
self.new_tab.hide()
|
||||
|
||||
self.chat_mode = ChatMode(self)
|
||||
self.chat_mode.change_persistent.connect(self.change_persistent)
|
||||
|
||||
self.layout.addWidget(self.chat_mode)
|
||||
self.chat_mode.show()
|
||||
|
||||
self.chat_mode.init()
|
||||
self.chat_mode.server_status.server_started.connect(
|
||||
self.update_server_status_indicator
|
||||
)
|
||||
self.chat_mode.server_status.server_stopped.connect(
|
||||
self.update_server_status_indicator
|
||||
)
|
||||
self.chat_mode.start_server_finished.connect(
|
||||
self.update_server_status_indicator
|
||||
)
|
||||
self.chat_mode.stop_server_finished.connect(
|
||||
self.update_server_status_indicator
|
||||
)
|
||||
self.chat_mode.stop_server_finished.connect(self.stop_server_finished)
|
||||
self.chat_mode.start_server_finished.connect(self.clear_message)
|
||||
self.chat_mode.server_status.button_clicked.connect(self.clear_message)
|
||||
self.chat_mode.server_status.url_copied.connect(self.copy_url)
|
||||
self.chat_mode.server_status.hidservauth_copied.connect(
|
||||
self.copy_hidservauth
|
||||
)
|
||||
|
||||
self.change_title.emit(self.tab_id, strings._("gui_new_tab_chat_button"))
|
||||
|
||||
self.update_server_status_indicator()
|
||||
self.timer.start(500)
|
||||
|
||||
def update_server_status_indicator(self):
|
||||
# Set the status image
|
||||
if self.mode == self.common.gui.MODE_SHARE:
|
||||
@ -486,6 +537,8 @@ class Tab(QtWidgets.QWidget):
|
||||
return self.share_mode
|
||||
elif self.mode == self.common.gui.MODE_RECEIVE:
|
||||
return self.receive_mode
|
||||
elif self.mode == self.common.gui.MODE_CHAT:
|
||||
return self.chat_mode
|
||||
else:
|
||||
return self.website_mode
|
||||
else:
|
||||
|
219
poetry.lock
generated
219
poetry.lock
generated
@ -62,13 +62,33 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "0.4.3"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python 2.7 backport of the \"dis\" module from Python 3.5+"
|
||||
marker = "sys_platform == \"darwin\""
|
||||
name = "dis3"
|
||||
category = "main"
|
||||
description = "DNS toolkit"
|
||||
name = "dnspython"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
version = "2.0.0"
|
||||
|
||||
[package.extras]
|
||||
curio = ["curio (>=1.2)", "sniffio (>=1.1)"]
|
||||
dnssec = ["cryptography (>=2.6)"]
|
||||
doh = ["requests", "requests-toolbelt"]
|
||||
idna = ["idna (>=2.1)"]
|
||||
trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Highly concurrent networking library"
|
||||
name = "eventlet"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.1.3"
|
||||
version = "0.25.2"
|
||||
|
||||
[package.dependencies]
|
||||
dnspython = ">=1.15.0"
|
||||
greenlet = ">=0.3"
|
||||
monotonic = ">=1.4"
|
||||
six = ">=1.10.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
@ -100,6 +120,18 @@ version = "4.1.0"
|
||||
[package.dependencies]
|
||||
Flask = "*"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Socket.IO integration for Flask applications"
|
||||
name = "flask-socketio"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "4.3.1"
|
||||
|
||||
[package.dependencies]
|
||||
Flask = ">=0.9"
|
||||
python-socketio = ">=4.3.0"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Clean single-source support for Python 3 and 2"
|
||||
@ -108,6 +140,14 @@ optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
version = "0.18.2"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Lightweight in-process concurrent programming"
|
||||
name = "greenlet"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.4.16"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
@ -132,6 +172,14 @@ zipp = ">=0.5"
|
||||
docs = ["sphinx", "rst.linker"]
|
||||
testing = ["packaging", "pep517", "importlib-resources (>=1.3)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "iniconfig: brain-dead simple config-ini parsing"
|
||||
name = "iniconfig"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.0.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Various helpers to pass data to untrusted environments and back."
|
||||
@ -173,6 +221,14 @@ optional = false
|
||||
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*"
|
||||
version = "1.1.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "An implementation of time.monotonic() for Python 2 & < 3.3"
|
||||
name = "monotonic"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "1.5"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "More routines for operating on iterables, beyond itertools"
|
||||
@ -242,14 +298,28 @@ description = "PyInstaller bundles a Python application and all its dependencies
|
||||
marker = "sys_platform == \"darwin\""
|
||||
name = "pyinstaller"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
version = "3.6"
|
||||
python-versions = "*"
|
||||
version = "4.0"
|
||||
|
||||
[package.dependencies]
|
||||
altgraph = "*"
|
||||
dis3 = "*"
|
||||
macholib = ">=1.8"
|
||||
pyinstaller-hooks-contrib = ">=2020.6"
|
||||
setuptools = "*"
|
||||
|
||||
[package.extras]
|
||||
encryption = ["tinyaes (>=1.0.0)"]
|
||||
hook_testing = ["pytest (>=2.7.3)", "execnet (>=1.5.0)", "psutil"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Community maintained hooks for PyInstaller"
|
||||
marker = "sys_platform == \"darwin\""
|
||||
name = "pyinstaller-hooks-contrib"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "2020.7"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python parsing module"
|
||||
@ -291,24 +361,25 @@ description = "pytest: simple powerful testing with Python"
|
||||
name = "pytest"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
version = "5.4.3"
|
||||
version = "6.0.1"
|
||||
|
||||
[package.dependencies]
|
||||
atomicwrites = ">=1.0"
|
||||
attrs = ">=17.4.0"
|
||||
colorama = "*"
|
||||
iniconfig = "*"
|
||||
more-itertools = ">=4.0.0"
|
||||
packaging = "*"
|
||||
pluggy = ">=0.12,<1.0"
|
||||
py = ">=1.5.0"
|
||||
wcwidth = "*"
|
||||
py = ">=1.8.2"
|
||||
toml = "*"
|
||||
|
||||
[package.dependencies.importlib-metadata]
|
||||
python = "<3.8"
|
||||
version = ">=0.12"
|
||||
|
||||
[package.extras]
|
||||
checkqa-mypy = ["mypy (v0.761)"]
|
||||
checkqa_mypy = ["mypy (0.780)"]
|
||||
testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"]
|
||||
|
||||
[[package]]
|
||||
@ -337,6 +408,37 @@ pytest = ">=3.0.0"
|
||||
dev = ["pre-commit", "tox"]
|
||||
doc = ["sphinx", "sphinx-rtd-theme"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Engine.IO server"
|
||||
name = "python-engineio"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "3.13.2"
|
||||
|
||||
[package.dependencies]
|
||||
six = ">=1.9.0"
|
||||
|
||||
[package.extras]
|
||||
asyncio_client = ["aiohttp (>=3.4)"]
|
||||
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "Socket.IO server"
|
||||
name = "python-socketio"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "4.6.0"
|
||||
|
||||
[package.dependencies]
|
||||
python-engineio = ">=3.13.0"
|
||||
six = ">=1.9.0"
|
||||
|
||||
[package.extras]
|
||||
asyncio_client = ["aiohttp (>=3.4)", "websockets (>=7.0)"]
|
||||
client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "QR Code image generator"
|
||||
@ -389,27 +491,27 @@ optional = false
|
||||
python-versions = "*"
|
||||
version = "1.8.0"
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
name = "toml"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.10.1"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "HTTP library with thread-safe connection pooling, file post, and more."
|
||||
name = "urllib3"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4"
|
||||
version = "1.25.9"
|
||||
version = "1.25.10"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlipy (>=0.6.0)"]
|
||||
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "pyOpenSSL (>=0.14)", "ipaddress"]
|
||||
socks = ["PySocks (>=1.5.6,<1.5.7 || >1.5.7,<2.0)"]
|
||||
|
||||
[[package]]
|
||||
category = "dev"
|
||||
description = "Measures the displayed width of unicode strings in a terminal"
|
||||
name = "wcwidth"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
version = "0.2.5"
|
||||
|
||||
[[package]]
|
||||
category = "main"
|
||||
description = "The comprehensive WSGI web application library."
|
||||
@ -436,7 +538,8 @@ docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"]
|
||||
testing = ["jaraco.itertools", "func-timeout"]
|
||||
|
||||
[metadata]
|
||||
content-hash = "71c32a60a36f2e66745f800f5cab96e8d2551f5959acc8b07aa9003d6f3f702b"
|
||||
content-hash = "de18641607a5f3bf11a3051b84eb8a02d4263f435f3554c1aa5860136011cbf3"
|
||||
lock-version = "1.0"
|
||||
python-versions = "^3.7"
|
||||
|
||||
[metadata.files]
|
||||
@ -468,10 +571,13 @@ colorama = [
|
||||
{file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"},
|
||||
{file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"},
|
||||
]
|
||||
dis3 = [
|
||||
{file = "dis3-0.1.3-py2-none-any.whl", hash = "sha256:61f7720dd0d8749d23fda3d7227ce74d73da11c2fade993a67ab2f9852451b14"},
|
||||
{file = "dis3-0.1.3-py3-none-any.whl", hash = "sha256:30b6412d33d738663e8ded781b138f4b01116437f0872aa56aa3adba6aeff218"},
|
||||
{file = "dis3-0.1.3.tar.gz", hash = "sha256:9259b881fc1df02ed12ac25f82d4a85b44241854330b1a651e40e0c675cb2d1e"},
|
||||
dnspython = [
|
||||
{file = "dnspython-2.0.0-py3-none-any.whl", hash = "sha256:40bb3c24b9d4ec12500f0124288a65df232a3aa749bb0c39734b782873a2544d"},
|
||||
{file = "dnspython-2.0.0.zip", hash = "sha256:044af09374469c3a39eeea1a146e8cac27daec951f1f1f157b1962fc7cb9d1b7"},
|
||||
]
|
||||
eventlet = [
|
||||
{file = "eventlet-0.25.2-py2.py3-none-any.whl", hash = "sha256:955f2cf538829bfcb7b3aa885ace40e8ae5965dcd5b876c384d0c5869702db1d"},
|
||||
{file = "eventlet-0.25.2.tar.gz", hash = "sha256:4c8ab42c51bff55204fef43cff32616558bedbc7538d876bb6a96ce820c7f9ed"},
|
||||
]
|
||||
flask = [
|
||||
{file = "Flask-1.1.2-py2.py3-none-any.whl", hash = "sha256:8a4fdd8936eba2512e9c85df320a37e694c93945b33ef33c89946a340a238557"},
|
||||
@ -481,9 +587,32 @@ flask-httpauth = [
|
||||
{file = "Flask-HTTPAuth-4.1.0.tar.gz", hash = "sha256:9e028e4375039a49031eb9ecc40be4761f0540476040f6eff329a31dabd4d000"},
|
||||
{file = "Flask_HTTPAuth-4.1.0-py2.py3-none-any.whl", hash = "sha256:29e0288869a213c7387f0323b6bf2c7191584fb1da8aa024d9af118e5cd70de7"},
|
||||
]
|
||||
flask-socketio = [
|
||||
{file = "Flask-SocketIO-4.3.1.tar.gz", hash = "sha256:36c1d5765010d1f4e4f05b4cc9c20c289d9dc70698c88d1addd0afcfedc5b062"},
|
||||
{file = "Flask_SocketIO-4.3.1-py2.py3-none-any.whl", hash = "sha256:3668675bf7763c5b5f56689d439f07356e89c0a52e0c9e9cd3cc08563c07b252"},
|
||||
]
|
||||
future = [
|
||||
{file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"},
|
||||
]
|
||||
greenlet = [
|
||||
{file = "greenlet-0.4.16-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:80cb0380838bf4e48da6adedb0c7cd060c187bb4a75f67a5aa9ec33689b84872"},
|
||||
{file = "greenlet-0.4.16-cp27-cp27m-win32.whl", hash = "sha256:df7de669cbf21de4b04a3ffc9920bc8426cab4c61365fa84d79bf97401a8bef7"},
|
||||
{file = "greenlet-0.4.16-cp27-cp27m-win_amd64.whl", hash = "sha256:1429dc183b36ec972055e13250d96e174491559433eb3061691b446899b87384"},
|
||||
{file = "greenlet-0.4.16-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5ea034d040e6ab1d2ae04ab05a3f37dbd719c4dee3804b13903d4cc794b1336e"},
|
||||
{file = "greenlet-0.4.16-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c196a5394c56352e21cb7224739c6dd0075b69dd56f758505951d1d8d68cf8a9"},
|
||||
{file = "greenlet-0.4.16-cp35-cp35m-win32.whl", hash = "sha256:1000038ba0ea9032948e2156a9c15f5686f36945e8f9906e6b8db49f358e7b52"},
|
||||
{file = "greenlet-0.4.16-cp35-cp35m-win_amd64.whl", hash = "sha256:1b805231bfb7b2900a16638c3c8b45c694334c811f84463e52451e00c9412691"},
|
||||
{file = "greenlet-0.4.16-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:e5db19d4a7d41bbeb3dd89b49fc1bc7e6e515b51bbf32589c618655a0ebe0bf0"},
|
||||
{file = "greenlet-0.4.16-cp36-cp36m-win32.whl", hash = "sha256:eac2a3f659d5f41d6bbfb6a97733bc7800ea5e906dc873732e00cebb98cec9e4"},
|
||||
{file = "greenlet-0.4.16-cp36-cp36m-win_amd64.whl", hash = "sha256:7eed31f4efc8356e200568ba05ad645525f1fbd8674f1e5be61a493e715e3873"},
|
||||
{file = "greenlet-0.4.16-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:682328aa576ec393c1872615bcb877cf32d800d4a2f150e1a5dc7e56644010b1"},
|
||||
{file = "greenlet-0.4.16-cp37-cp37m-win32.whl", hash = "sha256:3a35e33902b2e6079949feed7a2dafa5ac6f019da97bd255842bb22de3c11bf5"},
|
||||
{file = "greenlet-0.4.16-cp37-cp37m-win_amd64.whl", hash = "sha256:b0b2a984bbfc543d144d88caad6cc7ff4a71be77102014bd617bd88cfb038727"},
|
||||
{file = "greenlet-0.4.16-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:d83c1d38658b0f81c282b41238092ed89d8f93c6e342224ab73fb39e16848721"},
|
||||
{file = "greenlet-0.4.16-cp38-cp38-win32.whl", hash = "sha256:e695ac8c3efe124d998230b219eb51afb6ef10524a50b3c45109c4b77a8a3a92"},
|
||||
{file = "greenlet-0.4.16-cp38-cp38-win_amd64.whl", hash = "sha256:133ba06bad4e5f2f8bf6a0ac434e0fd686df749a86b3478903b92ec3a9c0c90b"},
|
||||
{file = "greenlet-0.4.16.tar.gz", hash = "sha256:6e06eac722676797e8fce4adb8ad3dc57a1bb3adfb0dd3fdf8306c055a38456c"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-2.10-py2.py3-none-any.whl", hash = "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"},
|
||||
{file = "idna-2.10.tar.gz", hash = "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6"},
|
||||
@ -492,6 +621,10 @@ importlib-metadata = [
|
||||
{file = "importlib_metadata-1.7.0-py2.py3-none-any.whl", hash = "sha256:dc15b2969b4ce36305c51eebe62d418ac7791e9a157911d58bfb1f9ccd8e2070"},
|
||||
{file = "importlib_metadata-1.7.0.tar.gz", hash = "sha256:90bb658cdbbf6d1735b6341ce708fc7024a3e14e99ffdc5783edea9f9b077f83"},
|
||||
]
|
||||
iniconfig = [
|
||||
{file = "iniconfig-1.0.1-py3-none-any.whl", hash = "sha256:80cf40c597eb564e86346103f609d74efce0f6b4d4f30ec8ce9e2c26411ba437"},
|
||||
{file = "iniconfig-1.0.1.tar.gz", hash = "sha256:e5f92f89355a67de0595932a6c6c02ab4afddc6fcdc0bfc5becd0d60884d3f69"},
|
||||
]
|
||||
itsdangerous = [
|
||||
{file = "itsdangerous-1.1.0-py2.py3-none-any.whl", hash = "sha256:b12271b2047cb23eeb98c8b5622e2e5c5e9abd9784a153e9d8ef9cb4dd09d749"},
|
||||
{file = "itsdangerous-1.1.0.tar.gz", hash = "sha256:321b033d07f2a4136d3ec762eac9f16a10ccd60f53c0c91af90217ace7ba1f19"},
|
||||
@ -539,6 +672,10 @@ markupsafe = [
|
||||
{file = "MarkupSafe-1.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be"},
|
||||
{file = "MarkupSafe-1.1.1.tar.gz", hash = "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b"},
|
||||
]
|
||||
monotonic = [
|
||||
{file = "monotonic-1.5-py2.py3-none-any.whl", hash = "sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"},
|
||||
{file = "monotonic-1.5.tar.gz", hash = "sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0"},
|
||||
]
|
||||
more-itertools = [
|
||||
{file = "more-itertools-8.4.0.tar.gz", hash = "sha256:68c70cc7167bdf5c7c9d8f6954a7837089c6a36bf565383919bb595efb8a17e5"},
|
||||
{file = "more_itertools-8.4.0-py3-none-any.whl", hash = "sha256:b78134b2063dd214000685165d81c154522c3ee0a1c0d4d113c80361c234c5a2"},
|
||||
@ -591,7 +728,11 @@ pycryptodome = [
|
||||
{file = "pycryptodome-3.9.8.tar.gz", hash = "sha256:0e24171cf01021bc5dc17d6a9d4f33a048f09d62cc3f62541e95ef104588bda4"},
|
||||
]
|
||||
pyinstaller = [
|
||||
{file = "PyInstaller-3.6.tar.gz", hash = "sha256:3730fa80d088f8bb7084d32480eb87cbb4ddb64123363763cf8f2a1378c1c4b7"},
|
||||
{file = "pyinstaller-4.0.tar.gz", hash = "sha256:970beb07115761d5e4ec317c1351b712fd90ae7f23994db914c633281f99bab0"},
|
||||
]
|
||||
pyinstaller-hooks-contrib = [
|
||||
{file = "pyinstaller-hooks-contrib-2020.7.tar.gz", hash = "sha256:74936d044f319cd7a9dca322b46a818fcb6e2af1c67af62e8a6a3121eb2863d2"},
|
||||
{file = "pyinstaller_hooks_contrib-2020.7-py2.py3-none-any.whl", hash = "sha256:5b6e06ba6072499189f5b8e1623d5f0414962941aac370ee4f842de25455be5b"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
|
||||
@ -629,8 +770,8 @@ pysocks = [
|
||||
{file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"},
|
||||
]
|
||||
pytest = [
|
||||
{file = "pytest-5.4.3-py3-none-any.whl", hash = "sha256:5c0db86b698e8f170ba4582a492248919255fcd4c79b1ee64ace34301fb589a1"},
|
||||
{file = "pytest-5.4.3.tar.gz", hash = "sha256:7979331bfcba207414f5e1263b5a0f8f521d0f457318836a7355531ed1a4c7d8"},
|
||||
{file = "pytest-6.0.1-py3-none-any.whl", hash = "sha256:8b6007800c53fdacd5a5c192203f4e531eb2a1540ad9c752e052ec0f7143dbad"},
|
||||
{file = "pytest-6.0.1.tar.gz", hash = "sha256:85228d75db9f45e06e57ef9bf4429267f81ac7c0d742cc9ed63d09886a9fe6f4"},
|
||||
]
|
||||
pytest-faulthandler = [
|
||||
{file = "pytest-faulthandler-2.0.1.tar.gz", hash = "sha256:ed72bbce87ac344da81eb7d882196a457d4a1026a3da4a57154dacd85cd71ae5"},
|
||||
@ -640,6 +781,14 @@ pytest-qt = [
|
||||
{file = "pytest-qt-3.3.0.tar.gz", hash = "sha256:714b0bf86c5313413f2d300ac613515db3a1aef595051ab8ba2ffe619dbe8925"},
|
||||
{file = "pytest_qt-3.3.0-py2.py3-none-any.whl", hash = "sha256:5f8928288f50489d83f5d38caf2d7d9fcd6e7cf769947902caa4661dc7c851e3"},
|
||||
]
|
||||
python-engineio = [
|
||||
{file = "python-engineio-3.13.2.tar.gz", hash = "sha256:36b33c6aa702d9b6a7f527eec6387a2da1a9a24484ec2f086d76576413cef04b"},
|
||||
{file = "python_engineio-3.13.2-py2.py3-none-any.whl", hash = "sha256:cfded18156862f94544a9f8ef37f56727df731c8552d7023f5afee8369be2db6"},
|
||||
]
|
||||
python-socketio = [
|
||||
{file = "python-socketio-4.6.0.tar.gz", hash = "sha256:358d8fbbc029c4538ea25bcaa283e47f375be0017fcba829de8a3a731c9df25a"},
|
||||
{file = "python_socketio-4.6.0-py2.py3-none-any.whl", hash = "sha256:d437f797c44b6efba2f201867cf02b8c96b97dff26d4e4281ac08b45817cd522"},
|
||||
]
|
||||
qrcode = [
|
||||
{file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"},
|
||||
{file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"},
|
||||
@ -655,13 +804,13 @@ six = [
|
||||
stem = [
|
||||
{file = "stem-1.8.0.tar.gz", hash = "sha256:a0b48ea6224e95f22aa34c0bc3415f0eb4667ddeae3dfb5e32a6920c185568c2"},
|
||||
]
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.9-py2.py3-none-any.whl", hash = "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"},
|
||||
{file = "urllib3-1.25.9.tar.gz", hash = "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527"},
|
||||
toml = [
|
||||
{file = "toml-0.10.1-py2.py3-none-any.whl", hash = "sha256:bda89d5935c2eac546d648028b9901107a595863cb36bae0c73ac804a9b4ce88"},
|
||||
{file = "toml-0.10.1.tar.gz", hash = "sha256:926b612be1e5ce0634a2ca03470f95169cf16f939018233a670519cb4ac58b0f"},
|
||||
]
|
||||
wcwidth = [
|
||||
{file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"},
|
||||
{file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"},
|
||||
urllib3 = [
|
||||
{file = "urllib3-1.25.10-py2.py3-none-any.whl", hash = "sha256:e7983572181f5e1522d9c98453462384ee92a0be7fac5f1413a1e35c56cc0461"},
|
||||
{file = "urllib3-1.25.10.tar.gz", hash = "sha256:91056c15fa70756691db97756772bb1eb9678fa585d9184f24534b100dc60f4a"},
|
||||
]
|
||||
werkzeug = [
|
||||
{file = "Werkzeug-1.0.1-py2.py3-none-any.whl", hash = "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43"},
|
||||
|
@ -28,6 +28,8 @@ requests = "*"
|
||||
stem = "*"
|
||||
urllib3 = "*"
|
||||
Werkzeug = "*"
|
||||
flask-socketio = "^4.3.0"
|
||||
eventlet = "^0.25.2"
|
||||
qrcode = "^6.1"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
@ -17,6 +17,9 @@
|
||||
"gui_share_start_server": "Start sharing",
|
||||
"gui_share_stop_server": "Stop sharing",
|
||||
"gui_share_stop_server_autostop_timer": "Stop Sharing ({})",
|
||||
"gui_chat_stop_server_autostop_timer": "Stop Chat Server ({})",
|
||||
"gui_chat_start_server": "Start chat server",
|
||||
"gui_chat_stop_server": "Stop chat server",
|
||||
"gui_stop_server_autostop_timer_tooltip": "Auto-stop timer ends at {}",
|
||||
"gui_start_server_autostart_timer_tooltip": "Auto-start timer ends at {}",
|
||||
"gui_receive_start_server": "Start Receive Mode",
|
||||
@ -187,6 +190,8 @@
|
||||
"gui_new_tab_receive_description": "Turn your computer into an online dropbox. People will be able to use Tor Browser to send files to your computer.",
|
||||
"gui_new_tab_website_button": "Publish Website",
|
||||
"gui_new_tab_website_description": "Host a static HTML onion website from your computer.",
|
||||
"gui_new_tab_chat_button": "Start Chat Server",
|
||||
"gui_new_tab_chat_description": "Start an onion chat server and use it to chat in Tor Browser.",
|
||||
"gui_close_tab_warning_title": "Are you sure?",
|
||||
"gui_close_tab_warning_persistent_description": "This tab is persistent. If you close it you'll lose the onion address that it's using. Are you sure you want to close it?",
|
||||
"gui_close_tab_warning_share_description": "You're in the process of sending files. Are you sure you want to close this tab?",
|
||||
|
@ -167,6 +167,67 @@ ul.breadcrumbs li a:link, ul.breadcrumbs li a:visited {
|
||||
}
|
||||
}
|
||||
|
||||
.chat-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-users {
|
||||
width: 20%;
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 2px;
|
||||
overflow: auto;
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
.chat-users .editable-username {
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-users #user-list li {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.chat-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
margin: 0 1rem;
|
||||
height: calc(100vh - (45px + 2em));
|
||||
}
|
||||
|
||||
.chat-wrapper #chat {
|
||||
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-radius: 2px;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
background: #f2f2f2;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.chat-wrapper .chat-form {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.chat-wrapper input#new-message {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 992px) {
|
||||
.chat-users .editable-username {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.chat-users input#username {
|
||||
width: 90%;
|
||||
}
|
||||
}
|
||||
|
||||
.no-js {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.upload-wrapper {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
160
share/static/js/chat.js
Normal file
160
share/static/js/chat.js
Normal file
@ -0,0 +1,160 @@
|
||||
$(function(){
|
||||
$(document).ready(function(){
|
||||
$('.chat-container').removeClass('no-js');
|
||||
var socket = io.connect('http://' + document.domain + ':' + location.port + '/chat');
|
||||
|
||||
// Store current username received from app context
|
||||
var current_username = $('#username').val();
|
||||
|
||||
// On browser connect, emit a socket event to be added to
|
||||
// room and assigned random username
|
||||
socket.on('connect', function() {
|
||||
socket.emit('joined', {});
|
||||
});
|
||||
|
||||
// Triggered on any status change by any user, such as some
|
||||
// user joined, or changed username, or left, etc.
|
||||
socket.on('status', function(data) {
|
||||
addMessageToRoom(data, current_username, 'status');
|
||||
});
|
||||
|
||||
// Triggered when message is received from a user. Even when sent
|
||||
// by self, it get triggered after the server sends back the emit.
|
||||
socket.on('message', function(data) {
|
||||
addMessageToRoom(data, current_username, 'chat');
|
||||
});
|
||||
|
||||
// Triggered when disconnected either by server stop or timeout
|
||||
socket.on('disconnect', function(data) {
|
||||
addMessageToRoom({'msg': 'The chat server is disconnected.'}, current_username, 'status');
|
||||
})
|
||||
socket.on('connect_error', function(error) {
|
||||
console.log("error");
|
||||
})
|
||||
|
||||
// Trigger new message on enter or click of send message button.
|
||||
$('#new-message').on('keypress', function(e) {
|
||||
var code = e.keyCode || e.which;
|
||||
if (code == 13) {
|
||||
emitMessage(socket);
|
||||
}
|
||||
});
|
||||
$('#send-button').on('click', function(e) {
|
||||
emitMessage(socket);
|
||||
});
|
||||
|
||||
// Keep buttons disabled unless changed or not empty
|
||||
$('#username').on('keyup',function(event) {
|
||||
if ($('#username').val() !== '' && $('#username').val() !== current_username) {
|
||||
$('#update-username').removeAttr('disabled');
|
||||
if (event.keyCode == 13) {
|
||||
current_username = updateUsername(socket);
|
||||
}
|
||||
} else {
|
||||
$('#update-username').attr('disabled', true);
|
||||
}
|
||||
});
|
||||
|
||||
// Update username
|
||||
$('#update-username').on('click', function() {
|
||||
current_username = updateUsername(socket);
|
||||
});
|
||||
|
||||
// Show warning of losing data
|
||||
$(window).on('beforeunload', function (e) {
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
return '';
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var addMessageToRoom = function(data, current_username, messageType) {
|
||||
var scrollDiff = getScrollDiffBefore();
|
||||
if (messageType === 'status') {
|
||||
addStatusMessage(data.msg);
|
||||
if (data.connected_users) {
|
||||
addUserList(data.connected_users, current_username);
|
||||
}
|
||||
} else if (messageType === 'chat') {
|
||||
addChatMessage(data.msg)
|
||||
}
|
||||
scrollBottomMaybe(scrollDiff);
|
||||
}
|
||||
|
||||
var emitMessage = function(socket) {
|
||||
var text = $('#new-message').val();
|
||||
$('#new-message').val('');
|
||||
$('#chat').scrollTop($('#chat')[0].scrollHeight);
|
||||
socket.emit('text', {msg: text});
|
||||
}
|
||||
|
||||
var updateUsername = function(socket) {
|
||||
var username = $('#username').val();
|
||||
socket.emit('update_username', {username: username});
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: `http://${document.domain}:${location.port}/update-session-username`,
|
||||
contentType: 'application/json',
|
||||
dataType: 'json',
|
||||
data: JSON.stringify({'username': username})
|
||||
}).done(function(response) {
|
||||
console.log(response);
|
||||
});
|
||||
$('#update-username').attr('disabled', true);
|
||||
return username;
|
||||
}
|
||||
|
||||
/************************************/
|
||||
/********* Util Functions ***********/
|
||||
/************************************/
|
||||
|
||||
var createUserListHTML = function(connected_users, current_user) {
|
||||
var userListHTML = '';
|
||||
connected_users.sort();
|
||||
connected_users.forEach(function(username) {
|
||||
if (username !== current_user) {
|
||||
userListHTML += `<li>${sanitizeHTML(username)}</li>`;
|
||||
}
|
||||
});
|
||||
return userListHTML;
|
||||
}
|
||||
|
||||
var getScrollDiffBefore = function() {
|
||||
return $('#chat').scrollTop() - ($('#chat')[0].scrollHeight - $('#chat')[0].offsetHeight);
|
||||
}
|
||||
|
||||
var scrollBottomMaybe = function(scrollDiff) {
|
||||
// Scrolls to bottom if the user is scrolled at bottom
|
||||
// if the user has scrolled upp, it wont scroll at bottom.
|
||||
// Note: when a user themselves send a message, it will still
|
||||
// scroll to the bottom even if they had scrolled up before.
|
||||
if (scrollDiff > 0) {
|
||||
$('#chat').scrollTop($('#chat')[0].scrollHeight);
|
||||
}
|
||||
}
|
||||
|
||||
var addStatusMessage = function(message) {
|
||||
$('#chat').append(
|
||||
`<p><small><i>${sanitizeHTML(message)}</i></small></p>`
|
||||
);
|
||||
}
|
||||
|
||||
var addChatMessage = function(message) {
|
||||
$('#chat').append(`<p>${sanitizeHTML(message)}</p>`);
|
||||
}
|
||||
|
||||
var addUserList = function(connected_users, current_username) {
|
||||
$('#user-list').html(
|
||||
createUserListHTML(
|
||||
connected_users,
|
||||
current_username
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
var sanitizeHTML = function(str) {
|
||||
var temp = document.createElement('span');
|
||||
temp.textContent = str;
|
||||
return temp.innerHTML;
|
||||
};
|
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
46
share/templates/chat.html
Normal file
46
share/templates/chat.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!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>
|
||||
<noscript>
|
||||
<p>
|
||||
Chat <b>requires JavaScript</b>, so you must set your Tor Browser security
|
||||
level to <b>Safer</b> or <b>Standard</b> to join.
|
||||
</p>
|
||||
</noscript>
|
||||
|
||||
<div class="chat-container no-js">
|
||||
<div class="chat-users">
|
||||
<div class="editable-username">
|
||||
<input id="username" value="{{ username }}" />
|
||||
<button id="update-username" disabled>Save</button>
|
||||
</div>
|
||||
<ul id="user-list">
|
||||
</ul>
|
||||
</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.5.1.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…
Reference in New Issue
Block a user