This commit is contained in:
hshub 2024-04-16 22:26:30 +00:00
commit fce566a8b9
94 changed files with 52437 additions and 0 deletions

42
.gitignore vendored Normal file
View 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
View 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
View File

@ -0,0 +1 @@
hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa

236
api.php Normal file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

BIN
assets/img/cover.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

25
assets/img/handshake.svg Executable file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/img/icon-128x128.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/img/icon-144x144.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
assets/img/icon-152x152.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

BIN
assets/img/icon-192x192.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
assets/img/icon-384x384.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
assets/img/icon-48x48.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/img/icon-512x512.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
assets/img/icon-72x72.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/img/icon-96x96.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/img/icons/arrow.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
assets/img/icons/audio.gif Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

BIN
assets/img/icons/check.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

BIN
assets/img/icons/clipboard.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

BIN
assets/img/icons/close.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 B

BIN
assets/img/icons/compose.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 919 B

BIN
assets/img/icons/edit.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/img/icons/emoji.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
assets/img/icons/fail.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

BIN
assets/img/icons/fish.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
assets/img/icons/gif.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
assets/img/icons/leave.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
assets/img/icons/lock.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 859 B

BIN
assets/img/icons/mention.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
assets/img/icons/menu.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

BIN
assets/img/icons/message.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
assets/img/icons/pay.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
assets/img/icons/pin.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/img/icons/plus.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
assets/img/icons/replay.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/img/icons/save.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

BIN
assets/img/icons/screen.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
assets/img/icons/search.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
assets/img/icons/signature.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
assets/img/icons/trash.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
assets/img/icons/update.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
assets/img/icons/users.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/img/icons/video.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/img/icons/view.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
assets/img/icons/voice.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
assets/img/icons/warning.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
assets/img/logo.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

5
assets/img/unstoppable.svg Executable file
View 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

File diff suppressed because one or more lines are too long

1
assets/js/anchorme.js Executable file

File diff suppressed because one or more lines are too long

130
assets/js/confetti.js Executable file
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

345
assets/js/he.js Executable file

File diff suppressed because one or more lines are too long

1198
assets/js/hl.js Executable file

File diff suppressed because one or more lines are too long

3646
assets/js/janus.js Executable file

File diff suppressed because it is too large Load Diff

1141
assets/js/listeners.js Executable file

File diff suppressed because it is too large Load Diff

8
assets/js/mask.js Executable file

File diff suppressed because one or more lines are too long

320
assets/js/punycode.js Executable file
View 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

File diff suppressed because one or more lines are too long

10100
assets/js/qr2.js Executable file

File diff suppressed because it is too large Load Diff

1402
assets/js/script.js Executable file

File diff suppressed because it is too large Load Diff

492
assets/js/stream.js Executable file
View 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

File diff suppressed because it is too large Load Diff

68
assets/js/ws.js Executable file
View 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
View 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

Binary file not shown.

14
etc/avatar.php Normal file
View 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
View 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
View File

@ -0,0 +1,12 @@
{
"path": "/var/www/html/hnschat",
"sqlHost": "",
"sqlUser": "",
"sqlPass": "",
"sqlDatabase": "",
"typingDelay": 2000,
"tenorKey": ""
}

59
etc/cron.php Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
<?php
include "config.php";
include "sql.php";
include "functions.php";
?>

385
etc/page/chat.php Normal file
View 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">&copy; <?php echo date("Y"); ?>&nbsp;<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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

83
id.php Normal file
View 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
View 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&amp;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
View 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
View 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
View File

@ -0,0 +1 @@
// This serves no purpose for now besides supporting notifications

12
sync.php Normal file
View 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
View 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
View File

@ -0,0 +1,2 @@
RewriteEngine on
RewriteRule ^.well-known/wallets/(.+)$ index.php?wallet=$1

26
wallets/index.php Normal file
View 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>