v2
42
.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# ---> macOS
|
||||
# General
|
||||
.DS_Store
|
||||
._.DS_Store
|
||||
._*
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
/uploads/*
|
||||
etc/avatars/*
|
||||
etc/previews/*
|
||||
|
||||
etc/vendor/
|
||||
etc/config.json
|
||||
test.php
|
||||
test.js
|
||||
|
||||
node_modules/
|
||||
package-lock.json
|
||||
composer.lock
|
20
.htaccess
Normal file
@ -0,0 +1,20 @@
|
||||
<Files *>
|
||||
Header set Access-Control-Allow-Origin "*"
|
||||
Header set Access-Control-Allow-Methods: "GET"
|
||||
</Files>
|
||||
|
||||
<If "%{REQUEST_URI} =~ m#/(avatar.php)#">
|
||||
Header set Cache-Control "max-age=1800, public"
|
||||
</If>
|
||||
<If "%{REQUEST_URI} =~ m#/(preview.php)#">
|
||||
Header set Cache-Control "max-age=63072000, public"
|
||||
</If>
|
||||
<If "%{REQUEST_URI} =~ m#/(assets|uploads)/#">
|
||||
Header set Cache-Control "max-age=63072000, public"
|
||||
</If>
|
||||
|
||||
RewriteEngine on
|
||||
RewriteRule ^avatar/([A-Za-z0-9]+)/?$ etc/avatar.php?id=$1
|
||||
RewriteRule ^preview/([A-Za-z0-9]+)/?$ etc/preview.php?id=$1
|
||||
|
||||
RewriteRule ^invite/([A-Za-z0-9-]+)/?$ id.php?invite=$1
|
1
.well-known/wallets/HNS
Normal file
@ -0,0 +1 @@
|
||||
hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa
|
236
api.php
Normal file
@ -0,0 +1,236 @@
|
||||
<?php
|
||||
include "etc/includes.php";
|
||||
|
||||
$json = file_get_contents('php://input');
|
||||
$data = json_decode($json, true);
|
||||
|
||||
if (!$data) {
|
||||
$data = $_GET;
|
||||
}
|
||||
|
||||
if (!@$data["action"]) {
|
||||
die();
|
||||
}
|
||||
|
||||
$output = [
|
||||
"success" => true,
|
||||
"fields" => []
|
||||
];
|
||||
|
||||
foreach ($data as $key => $value) {
|
||||
if (!is_array($data[$key])) {
|
||||
$data[$key] = trim($value, ". ".chr(194).chr(160).PHP_EOL);
|
||||
}
|
||||
}
|
||||
|
||||
switch ($data["action"]) {
|
||||
case "setPublicKey":
|
||||
case "getPublicKey":
|
||||
case "saveSettings":
|
||||
case "getGifCategories":
|
||||
case "searchGifs":
|
||||
case "pushToken":
|
||||
case "getMessage":
|
||||
if ($data["session"]) {
|
||||
$keyValid = @sql("SELECT * FROM `sessions` WHERE `id` = ?", [$data["session"]]);
|
||||
if (!$keyValid) {
|
||||
error("Invalid key.");
|
||||
}
|
||||
}
|
||||
else {
|
||||
error("Missing key.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($data["action"]) {
|
||||
case "checkName":
|
||||
if (!activeDomainForName($data["domain"])) {
|
||||
error("The domain provided isn't available to message.");
|
||||
}
|
||||
break;
|
||||
|
||||
case "startSession":
|
||||
$code = "V2-".generateCode("session");
|
||||
sql("INSERT INTO `sessions` (id) VALUES (?)", [$code]);
|
||||
$output["session"] = $code;
|
||||
break;
|
||||
|
||||
case "setPublicKey":
|
||||
$insert = sql("UPDATE `sessions` SET `pubkey` = ? WHERE `id` = ? AND `pubkey` IS NULL", [$data["pubkey"], $data["session"]]);
|
||||
break;
|
||||
|
||||
case "getPublicKey":
|
||||
$key = @sql("SELECT `pubkey` FROM `sessions` WHERE `id` = ?", [$data["session"]])[0]["pubkey"];
|
||||
$output["pubkey"] = $key;
|
||||
break;
|
||||
|
||||
case "getAddress":
|
||||
$address = @sql("SELECT `address` FROM `domains` WHERE `id` = ?", [$data["domain"]])[0];
|
||||
if ($address["address"]) {
|
||||
$output["address"] = $address["address"];
|
||||
}
|
||||
break;
|
||||
|
||||
case "saveSettings":
|
||||
$settings = json_decode($data["settings"], true);
|
||||
|
||||
$domainInfo = domainForID($data["domain"]);
|
||||
$tld = tldForDomain($domainInfo["domain"]);
|
||||
|
||||
if (@$settings["avatar"]) {
|
||||
if (in_array($tld, getStakedNames())) {
|
||||
$settings["avatar"] = trim($settings["avatar"]);
|
||||
|
||||
if (!validImage($settings["avatar"])) {
|
||||
error("The Avatar URL provided isn't a valid image.");
|
||||
}
|
||||
|
||||
sql("UPDATE `domains` SET `avatar` = ? WHERE `id` = ? AND `session` = ?", [$settings["avatar"], $data["domain"], $data["session"]]);
|
||||
|
||||
$output["avatar"] = $settings["avatar"];
|
||||
}
|
||||
else {
|
||||
error("Only SLD's of staked TLD's can set an Avatar here.");
|
||||
}
|
||||
}
|
||||
|
||||
if (@$settings["address"]) {
|
||||
if (in_array($tld, getStakedHIP2Names())) {
|
||||
$settings["address"] = trim($settings["address"]);
|
||||
|
||||
if (!validateAddress($settings["address"])) {
|
||||
error("The HNS Address provided isn't valid.");
|
||||
}
|
||||
|
||||
sql("UPDATE `domains` SET `address` = ? WHERE `id` = ? AND `session` = ?", [$settings["address"], $data["domain"], $data["session"]]);
|
||||
}
|
||||
else {
|
||||
error("Only SLD's of certain staked TLD's can set an address here.");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "getMetaTags":
|
||||
$checkCache = @sql("SELECT `id`, `link`, `title`, `description`, `image`, `video` FROM `previews` WHERE `link` = ?", [$data["url"]])[0];
|
||||
if ($checkCache) {
|
||||
unset($checkCache["link"]);
|
||||
|
||||
foreach ($checkCache as $key => $value) {
|
||||
if (!$value) {
|
||||
unset($checkCache[$key]);
|
||||
}
|
||||
}
|
||||
|
||||
$tags = $checkCache;
|
||||
}
|
||||
else {
|
||||
$tags = fetchMetaTags($data["url"]);
|
||||
}
|
||||
|
||||
if (@$tags["id"]) {
|
||||
if (@$tags["title"]) {
|
||||
$output["tags"] = $tags;
|
||||
}
|
||||
|
||||
if (@$output["tags"]["image"]) {
|
||||
$output["tags"]["image"] = "/preview/".$tags["id"];
|
||||
}
|
||||
|
||||
if (@$output["tags"]["description"]) {
|
||||
$output["tags"]["description"] = $output["tags"]["description"];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "getGifCategories":
|
||||
$categories = [];
|
||||
$getGifs = file_get_contents("https://tenor.googleapis.com/v2/categories?key=".$GLOBALS["tenorKey"]."&client_key=HNSChat&limit=20");
|
||||
$json = json_decode($getGifs, true);
|
||||
|
||||
if (@$json["tags"]) {
|
||||
foreach ($json["tags"] as $key => $tag) {
|
||||
$categories[] = [
|
||||
"term" => @$tag["searchterm"],
|
||||
"gif" => @$tag["image"]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$output["categories"] = $categories;
|
||||
break;
|
||||
|
||||
case "searchGifs":
|
||||
if (@$data["query"]) {
|
||||
$gifs = [];
|
||||
$getGifs = file_get_contents("https://tenor.googleapis.com/v2/search?q=".urlencode($data["query"])."&key=".$GLOBALS["tenorKey"]."&client_key=HNSChat&limit=100");
|
||||
$json = json_decode($getGifs, true);
|
||||
|
||||
if (@$json["results"]) {
|
||||
foreach ($json["results"] as $key => $gif) {
|
||||
$gifs[] = [
|
||||
"id" => @$gif["id"],
|
||||
"preview" => @$gif["media_formats"]["tinygif"]["url"],
|
||||
"full" => @$gif["media_formats"]["gif"]["url"],
|
||||
"width" => @$gif["media_formats"]["gif"]["dims"][0],
|
||||
"height" => @$gif["media_formats"]["gif"]["dims"][1],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$output["gifs"] = $gifs;
|
||||
}
|
||||
break;
|
||||
|
||||
case "getMessage":
|
||||
$message = @sql("SELECT * FROM `messages` WHERE `id` = ?", [$data["id"]])[0];
|
||||
if ($message) {
|
||||
$domain = domainForID($data["domain"]);
|
||||
$channel = channelForID($message["conversation"]);
|
||||
if ($channel) {
|
||||
if ($channel["public"] || ($domain["tld"] == $channel["name"])) {
|
||||
$output = [
|
||||
"success" => true,
|
||||
"id" => $message["id"],
|
||||
"time" => $message["time"],
|
||||
"conversation" => $message["conversation"],
|
||||
"user" => $message["user"],
|
||||
"message" => $message["message"],
|
||||
"reactions" => $message["reactions"],
|
||||
];
|
||||
|
||||
if (@$message["reply"]) {
|
||||
$output["reply"] = true;
|
||||
$output["replying"] = $message["replying"];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "pushToken":
|
||||
if (preg_match("/^ExponentPushToken\[.+?\]$/", $data["token"])) {
|
||||
$exists = @sql("SELECT JSON_CONTAINS(`push`, JSON_QUOTE(?), '$') AS `exists` FROM `sessions` WHERE `id` = ?", [$data["token"], $data["session"]])[0]["exists"];
|
||||
if (!$exists) {
|
||||
sql("UPDATE `sessions` SET `push` = JSON_ARRAY_APPEND(`push`, '$', ?) WHERE `id` = ?", [$data["token"], $data["session"]]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
$output["message"] = "Unknown function.";
|
||||
$output["success"] = false;
|
||||
break;
|
||||
}
|
||||
|
||||
end:
|
||||
if (@$output["fields"] && @count($output["fields"])) {
|
||||
$output["fields"] = array_unique($output["fields"]);
|
||||
$output["success"] = false;
|
||||
}
|
||||
else {
|
||||
unset($output["fields"]);
|
||||
}
|
||||
|
||||
die(json_encode($output));
|
||||
?>
|
123
assets/css/dish.css
Executable file
@ -0,0 +1,123 @@
|
||||
.Scenary {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
/* Container of Screen and Dish */
|
||||
.Conference {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
border-radius: 10px;
|
||||
gap: 20px;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Container of Cameras */
|
||||
.Dish {
|
||||
overflow: scroll;
|
||||
display: flex;
|
||||
align-content: center;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
vertical-align: middle;
|
||||
flex: 1;
|
||||
border-radius: 10px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/* Camera */
|
||||
.Dish>div {
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
align-self: center;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
box-shadow: var(--shadow-dark);
|
||||
background: #fff;
|
||||
animation: show 0.4s ease;
|
||||
}
|
||||
|
||||
/* Video (check the nice property object-fit) */
|
||||
.Dish>div video {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
object-fit: cover;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #000;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
-webkit-transition: margin-top 1s ease-in-out;
|
||||
-moz-transition: margin-top 1s ease-in-out;
|
||||
-o-transition: margin-top 1s ease-in-out;
|
||||
transition: margin-top 1s ease-in-out;
|
||||
}
|
||||
|
||||
/* Animation of Loading Video */
|
||||
.Dish>div video.loading {
|
||||
margin-top: 100%;
|
||||
}
|
||||
|
||||
/* Aspect Ratio Number */
|
||||
.Dish div:after {
|
||||
color: #aaa;
|
||||
font-size: 13px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 23px;
|
||||
font-weight: 100;
|
||||
content: attr(data-aspect);
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Gray Diagonal */
|
||||
.Dish div:before {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
background: url(./../general/diagonal.jpg);
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
opacity: 0.3;
|
||||
font-weight: 100;
|
||||
content: '';
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Screen */
|
||||
.Screen {
|
||||
flex: 2;
|
||||
background: #000;
|
||||
opacity: 0.8;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
/* Animation of Cameras */
|
||||
@keyframes show {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.4) translateY(20px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: scale(1) translateY(0);
|
||||
}
|
||||
}
|
123
assets/css/hljs-varo.css
Executable file
@ -0,0 +1,123 @@
|
||||
/*!
|
||||
Theme: Default
|
||||
Description: Original highlight.js style
|
||||
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
|
||||
Maintainer: @highlightjs/core-team
|
||||
Website: https://highlightjs.org/
|
||||
License: see project LICENSE
|
||||
Touched: 2021
|
||||
*/
|
||||
|
||||
/*
|
||||
This is left on purpose making default.css the single file that can be lifted
|
||||
as-is from the repository directly without the need for a build step
|
||||
|
||||
Typically this "required" baseline CSS is added by `makestuff.js` during build.
|
||||
*/
|
||||
pre code.hljs {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* end baseline CSS */
|
||||
|
||||
/* Base color: saturation 0; */
|
||||
|
||||
.hljs-subst {
|
||||
/* default */
|
||||
}
|
||||
|
||||
/* purposely ignored */
|
||||
.hljs-formula,
|
||||
.hljs-attr,
|
||||
.hljs-property,
|
||||
.hljs-params {}
|
||||
|
||||
.hljs-comment {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
.hljs-tag,
|
||||
.hljs-punctuation {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
|
||||
.hljs-tag .hljs-name,
|
||||
.hljs-tag .hljs-attr {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-attribute,
|
||||
.hljs-selector-tag,
|
||||
.hljs-meta .hljs-keyword,
|
||||
|
||||
.hljs-doctag,
|
||||
.hljs-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
/* User color: hue: 0 */
|
||||
|
||||
.hljs-type,
|
||||
.hljs-string,
|
||||
.hljs-number,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-quote,
|
||||
.hljs-template-tag,
|
||||
.hljs-deletion {
|
||||
color: #409aed;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-section {
|
||||
color: #409aed;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.hljs-regexp,
|
||||
.hljs-symbol,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-link,
|
||||
.hljs-selector-attr,
|
||||
.hljs-operator,
|
||||
.hljs-selector-pseudo {
|
||||
color: #40ed50;
|
||||
}
|
||||
|
||||
/* Language color: hue: 90; */
|
||||
|
||||
.hljs-literal {
|
||||
color: #db4437;
|
||||
}
|
||||
|
||||
.hljs-built_in,
|
||||
.hljs-bullet,
|
||||
.hljs-code,
|
||||
.hljs-addition {
|
||||
color: #397300;
|
||||
}
|
||||
|
||||
|
||||
/* Meta color: hue: 200 */
|
||||
|
||||
.hljs-meta {
|
||||
color: #7a7a7a;
|
||||
}
|
||||
|
||||
.hljs-meta .hljs-string {
|
||||
color: #38a;
|
||||
}
|
||||
|
||||
|
||||
/* Misc effects */
|
||||
|
||||
.hljs-emphasis {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.hljs-strong {
|
||||
font-weight: bold;
|
||||
}
|
9
assets/css/hljs.css
Executable file
@ -0,0 +1,9 @@
|
||||
/*!
|
||||
Theme: Default
|
||||
Description: Original highlight.js style
|
||||
Author: (c) Ivan Sagalaev <maniac@softwaremaniacs.org>
|
||||
Maintainer: @highlightjs/core-team
|
||||
Website: https://highlightjs.org/
|
||||
License: see project LICENSE
|
||||
Touched: 2021
|
||||
*/pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}.hljs{background:#f3f3f3;color:#444}.hljs-comment{color:#697070}.hljs-punctuation,.hljs-tag{color:#444a}.hljs-tag .hljs-attr,.hljs-tag .hljs-name{color:#444}.hljs-attribute,.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-name,.hljs-selector-tag{font-weight:700}.hljs-deletion,.hljs-number,.hljs-quote,.hljs-selector-class,.hljs-selector-id,.hljs-string,.hljs-template-tag,.hljs-type{color:#800}.hljs-section,.hljs-title{color:#800;font-weight:700}.hljs-link,.hljs-operator,.hljs-regexp,.hljs-selector-attr,.hljs-selector-pseudo,.hljs-symbol,.hljs-template-variable,.hljs-variable{color:#ab5656}.hljs-literal{color:#695}.hljs-addition,.hljs-built_in,.hljs-bullet,.hljs-code{color:#397300}.hljs-meta{color:#1f7199}.hljs-meta .hljs-string{color:#38a}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
|
3036
assets/css/style.css
Executable file
BIN
assets/img/cover.png
Executable file
After Width: | Height: | Size: 19 KiB |
25
assets/img/handshake.svg
Executable file
@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 2000 2088.9" style="enable-background:new 0 0 2000 2088.9;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill-rule:evenodd;clip-rule:evenodd;}
|
||||
</style>
|
||||
<path class="st0" d="M1726.6,681.4l-129.8-230.6l251.5,0.1c6.7,0,14.6,4.5,18.2,10.5c4,6.6,24.4,40.3,49,80.7
|
||||
c29.4,48.4,64.5,106.3,84.6,139.3H1726.6z M1261.4,2078c-6.1,10.9-14,10.9-16.6,10.9h-102.8c-54.4,0-117.1-0.1-154.5-0.1
|
||||
l399.6-717.4c10.4-18.5,3.7-42-14.8-52.3c-5.7-3.2-12.1-4.9-18.6-4.9l0,0l-681,0.9l-135.5-234.5h992c0.1,0,0.2,0,0.3,0s0.2,0,0.3,0
|
||||
c0.6,0,1.1-0.2,1.6-0.2c2.2-0.1,4.4-0.4,6.6-0.9c1.6-0.4,3.3-0.9,4.8-1.5c0.8-0.3,1.7-0.7,2.5-1c7.7-3.3,14.1-9.1,18.2-16.5
|
||||
L1727,758.2h270L1261.4,2078z M921.1,2050.7c-8.7-14.3-20-32.9-32.3-53.1c-41.3-68.2-94.2-155.5-100.2-165.1
|
||||
c-2-3.2-2.9-11.1,1.3-18.7c9.6-17.2,190.4-343,234.5-422.4l264.1-0.3L921.1,2050.7z M470.8,1601.8l-131.3-233.2l132.3-248
|
||||
l132.7,229.7C563.3,1428,498.9,1549.1,470.8,1601.8z M294.6,1638.1c-66.8,0-133.3,0-143,0l0,0c-6.5,0-14.4-4.6-18-10.5l-42.7-70.4
|
||||
C60.4,1507,21.5,1442.9,0,1407.5h273.4l129.8,230.6C374.9,1638.1,334.8,1638.1,294.6,1638.1L294.6,1638.1z M738.6,11
|
||||
c6.1-11,14-11,16.5-11h258.3L612.7,717.4c-0.3,0.6-0.5,1.2-0.8,1.9c-0.6,1.2-1.1,2.4-1.6,3.7c-0.4,1.2-0.8,2.4-1.1,3.7
|
||||
s-0.6,2.3-0.8,3.5c-0.2,1.4-0.4,2.8-0.4,4.3c0,0.6-0.2,1.2-0.2,1.9c0,0.5,0.1,0.9,0.2,1.4c0,1.4,0.2,2.8,0.4,4.2
|
||||
c0.1,1.2,0.3,2.3,0.6,3.5c0.3,1.2,0.7,2.4,1.1,3.6c0.4,1.2,0.8,2.3,1.3,3.5s1.1,2.2,1.7,3.2s1.2,2.1,2,3.1c0.7,1,1.5,2,2.4,2.9
|
||||
c0.8,0.9,1.6,1.8,2.5,2.7s1.8,1.5,2.8,2.3c1.1,0.8,2.2,1.6,3.3,2.3c0.4,0.3,0.8,0.6,1.2,0.9s0.9,0.3,1.4,0.6c2,1,4.1,1.9,6.2,2.5
|
||||
c0.8,0.2,1.5,0.5,2.3,0.7c2.8,0.7,5.7,1.1,8.7,1.1c0,0,0,0,0.1,0h0.1h17c0.1,0,0.2,0,0.2,0l0,0l663.9-0.9
|
||||
c17.6,30.5,50.4,88,78.3,136.9c21.4,37.6,39.2,68.6,53.2,93h-988c-0.4,0-0.8,0.1-1.3,0.2c-13.9,0.1-26.6,7.9-33.2,20.1l-163.6,306.6
|
||||
H3.1C120.8,1119.3,730.7,25,738.6,11z M1079.4,39.1l24,39.5c42.1,69.5,101.6,167.6,107.9,177.8c2,3.2,2.9,11.1-1.3,18.7L975.6,697.3
|
||||
l-264,0.3L1079.4,39.1z M1529.1,486.9l131.3,233.3l-133.9,247.5c-16-27.8-35.8-62.6-54.4-95.2c-36.8-64.4-63.6-111.3-78.7-137.5
|
||||
C1422.1,682.8,1497.5,544.5,1529.1,486.9z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/img/icann.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
assets/img/icon-128x128.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/img/icon-144x144.png
Executable file
After Width: | Height: | Size: 5.1 KiB |
BIN
assets/img/icon-152x152.png
Executable file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/img/icon-192x192.png
Executable file
After Width: | Height: | Size: 7.0 KiB |
BIN
assets/img/icon-384x384.png
Executable file
After Width: | Height: | Size: 11 KiB |
BIN
assets/img/icon-48x48.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/img/icon-512x512.png
Executable file
After Width: | Height: | Size: 21 KiB |
BIN
assets/img/icon-72x72.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/img/icon-96x96.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/img/icons/arrow.png
Executable file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/img/icons/audio.gif
Executable file
After Width: | Height: | Size: 243 KiB |
BIN
assets/img/icons/check.png
Executable file
After Width: | Height: | Size: 666 B |
BIN
assets/img/icons/clipboard.png
Executable file
After Width: | Height: | Size: 661 B |
BIN
assets/img/icons/close.png
Executable file
After Width: | Height: | Size: 744 B |
BIN
assets/img/icons/compose.png
Executable file
After Width: | Height: | Size: 919 B |
BIN
assets/img/icons/edit.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/img/icons/emoji.png
Executable file
After Width: | Height: | Size: 4.0 KiB |
BIN
assets/img/icons/fail.png
Executable file
After Width: | Height: | Size: 577 B |
BIN
assets/img/icons/fish.png
Executable file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/img/icons/gif.png
Executable file
After Width: | Height: | Size: 1.1 KiB |
BIN
assets/img/icons/leave.png
Executable file
After Width: | Height: | Size: 2.3 KiB |
BIN
assets/img/icons/lock.png
Executable file
After Width: | Height: | Size: 859 B |
BIN
assets/img/icons/mention.png
Executable file
After Width: | Height: | Size: 4.6 KiB |
BIN
assets/img/icons/menu.png
Executable file
After Width: | Height: | Size: 325 B |
BIN
assets/img/icons/message.png
Executable file
After Width: | Height: | Size: 2.9 KiB |
BIN
assets/img/icons/pay.png
Executable file
After Width: | Height: | Size: 3.8 KiB |
BIN
assets/img/icons/pin.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/img/icons/plus.png
Executable file
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/img/icons/replay.png
Executable file
After Width: | Height: | Size: 3.7 KiB |
BIN
assets/img/icons/save.png
Executable file
After Width: | Height: | Size: 667 B |
BIN
assets/img/icons/screen.png
Executable file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/img/icons/search.png
Executable file
After Width: | Height: | Size: 1.6 KiB |
BIN
assets/img/icons/signature.png
Executable file
After Width: | Height: | Size: 3.0 KiB |
BIN
assets/img/icons/trash.png
Executable file
After Width: | Height: | Size: 2.5 KiB |
BIN
assets/img/icons/update.png
Executable file
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/img/icons/users.png
Executable file
After Width: | Height: | Size: 1.9 KiB |
BIN
assets/img/icons/video.png
Executable file
After Width: | Height: | Size: 1.8 KiB |
BIN
assets/img/icons/view.png
Executable file
After Width: | Height: | Size: 3.5 KiB |
BIN
assets/img/icons/voice.png
Executable file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/img/icons/warning.png
Executable file
After Width: | Height: | Size: 1.0 KiB |
BIN
assets/img/logo.png
Executable file
After Width: | Height: | Size: 21 KiB |
5
assets/img/unstoppable.svg
Executable file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg viewBox="0.3 0.234 43.492 39" width="43.492" height="39" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M 43.792 0.876 L 43.792 15.682 L 0.3 33.177 L 43.792 0.876 Z" fill="#00C9FF"/>
|
||||
<path d="M 35.639 0.234 L 35.639 25.775 C 35.639 29.345 34.207 32.768 31.658 35.292 C 29.109 37.816 25.652 39.234 22.047 39.234 C 18.442 39.234 14.985 37.816 12.436 35.292 C 9.887 32.768 8.455 29.345 8.455 25.775 L 8.455 15.013 L 16.609 10.569 L 16.609 25.775 C 16.532 26.439 16.598 27.111 16.803 27.748 C 17.007 28.384 17.345 28.971 17.794 29.469 C 18.243 29.967 18.793 30.366 19.409 30.639 C 20.025 30.912 20.692 31.053 21.366 31.053 C 22.041 31.053 22.708 30.912 23.323 30.639 C 23.939 30.366 24.489 29.967 24.938 29.469 C 25.388 28.971 25.725 28.384 25.93 27.748 C 26.134 27.111 26.2 26.439 26.124 25.775 L 26.124 5.388 L 35.639 0.234 Z" fill="#0D67FE"/>
|
||||
</svg>
|
After Width: | Height: | Size: 894 B |
1
assets/js/adapter.js
Executable file
1
assets/js/anchorme.js
Executable file
130
assets/js/confetti.js
Executable file
@ -0,0 +1,130 @@
|
||||
var maxParticleCount = 150; //set max confetti count
|
||||
var particleSpeed = 2; //set the particle animation speed
|
||||
var startConfetti; //call to start confetti animation
|
||||
var stopConfetti; //call to stop adding confetti
|
||||
var toggleConfetti; //call to start or stop the confetti animation depending on whether it's already running
|
||||
var removeConfetti; //call to stop the confetti animation and remove all confetti immediately
|
||||
|
||||
(function() {
|
||||
startConfetti = startConfettiInner;
|
||||
stopConfetti = stopConfettiInner;
|
||||
toggleConfetti = toggleConfettiInner;
|
||||
removeConfetti = removeConfettiInner;
|
||||
var colors = ["DodgerBlue", "OliveDrab", "Gold", "Pink", "SlateBlue", "LightBlue", "Violet", "PaleGreen", "SteelBlue", "SandyBrown", "Chocolate", "Crimson"]
|
||||
var streamingConfetti = false;
|
||||
var animationTimer = null;
|
||||
var particles = [];
|
||||
var waveAngle = 0;
|
||||
|
||||
function resetParticle(particle, width, height) {
|
||||
particle.color = colors[(Math.random() * colors.length) | 0];
|
||||
particle.x = Math.random() * width;
|
||||
particle.y = Math.random() * height - height;
|
||||
particle.diameter = Math.random() * 10 + 5;
|
||||
particle.tilt = Math.random() * 10 - 10;
|
||||
particle.tiltAngleIncrement = Math.random() * 0.07 + 0.05;
|
||||
particle.tiltAngle = 0;
|
||||
return particle;
|
||||
}
|
||||
|
||||
function startConfettiInner() {
|
||||
var width = window.innerWidth;
|
||||
var height = window.innerHeight;
|
||||
window.requestAnimFrame = (function() {
|
||||
return window.requestAnimationFrame ||
|
||||
window.webkitRequestAnimationFrame ||
|
||||
window.mozRequestAnimationFrame ||
|
||||
window.oRequestAnimationFrame ||
|
||||
window.msRequestAnimationFrame ||
|
||||
function (callback) {
|
||||
return window.setTimeout(callback, 16.6666667);
|
||||
};
|
||||
})();
|
||||
var canvas = document.getElementById("confetti-canvas");
|
||||
if (canvas === null) {
|
||||
canvas = document.createElement("canvas");
|
||||
canvas.setAttribute("id", "confetti-canvas");
|
||||
canvas.setAttribute("style", "display:block;z-index:999999;pointer-events:none");
|
||||
document.body.appendChild(canvas);
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
window.addEventListener("resize", function() {
|
||||
canvas.width = window.innerWidth;
|
||||
canvas.height = window.innerHeight;
|
||||
}, true);
|
||||
}
|
||||
var context = canvas.getContext("2d");
|
||||
while (particles.length < maxParticleCount)
|
||||
particles.push(resetParticle({}, width, height));
|
||||
streamingConfetti = true;
|
||||
if (animationTimer === null) {
|
||||
(function runAnimation() {
|
||||
context.clearRect(0, 0, window.innerWidth, window.innerHeight);
|
||||
if (particles.length === 0)
|
||||
animationTimer = null;
|
||||
else {
|
||||
updateParticles();
|
||||
drawParticles(context);
|
||||
animationTimer = requestAnimFrame(runAnimation);
|
||||
}
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
function stopConfettiInner() {
|
||||
streamingConfetti = false;
|
||||
}
|
||||
|
||||
function removeConfettiInner() {
|
||||
stopConfetti();
|
||||
particles = [];
|
||||
}
|
||||
|
||||
function toggleConfettiInner() {
|
||||
if (streamingConfetti)
|
||||
stopConfettiInner();
|
||||
else
|
||||
startConfettiInner();
|
||||
}
|
||||
|
||||
function drawParticles(context) {
|
||||
var particle;
|
||||
var x;
|
||||
for (var i = 0; i < particles.length; i++) {
|
||||
particle = particles[i];
|
||||
context.beginPath();
|
||||
context.lineWidth = particle.diameter;
|
||||
context.strokeStyle = particle.color;
|
||||
x = particle.x + particle.tilt;
|
||||
context.moveTo(x + particle.diameter / 2, particle.y);
|
||||
context.lineTo(x, particle.y + particle.tilt + particle.diameter / 2);
|
||||
context.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function updateParticles() {
|
||||
var width = window.innerWidth;
|
||||
var height = window.innerHeight;
|
||||
var particle;
|
||||
waveAngle += 0.01;
|
||||
for (var i = 0; i < particles.length; i++) {
|
||||
particle = particles[i];
|
||||
if (!streamingConfetti && particle.y < -15)
|
||||
particle.y = height + 100;
|
||||
else {
|
||||
particle.tiltAngle += particle.tiltAngleIncrement;
|
||||
particle.x += Math.sin(waveAngle);
|
||||
particle.y += (Math.cos(waveAngle) + particle.diameter + particleSpeed) * 0.5;
|
||||
particle.tilt = Math.sin(particle.tiltAngle) * 15;
|
||||
}
|
||||
if (particle.x > width + 20 || particle.x < -20 || particle.y > height) {
|
||||
if (streamingConfetti && particles.length <= maxParticleCount)
|
||||
resetParticle(particle, width, height);
|
||||
else {
|
||||
particles.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
94
assets/js/date.js
Executable file
@ -0,0 +1,94 @@
|
||||
/* eslint no-extend-native: 0 */
|
||||
|
||||
(function () {
|
||||
// Defining locale
|
||||
Date.shortMonths = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
Date.longMonths = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
|
||||
Date.shortDays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
Date.longDays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
|
||||
// Defining patterns
|
||||
var replaceChars = {
|
||||
// Day
|
||||
d: function () { var d = this.getDate(); return (d < 10 ? '0' : '') + d },
|
||||
D: function () { return Date.shortDays[this.getDay()] },
|
||||
j: function () { return this.getDate() },
|
||||
l: function () { return Date.longDays[this.getDay()] },
|
||||
N: function () { var N = this.getDay(); return (N === 0 ? 7 : N) },
|
||||
S: function () { var S = this.getDate(); return (S % 10 === 1 && S !== 11 ? 'st' : (S % 10 === 2 && S !== 12 ? 'nd' : (S % 10 === 3 && S !== 13 ? 'rd' : 'th'))) },
|
||||
w: function () { return this.getDay() },
|
||||
z: function () { var d = new Date(this.getFullYear(), 0, 1); return Math.ceil((this - d) / 86400000) },
|
||||
// Week
|
||||
W: function () {
|
||||
var target = new Date(this.valueOf())
|
||||
var dayNr = (this.getDay() + 6) % 7
|
||||
target.setDate(target.getDate() - dayNr + 3)
|
||||
var firstThursday = target.valueOf()
|
||||
target.setMonth(0, 1)
|
||||
if (target.getDay() !== 4) {
|
||||
target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7)
|
||||
}
|
||||
var retVal = 1 + Math.ceil((firstThursday - target) / 604800000)
|
||||
|
||||
return (retVal < 10 ? '0' + retVal : retVal)
|
||||
},
|
||||
// Month
|
||||
F: function () { return Date.longMonths[this.getMonth()] },
|
||||
m: function () { var m = this.getMonth(); return (m < 9 ? '0' : '') + (m + 1) },
|
||||
M: function () { return Date.shortMonths[this.getMonth()] },
|
||||
n: function () { return this.getMonth() + 1 },
|
||||
t: function () {
|
||||
var year = this.getFullYear()
|
||||
var nextMonth = this.getMonth() + 1
|
||||
if (nextMonth === 12) {
|
||||
year = year++
|
||||
nextMonth = 0
|
||||
}
|
||||
return new Date(year, nextMonth, 0).getDate()
|
||||
},
|
||||
// Year
|
||||
L: function () { var L = this.getFullYear(); return (L % 400 === 0 || (L % 100 !== 0 && L % 4 === 0)) },
|
||||
o: function () { var d = new Date(this.valueOf()); d.setDate(d.getDate() - ((this.getDay() + 6) % 7) + 3); return d.getFullYear() },
|
||||
Y: function () { return this.getFullYear() },
|
||||
y: function () { return ('' + this.getFullYear()).substr(2) },
|
||||
// Time
|
||||
a: function () { return this.getHours() < 12 ? 'am' : 'pm' },
|
||||
A: function () { return this.getHours() < 12 ? 'AM' : 'PM' },
|
||||
B: function () { return Math.floor((((this.getUTCHours() + 1) % 24) + this.getUTCMinutes() / 60 + this.getUTCSeconds() / 3600) * 1000 / 24) },
|
||||
g: function () { return this.getHours() % 12 || 12 },
|
||||
G: function () { return this.getHours() },
|
||||
h: function () { var h = this.getHours(); return ((h % 12 || 12) < 10 ? '0' : '') + (h % 12 || 12) },
|
||||
H: function () { var H = this.getHours(); return (H < 10 ? '0' : '') + H },
|
||||
i: function () { var i = this.getMinutes(); return (i < 10 ? '0' : '') + i },
|
||||
s: function () { var s = this.getSeconds(); return (s < 10 ? '0' : '') + s },
|
||||
v: function () { var v = this.getMilliseconds(); return (v < 10 ? '00' : (v < 100 ? '0' : '')) + v },
|
||||
// Timezone
|
||||
e: function () { return Intl.DateTimeFormat().resolvedOptions().timeZone },
|
||||
I: function () {
|
||||
var DST = null
|
||||
for (var i = 0; i < 12; ++i) {
|
||||
var d = new Date(this.getFullYear(), i, 1)
|
||||
var offset = d.getTimezoneOffset()
|
||||
|
||||
if (DST === null) DST = offset
|
||||
else if (offset < DST) { DST = offset; break } else if (offset > DST) break
|
||||
}
|
||||
return (this.getTimezoneOffset() === DST) | 0
|
||||
},
|
||||
O: function () { var O = this.getTimezoneOffset(); return (-O < 0 ? '-' : '+') + (Math.abs(O / 60) < 10 ? '0' : '') + Math.floor(Math.abs(O / 60)) + (Math.abs(O % 60) === 0 ? '00' : ((Math.abs(O % 60) < 10 ? '0' : '')) + (Math.abs(O % 60))) },
|
||||
P: function () { var P = this.getTimezoneOffset(); return (-P < 0 ? '-' : '+') + (Math.abs(P / 60) < 10 ? '0' : '') + Math.floor(Math.abs(P / 60)) + ':' + (Math.abs(P % 60) === 0 ? '00' : ((Math.abs(P % 60) < 10 ? '0' : '')) + (Math.abs(P % 60))) },
|
||||
T: function () { var tz = this.toLocaleTimeString(navigator.language, {timeZoneName: 'short'}).split(' '); return tz[tz.length - 1] },
|
||||
Z: function () { return -this.getTimezoneOffset() * 60 },
|
||||
// Full Date/Time
|
||||
c: function () { return this.format('Y-m-d\\TH:i:sP') },
|
||||
r: function () { return this.toString() },
|
||||
U: function () { return Math.floor(this.getTime() / 1000) }
|
||||
}
|
||||
|
||||
// Simulates PHP's date function
|
||||
Date.prototype.format = function (format) {
|
||||
var date = this
|
||||
return format.replace(/(\\?)(.)/g, function (_, esc, chr) {
|
||||
return (esc === '' && replaceChars[chr]) ? replaceChars[chr].call(date) : chr
|
||||
})
|
||||
}
|
||||
}).call(this);
|
275
assets/js/dish.js
Executable file
@ -0,0 +1,275 @@
|
||||
class Dish {
|
||||
|
||||
// ratios
|
||||
_ratios = ['4:3', '16:9', '1:1', '1:2']
|
||||
|
||||
// default options
|
||||
_dish = false
|
||||
_conference = false
|
||||
_cameras = 0
|
||||
_margin = 5
|
||||
_aspect = 2
|
||||
_video = false;
|
||||
_ratio = this.ratio() // to perfomance call here
|
||||
|
||||
// create dish
|
||||
constructor(scenary) {
|
||||
|
||||
// parent space to render dish
|
||||
this._scenary = scenary
|
||||
|
||||
// create the conference and dish
|
||||
this.create()
|
||||
|
||||
// render cameras
|
||||
this.render()
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// create Dish
|
||||
create() {
|
||||
|
||||
// create conference (dish and screen container)
|
||||
this._conference = document.createElement('div');
|
||||
this._conference.classList.add('videoHolder');
|
||||
|
||||
// create dish (cameras container)
|
||||
this._dish = document.createElement('div');
|
||||
this._dish.classList.add('cams');
|
||||
|
||||
let screen = document.createElement('div');
|
||||
screen.classList.add('screen');
|
||||
|
||||
let info = document.createElement('div');
|
||||
info.classList.add("info");
|
||||
screen.append(info);
|
||||
|
||||
let table = document.createElement('table');
|
||||
info.append(table);
|
||||
|
||||
let video = document.createElement('video');
|
||||
video.setAttribute("autoplay", "");
|
||||
video.setAttribute("playsinline", "");
|
||||
video.muted = true;
|
||||
screen.append(video);
|
||||
|
||||
// append first to scenary
|
||||
this._conference.prepend(screen);
|
||||
|
||||
//this.expand();
|
||||
|
||||
// append dish to conference
|
||||
this._conference.appendChild(this._dish);
|
||||
|
||||
}
|
||||
|
||||
// set dish in scenary
|
||||
append() {
|
||||
|
||||
// append to scenary
|
||||
this._scenary.appendChild(this._conference);
|
||||
|
||||
}
|
||||
|
||||
// calculate dimensions
|
||||
dimensions() {
|
||||
this._width = this._dish.offsetWidth - (this._margin * 2);
|
||||
this._height = this._dish.offsetHeight - (this._margin * 2);
|
||||
}
|
||||
|
||||
// render cameras of dish
|
||||
render() {
|
||||
|
||||
// delete cameras (only those that are left over)
|
||||
if (this._dish.children) {
|
||||
for (let i = this._cameras; i < this._dish.children.length; i++) {
|
||||
let Camera = this._dish.children[i]
|
||||
this._dish.removeChild(Camera);
|
||||
}
|
||||
}
|
||||
|
||||
// add cameras (only the necessary ones)
|
||||
for (let i = this._dish.children.length; i < this._cameras; i++) {
|
||||
let Camera = document.createElement('div')
|
||||
this._dish.appendChild(Camera);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// resizer of cameras
|
||||
resizer(width) {
|
||||
|
||||
for (var s = 0; s < this._dish.children.length; s++) {
|
||||
|
||||
// camera fron dish (div without class)
|
||||
let element = this._dish.children[s];
|
||||
|
||||
// custom margin
|
||||
element.style.margin = this._margin + "px"
|
||||
|
||||
// calculate dimensions
|
||||
element.style.width = width + "px"
|
||||
element.style.height = (width * this._ratio) + "px"
|
||||
|
||||
// to show the aspect ratio in demo (optional)
|
||||
element.setAttribute('data-aspect', this._ratios[this._aspect]);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
resize() {
|
||||
|
||||
// get dimensions of dish
|
||||
this.dimensions()
|
||||
|
||||
// loop (i recommend you optimize this)
|
||||
let max = 0
|
||||
let i = 1
|
||||
while (i < 5000) {
|
||||
let area = this.area(i);
|
||||
if (area === false) {
|
||||
max = i - 1;
|
||||
break;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
// remove margins
|
||||
max = max - (this._margin * 2);
|
||||
|
||||
// set dimensions to all cameras
|
||||
this.resizer(max);
|
||||
}
|
||||
|
||||
// split aspect ratio (format n:n)
|
||||
ratio() {
|
||||
var ratio = this._ratios[this._aspect].split(":");
|
||||
return ratio[1] / ratio[0];
|
||||
}
|
||||
|
||||
// calculate area of dish:
|
||||
area(increment) {
|
||||
|
||||
let i = 0;
|
||||
let w = 0;
|
||||
let h = increment * this._ratio + (this._margin * 2);
|
||||
while (i < (this._dish.children.length)) {
|
||||
if ((w + increment) > this._width) {
|
||||
w = 0;
|
||||
h = h + (increment * this._ratio) + (this._margin * 2);
|
||||
}
|
||||
w = w + increment + (this._margin * 2);
|
||||
i++;
|
||||
}
|
||||
if (h > this._height || increment > this._width) return false;
|
||||
else return increment;
|
||||
|
||||
}
|
||||
|
||||
// add new camera
|
||||
add() {
|
||||
this._cameras++;
|
||||
this.render();
|
||||
this.resize();
|
||||
}
|
||||
|
||||
// remove last camera
|
||||
delete() {
|
||||
this._cameras--;
|
||||
this.render();
|
||||
this.resize();
|
||||
}
|
||||
|
||||
// return ratios
|
||||
ratios() {
|
||||
return this._ratios;
|
||||
}
|
||||
|
||||
// return cameras
|
||||
cameras() {
|
||||
return this._cameras;
|
||||
}
|
||||
|
||||
// set ratio
|
||||
aspect(i) {
|
||||
this._aspect = i;
|
||||
this._ratio = this.ratio()
|
||||
this.resize();
|
||||
}
|
||||
|
||||
// set screen scenary
|
||||
/*
|
||||
expand() {
|
||||
|
||||
|
||||
// detect screen exist
|
||||
let screens = this._conference.querySelector('.screen');
|
||||
if (screens) {
|
||||
|
||||
// remove screen
|
||||
this._conference.removeChild(screens);
|
||||
|
||||
} else {
|
||||
|
||||
// add div to scenary
|
||||
let screen = document.createElement('div');
|
||||
screen.classList.add('screen');
|
||||
|
||||
let table = document.createElement('table');
|
||||
screen.append(table);
|
||||
|
||||
let video = document.createElement('video');
|
||||
video.setAttribute("autoplay", "");
|
||||
video.setAttribute("playsinline", "");
|
||||
video.muted = true;
|
||||
screen.append(video);
|
||||
|
||||
// append first to scenary
|
||||
this._conference.prepend(screen);
|
||||
|
||||
}
|
||||
this.resize();
|
||||
}
|
||||
*/
|
||||
|
||||
video(camera, callback, hide = false) {
|
||||
|
||||
// check have video
|
||||
if (this._dish.children[camera].video) {
|
||||
if (hide) {
|
||||
// delete video:
|
||||
this._dish.children[camera].video = false
|
||||
let videos = this._dish.children[camera].querySelectorAll('video');
|
||||
this._dish.children[camera].removeChild(videos[0]);
|
||||
}
|
||||
} else {
|
||||
// set video
|
||||
this._dish.children[camera].video = true
|
||||
|
||||
// create video
|
||||
let video = document.createElement('video');
|
||||
video.classList.add('loading');
|
||||
|
||||
// random number 1-5
|
||||
let filename = 'demo.mp4';
|
||||
video.src = `./videos/${filename}`;
|
||||
video.autoplay = true;
|
||||
video.loop = true;
|
||||
video.muted = true;
|
||||
video.playsinline = true;
|
||||
video.controls = false;
|
||||
|
||||
// event to show video
|
||||
video.addEventListener('loadedmetadata', function () {
|
||||
callback(video);
|
||||
}, false);
|
||||
|
||||
// append video to camera
|
||||
this._dish.children[camera].appendChild(video);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
124
assets/js/e2ee.js
Executable file
@ -0,0 +1,124 @@
|
||||
export class e2ee {
|
||||
async generateKeys() {
|
||||
const keyPair = await window.crypto.subtle.generateKey(
|
||||
{
|
||||
name: "ECDH",
|
||||
namedCurve: "P-256",
|
||||
},
|
||||
true,
|
||||
["deriveKey", "deriveBits"]
|
||||
);
|
||||
|
||||
const publicKeyJwk = await window.crypto.subtle.exportKey(
|
||||
"jwk",
|
||||
keyPair.publicKey
|
||||
);
|
||||
|
||||
const privateKeyJwk = await window.crypto.subtle.exportKey(
|
||||
"jwk",
|
||||
keyPair.privateKey
|
||||
);
|
||||
|
||||
return { publicKeyJwk, privateKeyJwk };
|
||||
}
|
||||
|
||||
async importKey(x, y, d) {
|
||||
const privateKey = await window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
{
|
||||
kty: "EC",
|
||||
crv: "P-256",
|
||||
x: x,
|
||||
y: y,
|
||||
d: d,
|
||||
ext: true,
|
||||
},
|
||||
{
|
||||
name: "ECDH",
|
||||
namedCurve: "P-256",
|
||||
},
|
||||
true,
|
||||
["deriveKey", "deriveBits"]
|
||||
);
|
||||
|
||||
const privateKeyJwk = await window.crypto.subtle.exportKey(
|
||||
"jwk",
|
||||
privateKey
|
||||
);
|
||||
|
||||
return privateKeyJwk;
|
||||
}
|
||||
|
||||
async deriveKey(publicKeyJwk, privateKeyJwk) {
|
||||
const publicKey = await window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
publicKeyJwk,
|
||||
{
|
||||
name: "ECDH",
|
||||
namedCurve: "P-256",
|
||||
},
|
||||
true,
|
||||
[]
|
||||
);
|
||||
|
||||
const privateKey = await window.crypto.subtle.importKey(
|
||||
"jwk",
|
||||
privateKeyJwk,
|
||||
{
|
||||
name: "ECDH",
|
||||
namedCurve: "P-256",
|
||||
},
|
||||
true,
|
||||
["deriveKey", "deriveBits"]
|
||||
);
|
||||
|
||||
return await window.crypto.subtle.deriveKey(
|
||||
{ name: "ECDH", public: publicKey },
|
||||
privateKey,
|
||||
{ name: "AES-GCM", length: 256 },
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
};
|
||||
|
||||
async encryptMessage(text, derivedKey, conversation) {
|
||||
const encodedText = new TextEncoder().encode(text);
|
||||
|
||||
const encryptedData = await window.crypto.subtle.encrypt(
|
||||
{ name: "AES-GCM", iv: new TextEncoder().encode(conversation) },
|
||||
derivedKey,
|
||||
encodedText
|
||||
);
|
||||
|
||||
const uintArray = new Uint8Array(encryptedData);
|
||||
const string = String.fromCharCode.apply(null, uintArray);
|
||||
const base64Data = btoa(string);
|
||||
|
||||
return base64Data;
|
||||
};
|
||||
|
||||
async decryptMessage(text, derivedKey, conversation) {
|
||||
try {
|
||||
const initializationVector = new Uint8Array(new TextEncoder().encode(conversation)).buffer;
|
||||
|
||||
const string = atob(text);
|
||||
const uintArray = new Uint8Array(
|
||||
[...string].map((char) => char.charCodeAt(0))
|
||||
);
|
||||
const algorithm = {
|
||||
name: "AES-GCM",
|
||||
iv: initializationVector,
|
||||
};
|
||||
const decryptedData = await window.crypto.subtle.decrypt(
|
||||
algorithm,
|
||||
derivedKey,
|
||||
uintArray
|
||||
);
|
||||
|
||||
return new TextDecoder().decode(decryptedData);
|
||||
}
|
||||
catch {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
}
|
24611
assets/js/emojis.js
Executable file
345
assets/js/he.js
Executable file
1198
assets/js/hl.js
Executable file
3646
assets/js/janus.js
Executable file
1141
assets/js/listeners.js
Executable file
8
assets/js/mask.js
Executable file
320
assets/js/punycode.js
Executable file
@ -0,0 +1,320 @@
|
||||
var punycode = new function Punycode() {
|
||||
// This object converts to and from puny-code used in IDN
|
||||
//
|
||||
// punycode.ToASCII ( domain )
|
||||
//
|
||||
// Returns a puny coded representation of "domain".
|
||||
// It only converts the part of the domain name that
|
||||
// has non ASCII characters. I.e. it dosent matter if
|
||||
// you call it with a domain that already is in ASCII.
|
||||
//
|
||||
// punycode.ToUnicode (domain)
|
||||
//
|
||||
// Converts a puny-coded domain name to unicode.
|
||||
// It only converts the puny-coded parts of the domain name.
|
||||
// I.e. it dosent matter if you call it on a string
|
||||
// that already has been converted to unicode.
|
||||
//
|
||||
//
|
||||
this.utf16 = {
|
||||
// The utf16-class is necessary to convert from javascripts internal character representation to unicode and back.
|
||||
decode:function(input){
|
||||
var output = [], i=0, len=input.length,value,extra;
|
||||
while (i < len) {
|
||||
value = input.charCodeAt(i++);
|
||||
if ((value & 0xF800) === 0xD800) {
|
||||
extra = input.charCodeAt(i++);
|
||||
if ( ((value & 0xFC00) !== 0xD800) || ((extra & 0xFC00) !== 0xDC00) ) {
|
||||
throw new RangeError("UTF-16(decode): Illegal UTF-16 sequence");
|
||||
}
|
||||
value = ((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000;
|
||||
}
|
||||
output.push(value);
|
||||
}
|
||||
return output;
|
||||
},
|
||||
encode:function(input){
|
||||
var output = [], i=0, len=input.length,value;
|
||||
while (i < len) {
|
||||
value = input[i++];
|
||||
if ( (value & 0xF800) === 0xD800 ) {
|
||||
throw new RangeError("UTF-16(encode): Illegal UTF-16 value");
|
||||
}
|
||||
if (value > 0xFFFF) {
|
||||
value -= 0x10000;
|
||||
output.push(String.fromCharCode(((value >>>10) & 0x3FF) | 0xD800));
|
||||
value = 0xDC00 | (value & 0x3FF);
|
||||
}
|
||||
output.push(String.fromCharCode(value));
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
}
|
||||
|
||||
//Default parameters
|
||||
var initial_n = 0x80;
|
||||
var initial_bias = 72;
|
||||
var delimiter = "\x2D";
|
||||
var base = 36;
|
||||
var damp = 700;
|
||||
var tmin=1;
|
||||
var tmax=26;
|
||||
var skew=38;
|
||||
var maxint = 0x7FFFFFFF;
|
||||
|
||||
// decode_digit(cp) returns the numeric value of a basic code
|
||||
// point (for use in representing integers) in the range 0 to
|
||||
// base-1, or base if cp is does not represent a value.
|
||||
|
||||
function decode_digit(cp) {
|
||||
return cp - 48 < 10 ? cp - 22 : cp - 65 < 26 ? cp - 65 : cp - 97 < 26 ? cp - 97 : base;
|
||||
}
|
||||
|
||||
// encode_digit(d,flag) returns the basic code point whose value
|
||||
// (when used for representing integers) is d, which needs to be in
|
||||
// the range 0 to base-1. The lowercase form is used unless flag is
|
||||
// nonzero, in which case the uppercase form is used. The behavior
|
||||
// is undefined if flag is nonzero and digit d has no uppercase form.
|
||||
|
||||
function encode_digit(d, flag) {
|
||||
return d + 22 + 75 * (d < 26) - ((flag != 0) << 5);
|
||||
// 0..25 map to ASCII a..z or A..Z
|
||||
// 26..35 map to ASCII 0..9
|
||||
}
|
||||
//** Bias adaptation function **
|
||||
function adapt(delta, numpoints, firsttime ) {
|
||||
var k;
|
||||
delta = firsttime ? Math.floor(delta / damp) : (delta >> 1);
|
||||
delta += Math.floor(delta / numpoints);
|
||||
|
||||
for (k = 0; delta > (((base - tmin) * tmax) >> 1); k += base) {
|
||||
delta = Math.floor(delta / ( base - tmin ));
|
||||
}
|
||||
return Math.floor(k + (base - tmin + 1) * delta / (delta + skew));
|
||||
}
|
||||
|
||||
// encode_basic(bcp,flag) forces a basic code point to lowercase if flag is zero,
|
||||
// uppercase if flag is nonzero, and returns the resulting code point.
|
||||
// The code point is unchanged if it is caseless.
|
||||
// The behavior is undefined if bcp is not a basic code point.
|
||||
|
||||
function encode_basic(bcp, flag) {
|
||||
bcp -= (bcp - 97 < 26) << 5;
|
||||
return bcp + ((!flag && (bcp - 65 < 26)) << 5);
|
||||
}
|
||||
|
||||
// Main decode
|
||||
this.decode=function(input,preserveCase) {
|
||||
// Dont use utf16
|
||||
var output=[];
|
||||
var case_flags=[];
|
||||
var input_length = input.length;
|
||||
|
||||
var n, out, i, bias, basic, j, ic, oldi, w, k, digit, t, len;
|
||||
|
||||
// Initialize the state:
|
||||
|
||||
n = initial_n;
|
||||
i = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points: Let basic be the number of input code
|
||||
// points before the last delimiter, or 0 if there is none, then
|
||||
// copy the first basic code points to the output.
|
||||
|
||||
basic = input.lastIndexOf(delimiter);
|
||||
if (basic < 0) basic = 0;
|
||||
|
||||
for (j = 0; j < basic; ++j) {
|
||||
if(preserveCase) case_flags[output.length] = ( input.charCodeAt(j) -65 < 26);
|
||||
if ( input.charCodeAt(j) >= 0x80) {
|
||||
throw new RangeError("Illegal input >= 0x80");
|
||||
}
|
||||
output.push( input.charCodeAt(j) );
|
||||
}
|
||||
|
||||
// Main decoding loop: Start just after the last delimiter if any
|
||||
// basic code points were copied; start at the beginning otherwise.
|
||||
|
||||
for (ic = basic > 0 ? basic + 1 : 0; ic < input_length; ) {
|
||||
|
||||
// ic is the index of the next character to be consumed,
|
||||
|
||||
// Decode a generalized variable-length integer into delta,
|
||||
// which gets added to i. The overflow checking is easier
|
||||
// if we increase i as we go, then subtract off its starting
|
||||
// value at the end to obtain delta.
|
||||
for (oldi = i, w = 1, k = base; ; k += base) {
|
||||
if (ic >= input_length) {
|
||||
throw RangeError ("punycode_bad_input(1)");
|
||||
}
|
||||
digit = decode_digit(input.charCodeAt(ic++));
|
||||
|
||||
if (digit >= base) {
|
||||
throw RangeError("punycode_bad_input(2)");
|
||||
}
|
||||
if (digit > Math.floor((maxint - i) / w)) {
|
||||
throw RangeError ("punycode_overflow(1)");
|
||||
}
|
||||
i += digit * w;
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (digit < t) { break; }
|
||||
if (w > Math.floor(maxint / (base - t))) {
|
||||
throw RangeError("punycode_overflow(2)");
|
||||
}
|
||||
w *= (base - t);
|
||||
}
|
||||
|
||||
out = output.length + 1;
|
||||
bias = adapt(i - oldi, out, oldi === 0);
|
||||
|
||||
// i was supposed to wrap around from out to 0,
|
||||
// incrementing n each time, so we'll fix that now:
|
||||
if ( Math.floor(i / out) > maxint - n) {
|
||||
throw RangeError("punycode_overflow(3)");
|
||||
}
|
||||
n += Math.floor( i / out ) ;
|
||||
i %= out;
|
||||
|
||||
// Insert n at position i of the output:
|
||||
// Case of last character determines uppercase flag:
|
||||
if (preserveCase) { case_flags.splice(i, 0, input.charCodeAt(ic -1) -65 < 26);}
|
||||
|
||||
output.splice(i, 0, n);
|
||||
i++;
|
||||
}
|
||||
if (preserveCase) {
|
||||
for (i = 0, len = output.length; i < len; i++) {
|
||||
if (case_flags[i]) {
|
||||
output[i] = (String.fromCharCode(output[i]).toUpperCase()).charCodeAt(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
return this.utf16.encode(output);
|
||||
};
|
||||
|
||||
//** Main encode function **
|
||||
|
||||
this.encode = function (input,preserveCase) {
|
||||
//** Bias adaptation function **
|
||||
|
||||
var n, delta, h, b, bias, j, m, q, k, t, ijv, case_flags;
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step1 of 2: Get a list of the unaltered string
|
||||
case_flags = this.utf16.decode(input);
|
||||
}
|
||||
// Converts the input in UTF-16 to Unicode
|
||||
input = this.utf16.decode(input.toLowerCase());
|
||||
|
||||
var input_length = input.length; // Cache the length
|
||||
|
||||
if (preserveCase) {
|
||||
// Preserve case, step2 of 2: Modify the list to true/false
|
||||
for (j=0; j < input_length; j++) {
|
||||
case_flags[j] = input[j] != case_flags[j];
|
||||
}
|
||||
}
|
||||
|
||||
var output=[];
|
||||
|
||||
|
||||
// Initialize the state:
|
||||
n = initial_n;
|
||||
delta = 0;
|
||||
bias = initial_bias;
|
||||
|
||||
// Handle the basic code points:
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
if ( input[j] < 0x80) {
|
||||
output.push(
|
||||
String.fromCharCode(
|
||||
case_flags ? encode_basic(input[j], case_flags[j]) : input[j]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
h = b = output.length;
|
||||
|
||||
// h is the number of code points that have been handled, b is the
|
||||
// number of basic code points
|
||||
|
||||
if (b > 0) output.push(delimiter);
|
||||
|
||||
// Main encoding loop:
|
||||
//
|
||||
while (h < input_length) {
|
||||
// All non-basic code points < n have been
|
||||
// handled already. Find the next larger one:
|
||||
|
||||
for (m = maxint, j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
if (ijv >= n && ijv < m) m = ijv;
|
||||
}
|
||||
|
||||
// Increase delta enough to advance the decoder's
|
||||
// <n,i> state to <m,0>, but guard against overflow:
|
||||
|
||||
if (m - n > Math.floor((maxint - delta) / (h + 1))) {
|
||||
throw RangeError("punycode_overflow (1)");
|
||||
}
|
||||
delta += (m - n) * (h + 1);
|
||||
n = m;
|
||||
|
||||
for (j = 0; j < input_length; ++j) {
|
||||
ijv = input[j];
|
||||
|
||||
if (ijv < n ) {
|
||||
if (++delta > maxint) return Error("punycode_overflow(2)");
|
||||
}
|
||||
|
||||
if (ijv == n) {
|
||||
// Represent delta as a generalized variable-length integer:
|
||||
for (q = delta, k = base; ; k += base) {
|
||||
t = k <= bias ? tmin : k >= bias + tmax ? tmax : k - bias;
|
||||
if (q < t) break;
|
||||
output.push( String.fromCharCode(encode_digit(t + (q - t) % (base - t), 0)) );
|
||||
q = Math.floor( (q - t) / (base - t) );
|
||||
}
|
||||
output.push( String.fromCharCode(encode_digit(q, preserveCase && case_flags[j] ? 1:0 )));
|
||||
bias = adapt(delta, h + 1, h == b);
|
||||
delta = 0;
|
||||
++h;
|
||||
}
|
||||
}
|
||||
|
||||
++delta, ++n;
|
||||
}
|
||||
return output.join("");
|
||||
}
|
||||
|
||||
this.ToASCII = function ( domain ) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i=0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/[^A-Za-z0-9-]/) ?
|
||||
"xn--" + punycode.encode(s) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
this.ToUnicode = function ( domain ) {
|
||||
var domain_array = domain.split(".");
|
||||
var out = [];
|
||||
for (var i=0; i < domain_array.length; ++i) {
|
||||
var s = domain_array[i];
|
||||
out.push(
|
||||
s.match(/^xn--/) ?
|
||||
punycode.decode(s.slice(4)) :
|
||||
s
|
||||
);
|
||||
}
|
||||
return out.join(".");
|
||||
}
|
||||
}();
|
||||
|
||||
export { punycode };
|
1
assets/js/qr.js
Executable file
10100
assets/js/qr2.js
Executable file
1402
assets/js/script.js
Executable file
492
assets/js/stream.js
Executable file
@ -0,0 +1,492 @@
|
||||
export class stream {
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
|
||||
this.janus;
|
||||
this.sfu;
|
||||
|
||||
this.janus2;
|
||||
this.sfu2;
|
||||
|
||||
this.dish = new Dish($("#videoContainer")[0]);
|
||||
this.dish.append();
|
||||
this.dish.resize();
|
||||
|
||||
this.myid;
|
||||
this.pvtid;
|
||||
this.pvtid2;
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
this.dish.resize();
|
||||
});
|
||||
|
||||
Janus.init({debug: "none"});
|
||||
}
|
||||
|
||||
async init(type=false) {
|
||||
let done = new Promise(resolve => {
|
||||
let instance = "main";
|
||||
let j = this;
|
||||
let janus = j.janus;
|
||||
if (type == "screen") {
|
||||
janus = j.janus2;
|
||||
instance = "screen";
|
||||
}
|
||||
|
||||
if (janus) {
|
||||
resolve();
|
||||
}
|
||||
else {
|
||||
if (type == "screen") {
|
||||
j.janus2 = new Janus({
|
||||
server: j.parent.streamURL,
|
||||
success: () => {
|
||||
j.janus2.attach({
|
||||
plugin: "janus.plugin.videoroom",
|
||||
success: (pluginHandle) => {
|
||||
this.handle(instance, "success", pluginHandle).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
this.handle(instance, "error", error).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
onmessage: (msg, jsep) => {
|
||||
if (["joined", "event"].includes(msg["videoroom"])) {
|
||||
if (msg["videoroom"] == "joined") {
|
||||
this.pvtid2 = msg["private_id"];
|
||||
}
|
||||
this.handle(instance, "message", msg, jsep);
|
||||
}
|
||||
if (jsep) {
|
||||
this.sfu2.handleRemoteJsep({ jsep: jsep });
|
||||
}
|
||||
},
|
||||
onlocalstream: (stream) => {
|
||||
this.handle(instance, "local", stream);
|
||||
j.parent.ui.setMute("toggleScreen", 0);
|
||||
},
|
||||
oncleanup: () => {
|
||||
this.handle(instance, "cleanup", { display: j.parent.domain });
|
||||
j.parent.ui.setMute("toggleScreen", 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
j.janus = new Janus({
|
||||
server: j.parent.streamURL,
|
||||
success: () => {
|
||||
j.janus.attach({
|
||||
plugin: "janus.plugin.videoroom",
|
||||
success: (pluginHandle) => {
|
||||
this.handle(instance, "success", pluginHandle).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
this.handle(instance, "error", error).then(() => {
|
||||
resolve();
|
||||
});
|
||||
},
|
||||
onmessage: (msg, jsep) => {
|
||||
if (["joined", "event", "talking", "stopped-talking"].includes(msg["videoroom"])) {
|
||||
if (msg["videoroom"] == "joined") {
|
||||
this.myid = msg["id"];
|
||||
this.pvtid = msg["private_id"];
|
||||
}
|
||||
this.handle(instance, "message", msg, jsep);
|
||||
}
|
||||
|
||||
if (jsep) {
|
||||
this.sfu.handleRemoteJsep({ jsep: jsep });
|
||||
}
|
||||
},
|
||||
onlocalstream: (stream) => {
|
||||
this.handle(instance, "local", stream);
|
||||
},
|
||||
oncleanup: () => {
|
||||
this.handle(instance, "cleanup", { display: j.parent.domain });
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return await done;
|
||||
}
|
||||
|
||||
subscribe(publisher) {
|
||||
let j = this;
|
||||
let feed;
|
||||
j.janus.attach({
|
||||
plugin: "janus.plugin.videoroom",
|
||||
opaqueId: j.parent.domain,
|
||||
success: (pluginHandle) => {
|
||||
feed = pluginHandle;
|
||||
let message = {
|
||||
request: "join",
|
||||
room: j.parent.conversation,
|
||||
ptype: "subscriber",
|
||||
feed: publisher.id,
|
||||
private_id: j.pvtid
|
||||
};
|
||||
feed.send({ message: message });
|
||||
},
|
||||
onmessage: function(msg, jsep) {
|
||||
if (jsep) {
|
||||
Janus.debug("Handling SDP as well...", jsep);
|
||||
feed.createAnswer({
|
||||
jsep: jsep,
|
||||
media: { audioSend: false, videoSend: false },
|
||||
success: function(jsep) {
|
||||
Janus.debug("Got SDP!", jsep);
|
||||
let message = { request: "start", room: j.parent.conversation };
|
||||
feed.send({ message: message, jsep: jsep });
|
||||
},
|
||||
error: function(error) {
|
||||
Janus.error("WebRTC error:", error);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
onremotestream: (stream) => {
|
||||
publisher.stream = stream;
|
||||
j.handle("video", "remote", publisher);
|
||||
},
|
||||
oncleanup: () => {
|
||||
j.handle("video", "cleanup", publisher);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async handle(instance, type, data=false, jsep=false) {
|
||||
let done = new Promise(resolve => {
|
||||
switch (type) {
|
||||
case "success":
|
||||
if (instance == "screen") {
|
||||
this.sfu2 = data;
|
||||
this.register(this.sfu2);
|
||||
}
|
||||
else {
|
||||
this.sfu = data;
|
||||
if (Object.keys(this.parent.currentVideoUsers()).includes(this.parent.domain)) {
|
||||
this.register(this.sfu);
|
||||
}
|
||||
else {
|
||||
this.publishers(this.sfu);
|
||||
}
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "error":
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "message":
|
||||
switch (data.videoroom) {
|
||||
case "talking":
|
||||
this.talking(data.id, true);
|
||||
break;
|
||||
|
||||
case "stopped-talking":
|
||||
this.talking(data.id, false);
|
||||
break;
|
||||
|
||||
default:
|
||||
$.each(data["publishers"], (k, p) => {
|
||||
if (Object.keys(this.parent.currentVideoUsers()).includes(p.display)) {
|
||||
this.subscribe(p);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "local":
|
||||
if (instance == "screen") {
|
||||
this.attachScreen(this.parent.domain, data);
|
||||
}
|
||||
else {
|
||||
this.addCam(this.parent.domain, this.myid);
|
||||
this.attachVideo(this.parent.domain, data);
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "remote":
|
||||
if (data.audio_codec || "talking" in data) {
|
||||
this.addCam(data.display, data.id);
|
||||
this.attachVideo(data.display, data.stream);
|
||||
}
|
||||
else {
|
||||
this.attachScreen(data.display, data.stream);
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
|
||||
case "cleanup":
|
||||
if ((data.audio_codec || !data.id) && instance !== "screen") {
|
||||
this.removeCam(data.display);
|
||||
}
|
||||
else {
|
||||
this.removeScreen();
|
||||
}
|
||||
resolve();
|
||||
break;
|
||||
}
|
||||
});
|
||||
return await done;
|
||||
}
|
||||
|
||||
getRandomNumber(digit) {
|
||||
return Math.random().toFixed(digit).split('.')[1];
|
||||
}
|
||||
|
||||
register(sfu) {
|
||||
let message = {
|
||||
request: "join",
|
||||
room: this.parent.conversation,
|
||||
ptype: "publisher",
|
||||
id: this.getRandomNumber(16),
|
||||
display: this.parent.domain
|
||||
};
|
||||
sfu.send({ message: message });
|
||||
}
|
||||
|
||||
publishers(sfu) {
|
||||
let message = {
|
||||
request: "listparticipants",
|
||||
room: this.parent.conversation
|
||||
};
|
||||
sfu.send({
|
||||
"message" : message,
|
||||
success: (result) => {
|
||||
$.each(result.participants, (k, p) => {
|
||||
if (Object.keys(this.parent.currentVideoUsers()).includes(p.display)) {
|
||||
this.subscribe(p);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
leave(sfu) {
|
||||
let message = {
|
||||
request: "leave"
|
||||
};
|
||||
sfu.send({ message: message });
|
||||
}
|
||||
|
||||
async publish(type=false) {
|
||||
let sfu = this.sfu;
|
||||
let published = "main";
|
||||
let media = { video: "video", audioRecv: false, videoRecv: false, audioSend: true, videoSend: true };
|
||||
let message = { request: "configure", audio: true, video: true };
|
||||
|
||||
let done = new Promise(resolve => {
|
||||
if (type == "screen") {
|
||||
this.init("screen").then(() => {
|
||||
sfu = this.sfu2;
|
||||
published = "screen";
|
||||
media = { video: "screen", audioRecv: false, videoRecv: false, audioSend: false, videoSend: true };
|
||||
message = { request: "configure", audio: false, video: true };
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
await done;
|
||||
|
||||
sfu.createOffer({
|
||||
media: media,
|
||||
success: (jsep) => {
|
||||
sfu.send({ message: message, jsep: jsep });
|
||||
if (type !== "screen") {
|
||||
sfu.muteVideo();
|
||||
sfu.muteAudio();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
unpublish(type=false) {
|
||||
let message = {
|
||||
request: "unpublish"
|
||||
};
|
||||
if (type) {
|
||||
switch (type) {
|
||||
case "video":
|
||||
if (this.sfu) {
|
||||
this.sfu.send({ message: message });
|
||||
}
|
||||
break;
|
||||
|
||||
case "screen":
|
||||
if (this.sfu2) {
|
||||
this.sfu2.send({ message: message });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.sfu) {
|
||||
this.sfu.send({ message: message });
|
||||
}
|
||||
if (this.sfu2) {
|
||||
this.sfu2.send({ message: message });
|
||||
}
|
||||
|
||||
$(".controls > .button").addClass("muted");
|
||||
}
|
||||
}
|
||||
|
||||
toggleScreen() {
|
||||
let muted = $(".controls .button[data-action=toggleScreen]").hasClass("muted");
|
||||
if (muted) {
|
||||
if (!$(".screen").hasClass("shown")) {
|
||||
this.publish("screen");
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.unpublish("screen");
|
||||
}
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
addCam(id, jid) {
|
||||
if (!$(`.cam[data-id=${id}]`).length) {
|
||||
let html = $(`
|
||||
<div class="cam" data-id="${id}" data-jid="${jid}">
|
||||
<div class="background"></div>
|
||||
<video autoplay playsinline></video>
|
||||
<div class="info">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
let row = $(`#users .users tr[data-id="${id}"]`).clone();
|
||||
row.append($(`
|
||||
<td>
|
||||
<div class="icon voice"></div>
|
||||
</td>
|
||||
`));
|
||||
html.find("table").append(row);
|
||||
|
||||
let row2 = $(`#users .users tr[data-id="${id}"]`).clone();
|
||||
html.find(".background").append(row2);
|
||||
|
||||
if (id == this.parent.domain) {
|
||||
html.find("video")[0].muted = true;
|
||||
html.addClass("me");
|
||||
}
|
||||
|
||||
$(".videoHolder .cams").append(html);
|
||||
}
|
||||
if (id && jid) {
|
||||
this.parent.ui.setData($(`.cam[data-id=${id}]`), "jid", jid);
|
||||
}
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
attachVideo(id, video) {
|
||||
Janus.attachMediaStream($(`.cam[data-id=${id}] video`).get(0), video);
|
||||
this.dish.resize();
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
attachScreen(id, video) {
|
||||
let html = $(`.videoHolder .screen`);
|
||||
let table = html.find("table");
|
||||
|
||||
let row = $(`#users .users tr[data-id="${id}"]`).clone();
|
||||
table.empty();
|
||||
table.append(row);
|
||||
|
||||
let screen = html.find("video").get(0);
|
||||
screen.muted = true;
|
||||
Janus.attachMediaStream(screen, video);
|
||||
$(".screen").addClass("shown");
|
||||
this.dish.resize();
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
removeCam(id) {
|
||||
$(`.cam[data-id=${id}]`).remove();
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
removeScreen() {
|
||||
$(".screen").removeClass("shown");
|
||||
this.dish.resize();
|
||||
}
|
||||
|
||||
mute(type, bool) {
|
||||
switch (type) {
|
||||
case "audio":
|
||||
if (bool) {
|
||||
this.sfu.muteAudio();
|
||||
}
|
||||
else {
|
||||
this.sfu.unmuteAudio();
|
||||
}
|
||||
break;
|
||||
|
||||
case "video":
|
||||
if (bool) {
|
||||
this.sfu.muteVideo();
|
||||
}
|
||||
else {
|
||||
this.sfu.unmuteVideo();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
talking(id, bool) {
|
||||
if (bool) {
|
||||
$(`.cam[data-jid=${id}]`).addClass("talking");
|
||||
}
|
||||
else {
|
||||
$(`.cam[data-jid=${id}]`).removeClass("talking");
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
$(".cam").remove();
|
||||
|
||||
if (this.janus2) {
|
||||
this.leave(this.sfu2);
|
||||
this.sfu2 = null;
|
||||
this.janus2 = null;
|
||||
}
|
||||
|
||||
if (this.janus) {
|
||||
this.leave(this.sfu);
|
||||
this.sfu = null;
|
||||
this.janus = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
3156
assets/js/ui.js
Executable file
68
assets/js/ws.js
Executable file
@ -0,0 +1,68 @@
|
||||
export class ws {
|
||||
constructor(parent) {
|
||||
this.parent = parent;
|
||||
|
||||
this.pingTimer;
|
||||
this.typing;
|
||||
}
|
||||
|
||||
async connect() {
|
||||
let connected = new Promise(resolve => {
|
||||
this.socket = new WebSocket(`wss://${window.location.host}/wss`);
|
||||
|
||||
this.socket.onopen = (e) => {
|
||||
this.logMessage("CONNECTED");
|
||||
this.identify();
|
||||
|
||||
this.pingTimer = setInterval(() => {
|
||||
this.sendPing();
|
||||
}, 30000);
|
||||
|
||||
this.typing = setInterval(() => {
|
||||
this.parent.sendTyping();
|
||||
this.parent.updateTypingStatus();
|
||||
this.parent.ui.updateTypingView();
|
||||
}, 250);
|
||||
|
||||
resolve();
|
||||
}
|
||||
|
||||
this.socket.onclose = (e) => {
|
||||
this.logMessage("DISCONNECTED");
|
||||
clearInterval(this.pingTimer);
|
||||
|
||||
this.parent.endAllVideo();
|
||||
this.parent.ready(false);
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
this.socket.onmessage = (e) => {
|
||||
this.logMessage(`IN: ${e.data}`);
|
||||
this.parent.message(e.data);
|
||||
}
|
||||
});
|
||||
|
||||
return await connected;
|
||||
}
|
||||
|
||||
sendPing() {
|
||||
this.send(`PING`);
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.logMessage(`OUT: ${message}`);
|
||||
this.socket.send(message);
|
||||
}
|
||||
|
||||
identify() {
|
||||
this.send(`IDENTIFY ${this.parent.session}`);
|
||||
}
|
||||
|
||||
logMessage(message) {
|
||||
if (this.parent.settings.debug) {
|
||||
console.log(message);
|
||||
}
|
||||
}
|
||||
}
|
279
assets/js/zwj.js
Executable file
@ -0,0 +1,279 @@
|
||||
function nameToUnicode(unicode) {
|
||||
const skinColors = ["🏻", "🏼", "🏽", "🏾", "🏿"];
|
||||
const tonedEmojis = [
|
||||
"❤",
|
||||
"💋",
|
||||
"😶",
|
||||
"😮",
|
||||
"😵",
|
||||
"👶",
|
||||
"🧒",
|
||||
"👦",
|
||||
"👧",
|
||||
"🧑",
|
||||
"👱",
|
||||
"👨",
|
||||
"🧔",
|
||||
"👨🦰",
|
||||
"👨🦱",
|
||||
"👨🦳",
|
||||
"👨🦲",
|
||||
"👩",
|
||||
"👩🦰",
|
||||
"🧑🦰",
|
||||
"👩🦱",
|
||||
"🧑🦱",
|
||||
"👩🦳",
|
||||
"🧑🦳",
|
||||
"👩🦲",
|
||||
"🧑🦲",
|
||||
"🧓",
|
||||
"👴",
|
||||
"👵",
|
||||
"🙍",
|
||||
"🙎",
|
||||
"🙅",
|
||||
"🙆",
|
||||
"💁",
|
||||
"🙋",
|
||||
"🧏",
|
||||
"🙇",
|
||||
"🤦",
|
||||
"🤷",
|
||||
"🧑🎓",
|
||||
"👨🎓",
|
||||
"👩🎓",
|
||||
"🧑🏫",
|
||||
"👨🏫",
|
||||
"👩🏫",
|
||||
"🧑🌾",
|
||||
"👨🌾",
|
||||
"👩🌾",
|
||||
"🧑🍳",
|
||||
"👨🍳",
|
||||
"👩🍳",
|
||||
"🧑🔧",
|
||||
"👨🔧",
|
||||
"👩🔧",
|
||||
"🧑🏭",
|
||||
"👨🏭",
|
||||
"👩🏭",
|
||||
"🧑💼",
|
||||
"👨💼",
|
||||
"👩💼",
|
||||
"🧑🔬",
|
||||
"👨🔬",
|
||||
"👩🔬",
|
||||
"🧑💻",
|
||||
"👨💻",
|
||||
"👩💻",
|
||||
"🧑🎤",
|
||||
"👨🎤",
|
||||
"👩🎤",
|
||||
"🧑🎨",
|
||||
"👨🎨",
|
||||
"👩🎨",
|
||||
"🧑✈",
|
||||
"👨✈",
|
||||
"👩✈",
|
||||
"🧑🚀",
|
||||
"👨🚀",
|
||||
"👩🚀",
|
||||
"🧑🚒",
|
||||
"👨🚒",
|
||||
"👩🚒",
|
||||
"👮",
|
||||
"🕵",
|
||||
"💂",
|
||||
"🥷",
|
||||
"👷",
|
||||
"🤴",
|
||||
"👸",
|
||||
"👳",
|
||||
"👲",
|
||||
"🧕",
|
||||
"🤵",
|
||||
"👰",
|
||||
"🤰",
|
||||
"🤱",
|
||||
"👩🍼",
|
||||
"👨🍼",
|
||||
"🧑🍼",
|
||||
"👼",
|
||||
"🎅",
|
||||
"🤶",
|
||||
"🧑🎄",
|
||||
"🦸",
|
||||
"🦹",
|
||||
"🧙",
|
||||
"🧚",
|
||||
"🧛",
|
||||
"🧜",
|
||||
"🧝",
|
||||
"🧞",
|
||||
"🧟",
|
||||
"💆",
|
||||
"💇",
|
||||
"🫅",
|
||||
"🫃",
|
||||
"🫄",
|
||||
"🚶",
|
||||
"🧍",
|
||||
"🧎",
|
||||
"🧑🦯",
|
||||
"👨🦯",
|
||||
"👩🦯",
|
||||
"🧑🦼",
|
||||
"👨🦼",
|
||||
"👩🦼",
|
||||
"🧑🦽",
|
||||
"👨🦽",
|
||||
"👩🦽",
|
||||
"🏃",
|
||||
"💃",
|
||||
"🕺",
|
||||
"👯",
|
||||
"🧖",
|
||||
"🧘",
|
||||
"🧑🤝🧑",
|
||||
"👭",
|
||||
"👫",
|
||||
"👬",
|
||||
"💏",
|
||||
"👩❤️💋👨",
|
||||
"👨❤️💋👨",
|
||||
"👩❤️💋👩",
|
||||
"💑",
|
||||
"👩❤️👨",
|
||||
"👨❤️👨",
|
||||
"👩❤️👩",
|
||||
"👪",
|
||||
"👨👩👦",
|
||||
"👨👩👧",
|
||||
"👨👩👧👦",
|
||||
"👨👩👦👦",
|
||||
"👨👩👧👧",
|
||||
"👨👨👦",
|
||||
"👨👨👧",
|
||||
"👨👨👧👦",
|
||||
"👨👨👦👦",
|
||||
"👨👨👧👧",
|
||||
"👩👩👦",
|
||||
"👩👩👧",
|
||||
"👩👩👧👦",
|
||||
"👩👩👦👦",
|
||||
"👩👩👧👧",
|
||||
"👨👦",
|
||||
"👨👦👦",
|
||||
"👨👧",
|
||||
"👨👧👦",
|
||||
"👨👧👧",
|
||||
"👩👦",
|
||||
"👩👦👦",
|
||||
"👩👧",
|
||||
"👩👧👦",
|
||||
"👩👧👧",
|
||||
"🕴",
|
||||
"🧗",
|
||||
"🧗",
|
||||
"🧗",
|
||||
"🤺",
|
||||
"🏇",
|
||||
"⛷",
|
||||
"🏂",
|
||||
"🏌",
|
||||
"🏄",
|
||||
"🚣",
|
||||
"🏊",
|
||||
"⛹",
|
||||
"🏋",
|
||||
"🚴",
|
||||
"🚵",
|
||||
"🤸",
|
||||
"🤼",
|
||||
"🤽",
|
||||
"🤾",
|
||||
"🤹",
|
||||
"🧘",
|
||||
"👋",
|
||||
"🤚",
|
||||
"🖐",
|
||||
"✋",
|
||||
"🫱",
|
||||
"🫲",
|
||||
"🫳",
|
||||
"🫴",
|
||||
"🫰",
|
||||
"🫵",
|
||||
"🫶",
|
||||
"🖖",
|
||||
"👌",
|
||||
"🤌",
|
||||
"🤏",
|
||||
"✌",
|
||||
"🤞",
|
||||
"🤟",
|
||||
"🤘",
|
||||
"🤙",
|
||||
"👈",
|
||||
"👉",
|
||||
"👆",
|
||||
"🖕",
|
||||
"👇",
|
||||
"☝",
|
||||
"👍",
|
||||
"👎",
|
||||
"✊",
|
||||
"👊",
|
||||
"🤛",
|
||||
"🤜",
|
||||
"👏",
|
||||
"🙌",
|
||||
"👐",
|
||||
"🤲",
|
||||
"🤝",
|
||||
"🙏",
|
||||
"✍",
|
||||
"💅",
|
||||
"🤳",
|
||||
"💪",
|
||||
"🦵",
|
||||
"🦶",
|
||||
"👂",
|
||||
"🦻",
|
||||
"👃",
|
||||
"🛌",
|
||||
"🛀",
|
||||
"🏳",
|
||||
"🏴",
|
||||
"👁",
|
||||
"🐈",
|
||||
"🐦",
|
||||
"🐕",
|
||||
"🦺",
|
||||
"🐻"
|
||||
];
|
||||
|
||||
const allChars = tonedEmojis.concat(skinColors);
|
||||
let chars = [];
|
||||
let i = 0;
|
||||
|
||||
for (let c of unicode) {
|
||||
// remove last zwj if the next one is a skin color
|
||||
if (skinColors.includes(c)) chars.pop();
|
||||
|
||||
// add emoji
|
||||
chars.push(c);
|
||||
|
||||
// add zwj
|
||||
if (allChars.includes(c)) chars.push("\u200d");
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
// remove last element if zwj
|
||||
if (chars[chars.length - 1] === "\u200d") chars.pop();
|
||||
|
||||
// combine to string
|
||||
return chars.join("");
|
||||
}
|
BIN
assets/sound/pop.wav
Executable file
14
etc/avatar.php
Normal file
@ -0,0 +1,14 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
$id = $_GET["id"];
|
||||
$avatarFile = $GLOBALS["path"]."/etc/avatars/".$id;
|
||||
|
||||
$domainInfo = domainForID($id);
|
||||
if (@$domainInfo && @$domainInfo["avatar"] && file_exists($avatarFile)) {
|
||||
$image = file_get_contents($avatarFile);
|
||||
$type = mime_content_type($avatarFile);
|
||||
header("Content-Type: ".$type);
|
||||
die($image);
|
||||
}
|
||||
?>
|
6
etc/config.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
$json = json_decode(file_get_contents(__DIR__."/config.json"), true);
|
||||
foreach ($json as $key => $value) {
|
||||
$GLOBALS[$key] = $value;
|
||||
}
|
||||
?>
|
12
etc/config.sample.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"path": "/var/www/html/hnschat",
|
||||
|
||||
"sqlHost": "",
|
||||
"sqlUser": "",
|
||||
"sqlPass": "",
|
||||
"sqlDatabase": "",
|
||||
|
||||
"typingDelay": 2000,
|
||||
|
||||
"tenorKey": ""
|
||||
}
|
59
etc/cron.php
Normal file
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
// ACTIVATE NEW CHANNELS
|
||||
$getChannels = sql("SELECT * FROM `channels` WHERE `tx` IS NOT NULL AND `activated` = 0 AND `hidden` = 1");
|
||||
if ($getChannels) {
|
||||
foreach ($getChannels as $key => $data) {
|
||||
$verify = verifyTransaction($data["tx"], $data["fee"]);
|
||||
|
||||
if ($verify) {
|
||||
sql("UPDATE `channels` SET `activated` = 1, `hidden` = 0 WHERE `id` = ?", [$data["id"]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIND REGISTRY FOR SLD GATED COMMUNITIES
|
||||
$getChannels = sql("SELECT * FROM `channels` WHERE `public` = 0 AND `hidden` = 0 AND `registry` IS NULL");
|
||||
foreach ($getChannels as $key => $data) {
|
||||
$tld = $data["name"];
|
||||
|
||||
$staked = isNameStaked($tld);
|
||||
if ($staked) {
|
||||
sql("UPDATE `channels` SET `registry` = ? WHERE `ai` = ?", [$staked, $data["ai"]]);
|
||||
}
|
||||
}
|
||||
|
||||
// FETCH AVATARS
|
||||
$getUsers = sql("SELECT * FROM `domains` WHERE `claimed` = 1 AND `locked` = 0 AND `deleted` = 0 ORDER BY `ai` DESC");
|
||||
foreach ($getUsers as $key => $data) {
|
||||
$avatar = fetchAvatar($data["domain"]);
|
||||
$avatarFile = $GLOBALS["path"]."/etc/avatars/".$data["id"];
|
||||
|
||||
$tld = tldForDomain($data["domain"]);
|
||||
if ($tld && in_array($tld, getStakedNames())) {
|
||||
if ($data["avatar"]) {
|
||||
$avatar = $data["avatar"];
|
||||
}
|
||||
}
|
||||
|
||||
if ($avatar) {
|
||||
$response = getContentsWithCode($avatar);
|
||||
|
||||
if (validImageWithoutFetch($response["data"])) {
|
||||
if ($response["code"] == 200) {
|
||||
sql("UPDATE `domains` SET `avatar` = ? WHERE `id` = ?", [$avatar, $data["id"]]);
|
||||
|
||||
$newSize = strlen($response["data"]);
|
||||
if (file_exists($avatarFile)) {
|
||||
$currentSize = filesize($avatarFile);
|
||||
}
|
||||
|
||||
if (!@$currentSize || (int)$newSize !== (int)$currentSize) {
|
||||
file_put_contents($avatarFile, $response["data"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
486
etc/functions.php
Normal file
@ -0,0 +1,486 @@
|
||||
<?php
|
||||
function error($message) {
|
||||
$output = [
|
||||
"success" => false,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
die(json_encode($output));
|
||||
}
|
||||
|
||||
function generateID($length) {
|
||||
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
$pass = array();
|
||||
$alphaLength = strlen($alphabet) - 1;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$n = rand(0, $alphaLength);
|
||||
$pass[] = $alphabet[$n];
|
||||
}
|
||||
return implode($pass);
|
||||
}
|
||||
|
||||
function generateNumber($length) {
|
||||
$alphabet = '123456789';
|
||||
$pass = array();
|
||||
$alphaLength = strlen($alphabet) - 1;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$n = rand(0, $alphaLength);
|
||||
$pass[] = $alphabet[$n];
|
||||
}
|
||||
return implode($pass);
|
||||
}
|
||||
|
||||
function generateCode($type) {
|
||||
switch ($type) {
|
||||
case "session":
|
||||
$db = "sessions";
|
||||
$param = "id";
|
||||
$length = 32;
|
||||
$prefix = "V2-";
|
||||
break;
|
||||
|
||||
case "domain":
|
||||
$db = "domains";
|
||||
$param = "id";
|
||||
$length = 16;
|
||||
break;
|
||||
|
||||
case "preview":
|
||||
$db = "previews";
|
||||
$param = "id";
|
||||
$length = 16;
|
||||
break;
|
||||
|
||||
case "upload":
|
||||
$db = "uploads";
|
||||
$param = "id";
|
||||
$length = 32;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
tryAgain:
|
||||
$id = generateID($length);
|
||||
|
||||
$checkExists = sql("SELECT * FROM `".$db."` WHERE `".$param."` = ?", [@$prefix.$id]);
|
||||
if ($checkExists) {
|
||||
goto tryAgain;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
function guidv4($data) {
|
||||
assert(strlen($data) == 16);
|
||||
|
||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
|
||||
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
function verifyTransaction($tx, $amount) {
|
||||
$amount = preg_replace("/[^0-9]/", "", $amount);
|
||||
$address = "hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa";
|
||||
|
||||
$response = queryHSW("/wallet/hnschat-hip-2/tx/".$tx);
|
||||
foreach (@$response["outputs"] as $key => $output) {
|
||||
if (@$response["confirmations"] >= 2 && $output["value"] == $amount && $output["address"] = $address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function tldForDomain($domain) {
|
||||
$split = explode(".", $domain);
|
||||
$tld = end($split);
|
||||
|
||||
return $tld;
|
||||
}
|
||||
|
||||
function domainForID($id) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `id` = ?", [$id])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function domainForName($name) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `domain` = ?", [$name])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function activeDomainForName($name) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `domain` = ? AND `claimed` = 1 AND `locked` = 0 AND `deleted` = 0", [$name])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function channelForID($id) {
|
||||
$getChannel = @sql("SELECT * FROM `channels` WHERE `id` = ?", [$id])[0];
|
||||
if ($getChannel) {
|
||||
return $getChannel;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getStakedNames() {
|
||||
$getNames = sql("SELECT `name` FROM `channels` WHERE `slds` = 1 AND `hidden` = 0 ORDER BY `name` ASC");
|
||||
|
||||
$names = [];
|
||||
foreach ($getNames as $key => $value) {
|
||||
$names[] = $value["name"];
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
function getStakedHIP2Names() {
|
||||
$getNames = sql("SELECT `name` FROM `channels` WHERE `slds` = 1 AND `hip2` = 1 AND `hidden` = 0 ORDER BY `name` ASC");
|
||||
|
||||
$names = [];
|
||||
foreach ($getNames as $key => $value) {
|
||||
$names[] = $value["name"];
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
function isNameStaked($tld) {
|
||||
if ($tld === "eth") {
|
||||
return "ens";
|
||||
}
|
||||
|
||||
$data = [
|
||||
"method" => "getnameresource",
|
||||
"params" => [$tld],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if (@$response["records"]) {
|
||||
foreach ($response["records"] as $key => $value) {
|
||||
if ($value["type"] == "NS") {
|
||||
if (stripos($value["ns"], ".nameserver.io.") !== false || stripos($value["ns"], ".registry.namebase.io.") !== false) {
|
||||
return "namebase";
|
||||
}
|
||||
if ($value["ns"] == "0x06081C6B2B876EABDC41DFD3345e8Fa59588C02e._eth.") {
|
||||
return "impervious";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hshub = checkVaro($tld);
|
||||
if ($hshub) {
|
||||
return "varo";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkVaro($tld) {
|
||||
$url = "https://hshub.io/tld/".$tld;
|
||||
$html = getContents($url);
|
||||
|
||||
$canPurchase = preg_match("/<div class=\"title\">Buy \./", $html);
|
||||
if ($canPurchase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function avatarFromTXT($content) {
|
||||
if ((substr($content, 0, 7) === "avatar=" || substr($content, 0, 15) === "profile avatar=")) {
|
||||
if (substr($content, 0, 15) === "profile avatar=") {
|
||||
$avatar = substr($content, 15);
|
||||
}
|
||||
else {
|
||||
$avatar = substr($content, 7);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filter_var(@$avatar, FILTER_VALIDATE_URL) === false) {
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function fetchAvatar($domain) {
|
||||
if ($domain) {
|
||||
$getRecords = shell_exec("dig @127.0.0.44 +noall +answer +noidnin +noidnout ".escapeshellarg($domain)." TXT");
|
||||
preg_match_all("/(?<domain>.+)\..+TXT\s\"(?<value>.+)\"/", $getRecords, $matches);
|
||||
|
||||
if ($matches) {
|
||||
foreach ($matches["domain"] as $key => $data) {
|
||||
if ($data === $domain) {
|
||||
$value = $matches["value"][$key];
|
||||
|
||||
$avatar = avatarFromTXT($value);
|
||||
if ($avatar) {
|
||||
return $avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($domain, ".") == false) {
|
||||
$data = [
|
||||
"method" => "getnameresource",
|
||||
"params" => [$domain],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if ($response) {
|
||||
$records = @$response["records"];
|
||||
if ($records) {
|
||||
foreach ($records as $key => $record) {
|
||||
if (@$record["txt"]) {
|
||||
$content = @$record["txt"][0];
|
||||
|
||||
$avatar = avatarFromTXT($content);
|
||||
if ($avatar) {
|
||||
return $avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getContents($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
$c = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
function getContentsWithCode($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
$data = curl_exec($curl);
|
||||
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
curl_close($curl);
|
||||
|
||||
return [
|
||||
"data" => $data,
|
||||
"code" => $code
|
||||
];
|
||||
}
|
||||
|
||||
function getContentsWithSpoof($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Googlebot/2.1; +http://google.com/bot.html)");
|
||||
$c = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
function post($url, $data) {
|
||||
$ch = curl_init($url);
|
||||
$payload = json_encode($data);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetchMetaTags($url) {
|
||||
$output = [];
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$c = getContentsWithSpoof($url);
|
||||
|
||||
if ($c) {
|
||||
$d = new DomDocument();
|
||||
$d->loadHTML($c);
|
||||
$xp = new domxpath($d);
|
||||
|
||||
foreach ($xp->query("//title") as $el) {
|
||||
$output["title"] = $el->textContent;
|
||||
}
|
||||
foreach ($xp->query("//meta[@property='og:title']") as $el) {
|
||||
$output["title"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:title']") as $el) {
|
||||
$output["title"] = $el->getAttribute("content");
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@property='og:description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='og:image']") as $el) {
|
||||
$image = $el->getAttribute("content");
|
||||
if (validImage($image)) {
|
||||
$output["image"] = $image;
|
||||
}
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:image']") as $el) {
|
||||
$image = $el->getAttribute("content");
|
||||
if (validImage($image)) {
|
||||
$output["image"] = $image;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='og:video:secure_url']") as $el) {
|
||||
$output["video"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:video:secure_url']") as $el) {
|
||||
$output["video"] = $el->getAttribute("content");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@$output["title"]) {
|
||||
$id = generateCode("preview");
|
||||
$insert = sql("INSERT INTO `previews` (id, link, title, description, image, video) VALUES (?,?,?,?,?,?)", [$id, $url, @$output["title"], @$output["description"], @$output["image"], @$output["video"]]);
|
||||
|
||||
if ($insert) {
|
||||
$output["id"] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function validImage($url) {
|
||||
$string = getContents($url);
|
||||
if ($string) {
|
||||
$id = generateID(16);
|
||||
$file = "/tmp/".$id;
|
||||
$f = fopen($file, 'wb');
|
||||
fputs($f, $string);
|
||||
fclose($f);
|
||||
$size = getimagesize($file);
|
||||
unlink($file);
|
||||
}
|
||||
return (strtolower(substr(@$size['mime'], 0, 5)) == 'image' ? true : false);
|
||||
}
|
||||
|
||||
function validImageWithoutFetch($string) {
|
||||
if ($string) {
|
||||
$id = generateID(16);
|
||||
$file = "/tmp/".$id;
|
||||
$f = fopen($file, 'wb');
|
||||
fputs($f, $string);
|
||||
fclose($f);
|
||||
$size = getimagesize($file);
|
||||
unlink($file);
|
||||
}
|
||||
return (strtolower(substr(@$size['mime'], 0, 5)) == 'image' ? true : false);
|
||||
}
|
||||
|
||||
function validateAddress($address) {
|
||||
$data = [
|
||||
"method" => "validateaddress",
|
||||
"params" => [$address],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if (@$response["isvalid"] && @$response["isspendable"]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function queryHSD($data) {
|
||||
if (@$data["params"]) {
|
||||
foreach ($data["params"] as $key => $value) {
|
||||
$data["params"][$key] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ["Content-Type:application/json"]);
|
||||
curl_setopt($curl, CURLOPT_URL,"http://x:a831d3c59ce474d8e13a7cea3a3935d3d5a55b84698abe38f2eea2329327e2c50@127.0.0.1:12037");
|
||||
curl_setopt($curl, CURLOPT_POST, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
curl_close ($curl);
|
||||
|
||||
if ($response) {
|
||||
$info = @json_decode($response, true);
|
||||
|
||||
if (@$info["result"]) {
|
||||
return $info["result"];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function queryHSW($endpoint) {
|
||||
$endpoint = trim($endpoint);
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ["Content-Type:application/json"]);
|
||||
curl_setopt($curl, CURLOPT_URL,"http://x:a831d3c59ce474d8e13a7cea3a3935d3d5a55b84698abe38f2eea2329327e2c50@127.0.0.1:12039".$endpoint);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
curl_close ($curl);
|
||||
|
||||
if ($response) {
|
||||
$info = @json_decode($response, true);
|
||||
|
||||
if (@$info) {
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
?>
|
45
etc/head.php
Normal file
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$revision = trim(file_get_contents(".git/refs/heads/main"));
|
||||
?>
|
||||
<title>HNSChat</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="darkreader" content="noplz">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<meta name="title" content="HNSChat">
|
||||
<meta name="description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://hns.chat/">
|
||||
<meta property="og:title" content="HNSChat">
|
||||
<meta property="og:description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="og:image" content="https://hns.chat/assets/img/cover">
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="https://hns.chat/">
|
||||
<meta property="twitter:title" content="HNSChat">
|
||||
<meta property="twitter:description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="twitter:image" content="https://hns.chat/assets/img/cover">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rubik&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/assets/css/style?r=<?php echo $revision; ?>">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://auth.varo.domains/v1"></script>
|
||||
<script type="text/javascript" src="/assets/js/qr?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/qr2?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/he?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/zwj?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/date?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/emojis?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/anchorme?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/mask?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/confetti?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/dish?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/janus?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/adapter?r=<?php echo $revision; ?>"></script>
|
||||
<script type="module" src="/assets/js/script?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript">
|
||||
var revision = "<?php echo $revision; ?>";
|
||||
</script>
|
5
etc/includes.php
Normal file
@ -0,0 +1,5 @@
|
||||
<?php
|
||||
include "config.php";
|
||||
include "sql.php";
|
||||
include "functions.php";
|
||||
?>
|
385
etc/page/chat.php
Normal file
@ -0,0 +1,385 @@
|
||||
<body data-page="chat" data-version="<?php echo $revision; ?>">
|
||||
<div class="connecting">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div id="blackout"></div>
|
||||
<div class="popover" data-name="update">
|
||||
<div class="head">
|
||||
<div class="title">Update</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">An update is available. Please reload for the best possible experience.</div>
|
||||
<div class="button" data-action="reload">Reload</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover" data-name="newConversation">
|
||||
<div class="head">
|
||||
<div class="title">New Conversation</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<input class="tab" type="text" name="domain" placeholder="hnschat/">
|
||||
<input type="text" name="message" placeholder="Message">
|
||||
<div class="button" data-action="startConversation">Start Conversation</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover" data-name="syncSession">
|
||||
<div class="head">
|
||||
<div class="title">Sync Session</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">Use this QR code or link to sync your session to another browser.</div>
|
||||
<div id="qrcode"></div>
|
||||
<div class="group">
|
||||
<input readonly="readonly" class="copyable" type="text" name="syncLink">
|
||||
<div class="icon action clipboard" data-action="clipboard"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="donate">
|
||||
<div class="head">
|
||||
<div class="title">Donate</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">If you enjoy using this free service, please consider donating.</div>
|
||||
<div class="group">
|
||||
<input readonly="readonly" class="copyable" type="text" name="donateAddress" value="hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa">
|
||||
<div class="icon action clipboard" data-action="clipboard"></div>
|
||||
</div>
|
||||
<div class="center">© <?php echo date("Y"); ?> <a href="https://eskimo.software" target="_blank">Eskimo Software</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="pay">
|
||||
<div class="head">
|
||||
<div class="title">Send HNS</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="loading flex shown">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<input type="hidden" name="address">
|
||||
<input type="text" name="hns" placeholder="0 HNS">
|
||||
<div class="button" data-action="sendPayment">Send with Bob Extension</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="settings">
|
||||
<div class="head">
|
||||
<div class="title">Settings</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="setting">
|
||||
<div class="subtitle">Avatar URL</div>
|
||||
<input class="remote tab" type="text" name="avatar" placeholder="">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">HNS Wallet Address</div>
|
||||
<input class="remote tab" type="text" name="address" placeholder="">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Chat Bubble Color</div>
|
||||
<input class="local color tab" type="color" name="bubbleBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Self Chat Bubble Color</div>
|
||||
<input class="local color tab" type="color" name="bubbleSelfBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Mention Chat Bubble Color</div>
|
||||
<input class="local color" type="color" name="bubbleMentionBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Chat Display Mode</div>
|
||||
<select class="local" name="chatDisplayMode">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="compact">Compact</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Sync Session</div>
|
||||
<div class="center action link" data-action="syncSession">Show QR + Link</div>
|
||||
</div>
|
||||
<div class="button" data-action="saveSettings">Save</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="userContext">
|
||||
<div class="actions">
|
||||
<div class="action icon edit" data-action="editProfile"></div>
|
||||
<div class="action icon save" data-action="saveProfile"></div>
|
||||
<div class="action icon close" data-action="undoProfile"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="pic"></div>
|
||||
<span class="user subtitle"></span>
|
||||
<div class="icon type"></div>
|
||||
</li>
|
||||
<li class="bio">
|
||||
<div class="title small">Bio</div>
|
||||
<div class="bioHolder">
|
||||
<div class="bio subtitle"></div>
|
||||
<div class="limit"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title small">Joined</div>
|
||||
<span class="joined subtitle"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="separator"></div>
|
||||
<ul class="contextActions">
|
||||
<li class="action" data-action="newConversationWith">
|
||||
<div class="icon message"></div>
|
||||
<span>Message</span>
|
||||
</li>
|
||||
<li class="action" data-action="mentionUser">
|
||||
<div class="icon mention"></div>
|
||||
<span>Mention</span>
|
||||
</li>
|
||||
<li class="action" data-action="slapUser">
|
||||
<div class="icon fish"></div>
|
||||
<span>Slap</span>
|
||||
</li>
|
||||
<li class="action speaker" data-action="inviteVideo">
|
||||
<div class="icon voice"></div>
|
||||
<span>Speaker</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="channelContext">
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="channel subtitle"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="separator"></div>
|
||||
<ul>
|
||||
<li class="action" data-action="switchConversation">
|
||||
<div class="icon view"></div>
|
||||
<span>View</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="messageContext">
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li class="action reply" data-action="reply">
|
||||
<div class="icon reply"></div>
|
||||
<span>Reply</span>
|
||||
</li>
|
||||
<li class="action emoji" data-action="emojis">
|
||||
<div class="icon emoji"></div>
|
||||
<span>React</span>
|
||||
</li>
|
||||
<li class="action pin" data-action="pinMessage">
|
||||
<div class="icon pin"></div>
|
||||
<span>Pin</span>
|
||||
</li>
|
||||
<li class="action delete error" data-action="deleteMessage">
|
||||
<div class="icon delete"></div>
|
||||
<span>Delete</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="holder">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<div class="icon menu"></div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="logo">
|
||||
<img draggable="false" src="/assets/img/handshake">
|
||||
</div>
|
||||
<div class="messageHeader">
|
||||
<table></table>
|
||||
<div class="pinnedMessage flex">
|
||||
<div class="icon pin"></div>
|
||||
<div class="message"></div>
|
||||
<div class="action icon delete" data-action="pinMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="end">
|
||||
<div id="me"></div>
|
||||
<div class="domains">
|
||||
<select></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="icon users"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chats">
|
||||
<div id="conversations" class="sidebar">
|
||||
<div class="title">
|
||||
<div class="tabs">
|
||||
<div class="tab" data-tab="channels">Channels</div>
|
||||
<div class="tab" data-tab="pms">Private</div>
|
||||
</div>
|
||||
<div class="actionHolder">
|
||||
<div class="action icon compose" data-action="newConversation"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sections">
|
||||
<div class="section channels">
|
||||
<table></table>
|
||||
</div>
|
||||
<div class="section pms">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="action link" data-action="settings">Settings</div>
|
||||
<div class="action link" data-action="docs">Docs</div>
|
||||
<div class="action link" data-action="donate">Donate</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="pinnedMessage flex">
|
||||
<div class="icon pin"></div>
|
||||
<div class="message"></div>
|
||||
<div class="action icon delete" data-action="pinMessage"></div>
|
||||
</div>
|
||||
<div id="closeMenu"></div>
|
||||
<div id="videoInfo" class="flex">
|
||||
<div class="info">
|
||||
<div class="users"></div>
|
||||
<div class="title flex">
|
||||
<span>LIVE</span>
|
||||
<div class="icon audio"></div>
|
||||
</div>
|
||||
<div class="watching flex">
|
||||
<div class="watchers"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="link" data-action="viewVideo">Watch</div>
|
||||
<div class="link" data-action="startVideo">Stream</div>
|
||||
<div class="link" data-action="joinVideo">Join</div>
|
||||
<div class="link destructive" data-action="leaveVideo">Leave</div>
|
||||
<div class="link destructive" data-action="endVideo">End</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="videoContainer" class="flex">
|
||||
<div class="controls">
|
||||
<div class="button outline muted" data-action="toggleScreen">
|
||||
<div class="icon screen"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="toggleAudio">
|
||||
<div class="icon voice"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="toggleVideo">
|
||||
<div class="icon video"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="leaveVideo">
|
||||
<div class="icon leave"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="messageHolder">
|
||||
<div class="popover" id="completions" data-name="completions">
|
||||
<div class="head">
|
||||
<div class="title"></div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<table class="list"></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" id="react" data-name="react">
|
||||
<div class="head">
|
||||
<div class="title">
|
||||
<div class="tabs">
|
||||
<div class="tab" data-name="gifs">Gifs</div>
|
||||
<div class="tab" data-name="emojis">Emojis</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="search">
|
||||
<input type="text" name="searchGifs" placeholder="Search Tenor">
|
||||
<input type="text" class="shown" name="searchEmojis" placeholder="Search Emojis">
|
||||
</div>
|
||||
<div class="grids">
|
||||
<div class="grid" data-type="gifs">
|
||||
<div class="section" data-type="categories"></div>
|
||||
<div class="section flex" data-type="gifs">
|
||||
<div class="column" data-column="0"></div>
|
||||
<div class="column" data-column="1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid shown" data-type="emojis"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="messages"></div>
|
||||
<div id="jumpToPresent" class="hidden">
|
||||
<div class="action" data-action="jumpToPresent">Jump To Present</div>
|
||||
</div>
|
||||
<div class="loading flex">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<div id="typing" class="flex">
|
||||
<div class="message"></div>
|
||||
</div>
|
||||
<div id="replying" class="flex">
|
||||
<div class="message">Replying to <span class="name"></span></div>
|
||||
<div class="action icon remove" data-action="removeReply"></div>
|
||||
</div>
|
||||
<div id="attachments" class="flex"></div>
|
||||
<div class="inputHolder">
|
||||
<div class="input">
|
||||
<div class="action icon plus" data-action="file">
|
||||
<input id="file" type="file" name="file">
|
||||
</div>
|
||||
<div class="action icon pay" data-action="pay"></div>
|
||||
<div class="inputs">
|
||||
<textarea id="message" placeholder="Message"></textarea>
|
||||
</div>
|
||||
<div class="action icon gif big" data-action="gifs"></div>
|
||||
<div class="action icon emoji big" data-action="emojis"></div>
|
||||
</div>
|
||||
<div class="locked"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="users" class="sidebar">
|
||||
<div class="title">
|
||||
<div class="group normal">
|
||||
<div class="action icon search" data-action="searchUsers"></div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
<div class="group flex searching">
|
||||
<input type="text" name="search">
|
||||
<div class="action icon close" data-action="searchUsers"></div>
|
||||
</div>
|
||||
<div id="count"></div>
|
||||
</div>
|
||||
<div class="sections">
|
||||
<div class="section users">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="avatars"></div>
|
||||
</body>
|
26
etc/preview.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
$id = $_GET["id"];
|
||||
$previewFile = $GLOBALS["path"]."/etc/previews/".$id;
|
||||
|
||||
$getImage = @sql("SELECT `image` FROM `previews` WHERE `id` = ? AND `image` IS NOT NULL", [$id])[0];
|
||||
|
||||
if ($getImage) {
|
||||
if (file_exists($previewFile)) {
|
||||
$image = file_get_contents($previewFile);
|
||||
$type = mime_content_type($previewFile);
|
||||
}
|
||||
else {
|
||||
$getImage["image"] = html_entity_decode(html_entity_decode($getImage["image"]));
|
||||
$image = getContents($getImage["image"]);
|
||||
if (validImageWithoutFetch($image)) {
|
||||
file_put_contents($previewFile, $image);
|
||||
$type = mime_content_type($previewFile);
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Type: ".$type);
|
||||
die($image);
|
||||
}
|
||||
?>
|
68
etc/sql.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
$GLOBALS["sqlInfo"] = [
|
||||
"host" => $GLOBALS["sqlHost"],
|
||||
"user" => $GLOBALS["sqlUser"],
|
||||
"pass" => $GLOBALS["sqlPass"],
|
||||
"db" => $GLOBALS["sqlDatabase"],
|
||||
"options" => [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
];
|
||||
$GLOBALS["sqlDSN"] = "mysql:host=".$GLOBALS["sqlInfo"]["host"].";dbname=".$GLOBALS["sqlInfo"]["db"].";charset=utf8mb4";
|
||||
|
||||
function initSQL() {
|
||||
retry:
|
||||
|
||||
try {
|
||||
$GLOBALS["remoteSQL"] = new PDO($GLOBALS["sqlDSN"], $GLOBALS["sqlInfo"]["user"], $GLOBALS["sqlInfo"]["pass"], $GLOBALS["sqlInfo"]["options"]);
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (strpos($message, "Connection refused") !== false) {
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sql($query, $values = []) {
|
||||
if (!@$GLOBALS["remoteSQL"]) {
|
||||
initSQL();
|
||||
}
|
||||
|
||||
retry:
|
||||
try {
|
||||
$statement = $GLOBALS["remoteSQL"]->prepare($query);
|
||||
$success = $statement->execute($values);
|
||||
|
||||
$result = $statement->fetchAll();
|
||||
|
||||
if (count($result) > 1) {
|
||||
return $result;
|
||||
}
|
||||
else if (count($result) == 1) {
|
||||
return [$result[0]];
|
||||
}
|
||||
else if ($success && (substr($query, 0, 12) === "INSERT INTO " || substr($query, 0, 12) === "DELETE FROM " || (substr($query, 0, 7) === "UPDATE "))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (strpos($message, "MySQL server has gone away") !== false || strpos($message, "Communication link failure") !== false) {
|
||||
initSQL();
|
||||
|
||||
goto retry;
|
||||
}
|
||||
else {
|
||||
//var_dump($message);
|
||||
//error
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
BIN
favicon.ico
Normal file
After Width: | Height: | Size: 1.8 KiB |
83
id.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
include "etc/includes.php";
|
||||
|
||||
if (@$_GET["invite"]) { ?>
|
||||
<script type="text/javascript">
|
||||
var invite = "<?php echo htmlspecialchars(addslashes($_GET["invite"])); ?>";
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<?php include "etc/head.php"; ?>
|
||||
</head>
|
||||
<body data-page="id">
|
||||
<div id="blackout"></div>
|
||||
<div class="popover" data-name="qr">
|
||||
<div class="head">
|
||||
<div class="title">Sync QR</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="loading flex shown">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
<canvas id="frame"></canvas>
|
||||
</div>
|
||||
<video id="camera" autoplay playsinline></video>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="form" id="id">
|
||||
<a href="/">
|
||||
<div class="logo">
|
||||
<img draggable="false" src="/assets/img/handshake">
|
||||
<span>Chat</span>
|
||||
</div>
|
||||
</a>
|
||||
<div class="section loading shown">
|
||||
<div class="loading flex shown">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" id="manageDomains">
|
||||
<div class="domains"></div>
|
||||
<div class="button" data-action="newDomain">Add Domain</div>
|
||||
<div class="button" data-action="scanQR">Scan Sync QR</div>
|
||||
<a href="/" id="startChatting" class="hidden">Start Chatting</a>
|
||||
</div>
|
||||
<div class="section" id="addDomain">
|
||||
<div class="button varo" data-action="addDomain">Authenticate with Varo Auth</div>
|
||||
<div class="or varo">OR</div>
|
||||
<div class="group">
|
||||
<input type="text" name="sld" placeholder="Create a name">
|
||||
<input type="text" name="dot" placeholder="." class="transparent" disabled>
|
||||
<select name="tld"></select>
|
||||
<input type="hidden" name="invite">
|
||||
</div>
|
||||
<div class="button" data-action="addSLD">Continue</div>
|
||||
<div class="response error"></div>
|
||||
<div class="link" data-action="manageDomains">Manage Domains</div>
|
||||
</div>
|
||||
<div class="section" id="verifyOptions">
|
||||
<input type="hidden" name="domain">
|
||||
<div class="title">How would you like to verify?</div><div id="code"></div>
|
||||
<div class="button" data-action="verifyDomainWithTXT">Verify with TXT Record</div>
|
||||
<div class="button" data-action="verifyDomainWithBob">Verify with Bob Extension</div>
|
||||
<div class="button" data-action="verifyDomainWithMetaMask">Verify with MetaMask</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="section" id="verifyDomain">
|
||||
<input type="hidden" name="domain">
|
||||
<div class="title">Please create a TXT record with the following value: </div><div id="code"></div>
|
||||
<div class="button" data-action="verifyDomain">Verify</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="section" id="startChatting">
|
||||
<div class="title">You're all set!</div>
|
||||
<div class="button" data-action="startChatting">Start Chatting</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
28
index.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
include "etc/includes.php";
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<?php include "etc/head.php"; ?>
|
||||
</head>
|
||||
<?php
|
||||
include "etc/page/chat.php";
|
||||
?>
|
||||
<script>
|
||||
var _paq = window._paq = window._paq || [];
|
||||
_paq.push(["setCookieDomain", "*.hns.chat"]);
|
||||
_paq.push(["setDomains", ["*.hns.chat","*.hnschat"]]);
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
(function() {
|
||||
var u="https://35k1m0.com/trkr/";
|
||||
_paq.push(['setTrackerUrl', u+'trkr.php']);
|
||||
_paq.push(['setSiteId', '13']);
|
||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
||||
g.async=true; g.src=u+'trkr.js'; s.parentNode.insertBefore(g,s);
|
||||
})();
|
||||
</script>
|
||||
<noscript><p><img src="https://35k1m0.com/trkr/trkr.php?idsite=13&rec=1" style="border:0;" alt="" /></p></noscript>
|
||||
<script async src="https://trkr.35k1m0.com/script.js" data-website-id="38ec710a-a492-4465-a2ba-292eb4b645a5"></script>
|
||||
</html>
|
64
manifest.json
Normal file
@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "HNSChat",
|
||||
"short_name": "HNSChat",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"background_color": "#232323",
|
||||
"description": "Securely chat with your handshake friends.",
|
||||
"dir": "ltr",
|
||||
"categories": "social",
|
||||
"lang": "en",
|
||||
"orientation": "any",
|
||||
"scope": "/",
|
||||
"theme_color": "#232323",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/assets/img/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
},
|
||||
{
|
||||
"src": "/assets/img/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable any"
|
||||
}
|
||||
]
|
||||
}
|
26
privacy.txt
Normal file
@ -0,0 +1,26 @@
|
||||
Privacy Policy for HNSChat
|
||||
|
||||
At HNSChat, we value your privacy and are committed to protecting your personal information. This Privacy Policy explains how we collect, use, and safeguard your data when you use our messaging platform. By using HNSChat, you consent to the practices outlined in this policy.
|
||||
|
||||
Information Collection:
|
||||
We may also collect non-personal information, such as device information and usage data, to improve our services.
|
||||
|
||||
Information Usage:
|
||||
We use the information we collect to provide and enhance our messaging platform. This includes facilitating communication between users, improving user experience, and ensuring the security of our services. We do not sell, rent, or share your personal information with third parties unless required by law or with your explicit consent.
|
||||
|
||||
Data Security:
|
||||
We take reasonable measures to protect your personal information from unauthorized access, loss, or misuse. However, please be aware that no security measures are completely foolproof, and we cannot guarantee the absolute security of your data.
|
||||
|
||||
Cookies and Tracking Technologies:
|
||||
HNSChat may use cookies and similar tracking technologies to enhance your browsing experience and gather information about how you use our platform. You can manage your cookie preferences in your browser settings.
|
||||
|
||||
Third-Party Links:
|
||||
Our platform may contain links to third-party websites or services. We are not responsible for the privacy practices or content of these third parties. We encourage you to review the privacy policies of any third-party websites you visit.
|
||||
|
||||
Changes to the Privacy Policy:
|
||||
We may update this Privacy Policy from time to time. We will notify you of any material changes by posting the updated policy on our website or through other communication channels. It is your responsibility to review the policy periodically.
|
||||
|
||||
Contact Us:
|
||||
If you have any questions, concerns, or requests regarding this Privacy Policy or your personal information, please contact us at contact@hns.chat.
|
||||
|
||||
By using HNSChat, you acknowledge and agree to the terms outlined in this Privacy Policy.
|
1
sw.js
Normal file
@ -0,0 +1 @@
|
||||
// This serves no purpose for now besides supporting notifications
|
12
sync.php
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
include "etc/includes.php";
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<?php include "etc/head.php"; ?>
|
||||
</head>
|
||||
<body data-page="sync">
|
||||
<div class="response">Just a second...</div>
|
||||
</body>
|
||||
</html>
|
77
upload.php
Normal file
@ -0,0 +1,77 @@
|
||||
<?php
|
||||
include "etc/includes.php";
|
||||
|
||||
set_time_limit(0);
|
||||
|
||||
if (@!$_FILES["file"]) {
|
||||
error("Missing file.");
|
||||
}
|
||||
if (@!$_POST["key"]) {
|
||||
error("Missing key.");
|
||||
}
|
||||
|
||||
$output["success"] = true;
|
||||
|
||||
$key = $_POST["key"];
|
||||
$name = $_FILES["file"]["name"];
|
||||
$tmp = $_FILES["file"]["tmp_name"];
|
||||
$size = filesize($tmp);
|
||||
|
||||
$fileType = mime_content_type($tmp);
|
||||
|
||||
if (!$fileType || $fileType === "application/octet-stream") {
|
||||
$fileType = shell_exec("exiftool -mimetype -b ".$tmp);
|
||||
}
|
||||
|
||||
$split = explode("/", $fileType);
|
||||
$uploadType = $split[0];
|
||||
if ($uploadType !== "image") {
|
||||
error("Only images are currently supported.");
|
||||
}
|
||||
|
||||
if ($size > 25600000) {
|
||||
error("Maximum file size is 25MB.");
|
||||
}
|
||||
|
||||
switch ($uploadType) {
|
||||
case "image":
|
||||
case "audio":
|
||||
case "video":
|
||||
$type = $uploadType;
|
||||
break;
|
||||
|
||||
default:
|
||||
$type = "file";
|
||||
break;
|
||||
}
|
||||
|
||||
switch ($type) {
|
||||
case "image":
|
||||
$imageInfo = getimagesize($tmp);
|
||||
$width = $imageInfo[0];
|
||||
$height = $imageInfo[1];
|
||||
|
||||
if (!$imageInfo || $width < 1 && $height < 1 || strpos(file_get_contents($tmp),"<?php") !== false || strpos(file_get_contents($tmp),"<script") !== false) {
|
||||
error("Something is wrong with this image.");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
$id = generateCode("upload");
|
||||
$insert = sql("INSERT INTO `uploads` (type, id, name, size, session) VALUES (?,?,?,?,?)", [$type, $id, $name, $size, $key]);
|
||||
$path = $GLOBALS["path"]."/uploads/".$id;
|
||||
$move = move_uploaded_file($tmp, $path);
|
||||
|
||||
if (!$insert || !$move) {
|
||||
unlink($path);
|
||||
error("Something went wrong. Try again?");
|
||||
}
|
||||
else {
|
||||
$output["id"] = $id;
|
||||
}
|
||||
|
||||
die(json_encode($output));
|
||||
?>
|
2
wallets/.htaccess
Normal file
@ -0,0 +1,2 @@
|
||||
RewriteEngine on
|
||||
RewriteRule ^.well-known/wallets/(.+)$ index.php?wallet=$1
|
26
wallets/index.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
include "../etc/includes.php";
|
||||
|
||||
$domain = $_SERVER["SERVER_NAME"];
|
||||
$request = $_SERVER["REQUEST_URI"];
|
||||
$wallet = @$_GET["wallet"];
|
||||
|
||||
if ($wallet) {
|
||||
if (strtolower($request) == strtolower("/.well-known/wallets/HNS")) {
|
||||
$info = @sql("SELECT * FROM `domains` WHERE `domain` = ?", [$domain])[0];
|
||||
|
||||
if ($info && @$info["address"]) {
|
||||
echo $info["address"];
|
||||
die();
|
||||
}
|
||||
}
|
||||
}
|
||||
http_response_code(404);
|
||||
?>
|
||||
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
|
||||
<html><head>
|
||||
<title>404 Not Found</title>
|
||||
</head><body>
|
||||
<h1>Not Found</h1>
|
||||
<p>The requested URL was not found on this server.</p>
|
||||
</body></html>
|