mirror of
https://github.com/Nathanwoodburn/hnschat-web.git
synced 2025-12-06 08:42:59 +11:00
v2
This commit is contained in:
14
etc/avatar.php
Normal file
14
etc/avatar.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
$id = $_GET["id"];
|
||||
$avatarFile = $GLOBALS["path"]."/etc/avatars/".$id;
|
||||
|
||||
$domainInfo = domainForID($id);
|
||||
if (@$domainInfo && @$domainInfo["avatar"] && file_exists($avatarFile)) {
|
||||
$image = file_get_contents($avatarFile);
|
||||
$type = mime_content_type($avatarFile);
|
||||
header("Content-Type: ".$type);
|
||||
die($image);
|
||||
}
|
||||
?>
|
||||
6
etc/config.php
Normal file
6
etc/config.php
Normal file
@@ -0,0 +1,6 @@
|
||||
<?php
|
||||
$json = json_decode(file_get_contents(__DIR__."/config.json"), true);
|
||||
foreach ($json as $key => $value) {
|
||||
$GLOBALS[$key] = $value;
|
||||
}
|
||||
?>
|
||||
12
etc/config.sample.json
Normal file
12
etc/config.sample.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"path": "/var/www/html/hnschat",
|
||||
|
||||
"sqlHost": "",
|
||||
"sqlUser": "",
|
||||
"sqlPass": "",
|
||||
"sqlDatabase": "",
|
||||
|
||||
"typingDelay": 2000,
|
||||
|
||||
"tenorKey": ""
|
||||
}
|
||||
59
etc/cron.php
Normal file
59
etc/cron.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
// ACTIVATE NEW CHANNELS
|
||||
$getChannels = sql("SELECT * FROM `channels` WHERE `tx` IS NOT NULL AND `activated` = 0 AND `hidden` = 1");
|
||||
if ($getChannels) {
|
||||
foreach ($getChannels as $key => $data) {
|
||||
$verify = verifyTransaction($data["tx"], $data["fee"]);
|
||||
|
||||
if ($verify) {
|
||||
sql("UPDATE `channels` SET `activated` = 1, `hidden` = 0 WHERE `id` = ?", [$data["id"]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FIND REGISTRY FOR SLD GATED COMMUNITIES
|
||||
$getChannels = sql("SELECT * FROM `channels` WHERE `public` = 0 AND `hidden` = 0 AND `registry` IS NULL");
|
||||
foreach ($getChannels as $key => $data) {
|
||||
$tld = $data["name"];
|
||||
|
||||
$staked = isNameStaked($tld);
|
||||
if ($staked) {
|
||||
sql("UPDATE `channels` SET `registry` = ? WHERE `ai` = ?", [$staked, $data["ai"]]);
|
||||
}
|
||||
}
|
||||
|
||||
// FETCH AVATARS
|
||||
$getUsers = sql("SELECT * FROM `domains` WHERE `claimed` = 1 AND `locked` = 0 AND `deleted` = 0 ORDER BY `ai` DESC");
|
||||
foreach ($getUsers as $key => $data) {
|
||||
$avatar = fetchAvatar($data["domain"]);
|
||||
$avatarFile = $GLOBALS["path"]."/etc/avatars/".$data["id"];
|
||||
|
||||
$tld = tldForDomain($data["domain"]);
|
||||
if ($tld && in_array($tld, getStakedNames())) {
|
||||
if ($data["avatar"]) {
|
||||
$avatar = $data["avatar"];
|
||||
}
|
||||
}
|
||||
|
||||
if ($avatar) {
|
||||
$response = getContentsWithCode($avatar);
|
||||
|
||||
if (validImageWithoutFetch($response["data"])) {
|
||||
if ($response["code"] == 200) {
|
||||
sql("UPDATE `domains` SET `avatar` = ? WHERE `id` = ?", [$avatar, $data["id"]]);
|
||||
|
||||
$newSize = strlen($response["data"]);
|
||||
if (file_exists($avatarFile)) {
|
||||
$currentSize = filesize($avatarFile);
|
||||
}
|
||||
|
||||
if (!@$currentSize || (int)$newSize !== (int)$currentSize) {
|
||||
file_put_contents($avatarFile, $response["data"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
486
etc/functions.php
Normal file
486
etc/functions.php
Normal file
@@ -0,0 +1,486 @@
|
||||
<?php
|
||||
function error($message) {
|
||||
$output = [
|
||||
"success" => false,
|
||||
"message" => $message
|
||||
];
|
||||
|
||||
die(json_encode($output));
|
||||
}
|
||||
|
||||
function generateID($length) {
|
||||
$alphabet = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
|
||||
$pass = array();
|
||||
$alphaLength = strlen($alphabet) - 1;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$n = rand(0, $alphaLength);
|
||||
$pass[] = $alphabet[$n];
|
||||
}
|
||||
return implode($pass);
|
||||
}
|
||||
|
||||
function generateNumber($length) {
|
||||
$alphabet = '123456789';
|
||||
$pass = array();
|
||||
$alphaLength = strlen($alphabet) - 1;
|
||||
for ($i = 0; $i < $length; $i++) {
|
||||
$n = rand(0, $alphaLength);
|
||||
$pass[] = $alphabet[$n];
|
||||
}
|
||||
return implode($pass);
|
||||
}
|
||||
|
||||
function generateCode($type) {
|
||||
switch ($type) {
|
||||
case "session":
|
||||
$db = "sessions";
|
||||
$param = "id";
|
||||
$length = 32;
|
||||
$prefix = "V2-";
|
||||
break;
|
||||
|
||||
case "domain":
|
||||
$db = "domains";
|
||||
$param = "id";
|
||||
$length = 16;
|
||||
break;
|
||||
|
||||
case "preview":
|
||||
$db = "previews";
|
||||
$param = "id";
|
||||
$length = 16;
|
||||
break;
|
||||
|
||||
case "upload":
|
||||
$db = "uploads";
|
||||
$param = "id";
|
||||
$length = 32;
|
||||
break;
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
tryAgain:
|
||||
$id = generateID($length);
|
||||
|
||||
$checkExists = sql("SELECT * FROM `".$db."` WHERE `".$param."` = ?", [@$prefix.$id]);
|
||||
if ($checkExists) {
|
||||
goto tryAgain;
|
||||
}
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
function guidv4($data) {
|
||||
assert(strlen($data) == 16);
|
||||
|
||||
$data[6] = chr(ord($data[6]) & 0x0f | 0x40); // set version to 0100
|
||||
$data[8] = chr(ord($data[8]) & 0x3f | 0x80); // set bits 6-7 to 10
|
||||
|
||||
return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
|
||||
}
|
||||
|
||||
function verifyTransaction($tx, $amount) {
|
||||
$amount = preg_replace("/[^0-9]/", "", $amount);
|
||||
$address = "hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa";
|
||||
|
||||
$response = queryHSW("/wallet/hnschat-hip-2/tx/".$tx);
|
||||
foreach (@$response["outputs"] as $key => $output) {
|
||||
if (@$response["confirmations"] >= 2 && $output["value"] == $amount && $output["address"] = $address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function tldForDomain($domain) {
|
||||
$split = explode(".", $domain);
|
||||
$tld = end($split);
|
||||
|
||||
return $tld;
|
||||
}
|
||||
|
||||
function domainForID($id) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `id` = ?", [$id])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function domainForName($name) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `domain` = ?", [$name])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function activeDomainForName($name) {
|
||||
$getDomain = @sql("SELECT * FROM `domains` WHERE `domain` = ? AND `claimed` = 1 AND `locked` = 0 AND `deleted` = 0", [$name])[0];
|
||||
if ($getDomain) {
|
||||
return $getDomain;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function channelForID($id) {
|
||||
$getChannel = @sql("SELECT * FROM `channels` WHERE `id` = ?", [$id])[0];
|
||||
if ($getChannel) {
|
||||
return $getChannel;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getStakedNames() {
|
||||
$getNames = sql("SELECT `name` FROM `channels` WHERE `slds` = 1 AND `hidden` = 0 ORDER BY `name` ASC");
|
||||
|
||||
$names = [];
|
||||
foreach ($getNames as $key => $value) {
|
||||
$names[] = $value["name"];
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
function getStakedHIP2Names() {
|
||||
$getNames = sql("SELECT `name` FROM `channels` WHERE `slds` = 1 AND `hip2` = 1 AND `hidden` = 0 ORDER BY `name` ASC");
|
||||
|
||||
$names = [];
|
||||
foreach ($getNames as $key => $value) {
|
||||
$names[] = $value["name"];
|
||||
}
|
||||
|
||||
return $names;
|
||||
}
|
||||
|
||||
function isNameStaked($tld) {
|
||||
if ($tld === "eth") {
|
||||
return "ens";
|
||||
}
|
||||
|
||||
$data = [
|
||||
"method" => "getnameresource",
|
||||
"params" => [$tld],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if (@$response["records"]) {
|
||||
foreach ($response["records"] as $key => $value) {
|
||||
if ($value["type"] == "NS") {
|
||||
if (stripos($value["ns"], ".nameserver.io.") !== false || stripos($value["ns"], ".registry.namebase.io.") !== false) {
|
||||
return "namebase";
|
||||
}
|
||||
if ($value["ns"] == "0x06081C6B2B876EABDC41DFD3345e8Fa59588C02e._eth.") {
|
||||
return "impervious";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$hshub = checkVaro($tld);
|
||||
if ($hshub) {
|
||||
return "varo";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function checkVaro($tld) {
|
||||
$url = "https://hshub.io/tld/".$tld;
|
||||
$html = getContents($url);
|
||||
|
||||
$canPurchase = preg_match("/<div class=\"title\">Buy \./", $html);
|
||||
if ($canPurchase) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function avatarFromTXT($content) {
|
||||
if ((substr($content, 0, 7) === "avatar=" || substr($content, 0, 15) === "profile avatar=")) {
|
||||
if (substr($content, 0, 15) === "profile avatar=") {
|
||||
$avatar = substr($content, 15);
|
||||
}
|
||||
else {
|
||||
$avatar = substr($content, 7);
|
||||
}
|
||||
}
|
||||
|
||||
if (!filter_var(@$avatar, FILTER_VALIDATE_URL) === false) {
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function fetchAvatar($domain) {
|
||||
if ($domain) {
|
||||
$getRecords = shell_exec("dig @127.0.0.44 +noall +answer +noidnin +noidnout ".escapeshellarg($domain)." TXT");
|
||||
preg_match_all("/(?<domain>.+)\..+TXT\s\"(?<value>.+)\"/", $getRecords, $matches);
|
||||
|
||||
if ($matches) {
|
||||
foreach ($matches["domain"] as $key => $data) {
|
||||
if ($data === $domain) {
|
||||
$value = $matches["value"][$key];
|
||||
|
||||
$avatar = avatarFromTXT($value);
|
||||
if ($avatar) {
|
||||
return $avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($domain, ".") == false) {
|
||||
$data = [
|
||||
"method" => "getnameresource",
|
||||
"params" => [$domain],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if ($response) {
|
||||
$records = @$response["records"];
|
||||
if ($records) {
|
||||
foreach ($records as $key => $record) {
|
||||
if (@$record["txt"]) {
|
||||
$content = @$record["txt"][0];
|
||||
|
||||
$avatar = avatarFromTXT($content);
|
||||
if ($avatar) {
|
||||
return $avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function getContents($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
$c = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
function getContentsWithCode($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
$data = curl_exec($curl);
|
||||
$code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||||
curl_close($curl);
|
||||
|
||||
return [
|
||||
"data" => $data,
|
||||
"code" => $code
|
||||
];
|
||||
}
|
||||
|
||||
function getContentsWithSpoof($url) {
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_URL, $url);
|
||||
curl_setopt($curl, CURLOPT_PROXY, "127.0.0.1:8080");
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, TRUE);
|
||||
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, TRUE);
|
||||
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_TIMEOUT, 5);
|
||||
curl_setopt($curl, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; Googlebot/2.1; +http://google.com/bot.html)");
|
||||
$c = curl_exec($curl);
|
||||
curl_close($curl);
|
||||
|
||||
return $c;
|
||||
}
|
||||
|
||||
function post($url, $data) {
|
||||
$ch = curl_init($url);
|
||||
$payload = json_encode($data);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type:application/json'));
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$result = curl_exec($ch);
|
||||
curl_close($ch);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
function fetchMetaTags($url) {
|
||||
$output = [];
|
||||
|
||||
if (filter_var($url, FILTER_VALIDATE_URL) !== false) {
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$c = getContentsWithSpoof($url);
|
||||
|
||||
if ($c) {
|
||||
$d = new DomDocument();
|
||||
$d->loadHTML($c);
|
||||
$xp = new domxpath($d);
|
||||
|
||||
foreach ($xp->query("//title") as $el) {
|
||||
$output["title"] = $el->textContent;
|
||||
}
|
||||
foreach ($xp->query("//meta[@property='og:title']") as $el) {
|
||||
$output["title"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:title']") as $el) {
|
||||
$output["title"] = $el->getAttribute("content");
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@property='og:description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:description']") as $el) {
|
||||
$output["description"] = $el->getAttribute("content");
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='og:image']") as $el) {
|
||||
$image = $el->getAttribute("content");
|
||||
if (validImage($image)) {
|
||||
$output["image"] = $image;
|
||||
}
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:image']") as $el) {
|
||||
$image = $el->getAttribute("content");
|
||||
if (validImage($image)) {
|
||||
$output["image"] = $image;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xp->query("//meta[@property='og:video:secure_url']") as $el) {
|
||||
$output["video"] = $el->getAttribute("content");
|
||||
}
|
||||
foreach ($xp->query("//meta[@name='og:video:secure_url']") as $el) {
|
||||
$output["video"] = $el->getAttribute("content");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (@$output["title"]) {
|
||||
$id = generateCode("preview");
|
||||
$insert = sql("INSERT INTO `previews` (id, link, title, description, image, video) VALUES (?,?,?,?,?,?)", [$id, $url, @$output["title"], @$output["description"], @$output["image"], @$output["video"]]);
|
||||
|
||||
if ($insert) {
|
||||
$output["id"] = $id;
|
||||
}
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
function validImage($url) {
|
||||
$string = getContents($url);
|
||||
if ($string) {
|
||||
$id = generateID(16);
|
||||
$file = "/tmp/".$id;
|
||||
$f = fopen($file, 'wb');
|
||||
fputs($f, $string);
|
||||
fclose($f);
|
||||
$size = getimagesize($file);
|
||||
unlink($file);
|
||||
}
|
||||
return (strtolower(substr(@$size['mime'], 0, 5)) == 'image' ? true : false);
|
||||
}
|
||||
|
||||
function validImageWithoutFetch($string) {
|
||||
if ($string) {
|
||||
$id = generateID(16);
|
||||
$file = "/tmp/".$id;
|
||||
$f = fopen($file, 'wb');
|
||||
fputs($f, $string);
|
||||
fclose($f);
|
||||
$size = getimagesize($file);
|
||||
unlink($file);
|
||||
}
|
||||
return (strtolower(substr(@$size['mime'], 0, 5)) == 'image' ? true : false);
|
||||
}
|
||||
|
||||
function validateAddress($address) {
|
||||
$data = [
|
||||
"method" => "validateaddress",
|
||||
"params" => [$address],
|
||||
];
|
||||
$response = queryHSD($data);
|
||||
|
||||
if (@$response["isvalid"] && @$response["isspendable"]) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function queryHSD($data) {
|
||||
if (@$data["params"]) {
|
||||
foreach ($data["params"] as $key => $value) {
|
||||
$data["params"][$key] = trim($value);
|
||||
}
|
||||
}
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data));
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ["Content-Type:application/json"]);
|
||||
curl_setopt($curl, CURLOPT_URL,"http://x:a831d3c59ce474d8e13a7cea3a3935d3d5a55b84698abe38f2eea2329327e2c50@127.0.0.1:12037");
|
||||
curl_setopt($curl, CURLOPT_POST, 1);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
curl_close ($curl);
|
||||
|
||||
if ($response) {
|
||||
$info = @json_decode($response, true);
|
||||
|
||||
if (@$info["result"]) {
|
||||
return $info["result"];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function queryHSW($endpoint) {
|
||||
$endpoint = trim($endpoint);
|
||||
|
||||
$curl = curl_init();
|
||||
curl_setopt($curl, CURLOPT_HTTPHEADER, ["Content-Type:application/json"]);
|
||||
curl_setopt($curl, CURLOPT_URL,"http://x:a831d3c59ce474d8e13a7cea3a3935d3d5a55b84698abe38f2eea2329327e2c50@127.0.0.1:12039".$endpoint);
|
||||
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||
$response = curl_exec($curl);
|
||||
curl_close ($curl);
|
||||
|
||||
if ($response) {
|
||||
$info = @json_decode($response, true);
|
||||
|
||||
if (@$info) {
|
||||
return $info;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
?>
|
||||
45
etc/head.php
Normal file
45
etc/head.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
header("Access-Control-Allow-Origin: *");
|
||||
|
||||
$revision = trim(file_get_contents(".git/refs/heads/main"));
|
||||
?>
|
||||
<title>HNSChat</title>
|
||||
<meta charset="utf-8">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="darkreader" content="noplz">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
|
||||
<meta name="title" content="HNSChat">
|
||||
<meta name="description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://hns.chat/">
|
||||
<meta property="og:title" content="HNSChat">
|
||||
<meta property="og:description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="og:image" content="https://hns.chat/assets/img/cover">
|
||||
<meta property="twitter:card" content="summary_large_image">
|
||||
<meta property="twitter:url" content="https://hns.chat/">
|
||||
<meta property="twitter:title" content="HNSChat">
|
||||
<meta property="twitter:description" content="HNSChat is a free end-to-end encrypted messaging platform where you chat using your Handshake names.">
|
||||
<meta property="twitter:image" content="https://hns.chat/assets/img/cover">
|
||||
<link rel="manifest" href="/manifest.json">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Rubik&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/assets/css/style?r=<?php echo $revision; ?>">
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script type="text/javascript" src="https://auth.varo.domains/v1"></script>
|
||||
<script type="text/javascript" src="/assets/js/qr?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/qr2?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/he?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/zwj?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/date?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/emojis?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/anchorme?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/mask?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/confetti?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/dish?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/janus?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript" src="/assets/js/adapter?r=<?php echo $revision; ?>"></script>
|
||||
<script type="module" src="/assets/js/script?r=<?php echo $revision; ?>"></script>
|
||||
<script type="text/javascript">
|
||||
var revision = "<?php echo $revision; ?>";
|
||||
</script>
|
||||
5
etc/includes.php
Normal file
5
etc/includes.php
Normal file
@@ -0,0 +1,5 @@
|
||||
<?php
|
||||
include "config.php";
|
||||
include "sql.php";
|
||||
include "functions.php";
|
||||
?>
|
||||
385
etc/page/chat.php
Normal file
385
etc/page/chat.php
Normal file
@@ -0,0 +1,385 @@
|
||||
<body data-page="chat" data-version="<?php echo $revision; ?>">
|
||||
<div class="connecting">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div id="blackout"></div>
|
||||
<div class="popover" data-name="update">
|
||||
<div class="head">
|
||||
<div class="title">Update</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">An update is available. Please reload for the best possible experience.</div>
|
||||
<div class="button" data-action="reload">Reload</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover" data-name="newConversation">
|
||||
<div class="head">
|
||||
<div class="title">New Conversation</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<input class="tab" type="text" name="domain" placeholder="hnschat/">
|
||||
<input type="text" name="message" placeholder="Message">
|
||||
<div class="button" data-action="startConversation">Start Conversation</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover" data-name="syncSession">
|
||||
<div class="head">
|
||||
<div class="title">Sync Session</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">Use this QR code or link to sync your session to another browser.</div>
|
||||
<div id="qrcode"></div>
|
||||
<div class="group">
|
||||
<input readonly="readonly" class="copyable" type="text" name="syncLink">
|
||||
<div class="icon action clipboard" data-action="clipboard"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="donate">
|
||||
<div class="head">
|
||||
<div class="title">Donate</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="subtitle">If you enjoy using this free service, please consider donating.</div>
|
||||
<div class="group">
|
||||
<input readonly="readonly" class="copyable" type="text" name="donateAddress" value="hs1qf0cxy6ukhgjlmqfhe0tpw800t2tcul4s0szwqa">
|
||||
<div class="icon action clipboard" data-action="clipboard"></div>
|
||||
</div>
|
||||
<div class="center">© <?php echo date("Y"); ?> <a href="https://eskimo.software" target="_blank">Eskimo Software</a></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="pay">
|
||||
<div class="head">
|
||||
<div class="title">Send HNS</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="loading flex shown">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<input type="hidden" name="address">
|
||||
<input type="text" name="hns" placeholder="0 HNS">
|
||||
<div class="button" data-action="sendPayment">Send with Bob Extension</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" data-name="settings">
|
||||
<div class="head">
|
||||
<div class="title">Settings</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="setting">
|
||||
<div class="subtitle">Avatar URL</div>
|
||||
<input class="remote tab" type="text" name="avatar" placeholder="">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">HNS Wallet Address</div>
|
||||
<input class="remote tab" type="text" name="address" placeholder="">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Chat Bubble Color</div>
|
||||
<input class="local color tab" type="color" name="bubbleBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Self Chat Bubble Color</div>
|
||||
<input class="local color tab" type="color" name="bubbleSelfBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Mention Chat Bubble Color</div>
|
||||
<input class="local color" type="color" name="bubbleMentionBackground">
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Chat Display Mode</div>
|
||||
<select class="local" name="chatDisplayMode">
|
||||
<option value="normal">Normal</option>
|
||||
<option value="compact">Compact</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="setting">
|
||||
<div class="subtitle">Sync Session</div>
|
||||
<div class="center action link" data-action="syncSession">Show QR + Link</div>
|
||||
</div>
|
||||
<div class="button" data-action="saveSettings">Save</div>
|
||||
</div>
|
||||
<div class="response error"></div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="userContext">
|
||||
<div class="actions">
|
||||
<div class="action icon edit" data-action="editProfile"></div>
|
||||
<div class="action icon save" data-action="saveProfile"></div>
|
||||
<div class="action icon close" data-action="undoProfile"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li>
|
||||
<div class="pic"></div>
|
||||
<span class="user subtitle"></span>
|
||||
<div class="icon type"></div>
|
||||
</li>
|
||||
<li class="bio">
|
||||
<div class="title small">Bio</div>
|
||||
<div class="bioHolder">
|
||||
<div class="bio subtitle"></div>
|
||||
<div class="limit"></div>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<div class="title small">Joined</div>
|
||||
<span class="joined subtitle"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="separator"></div>
|
||||
<ul class="contextActions">
|
||||
<li class="action" data-action="newConversationWith">
|
||||
<div class="icon message"></div>
|
||||
<span>Message</span>
|
||||
</li>
|
||||
<li class="action" data-action="mentionUser">
|
||||
<div class="icon mention"></div>
|
||||
<span>Mention</span>
|
||||
</li>
|
||||
<li class="action" data-action="slapUser">
|
||||
<div class="icon fish"></div>
|
||||
<span>Slap</span>
|
||||
</li>
|
||||
<li class="action speaker" data-action="inviteVideo">
|
||||
<div class="icon voice"></div>
|
||||
<span>Speaker</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="channelContext">
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li>
|
||||
<span class="channel subtitle"></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="separator"></div>
|
||||
<ul>
|
||||
<li class="action" data-action="switchConversation">
|
||||
<div class="icon view"></div>
|
||||
<span>View</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover contextMenu" data-name="messageContext">
|
||||
<div class="body">
|
||||
<ul>
|
||||
<li class="action reply" data-action="reply">
|
||||
<div class="icon reply"></div>
|
||||
<span>Reply</span>
|
||||
</li>
|
||||
<li class="action emoji" data-action="emojis">
|
||||
<div class="icon emoji"></div>
|
||||
<span>React</span>
|
||||
</li>
|
||||
<li class="action pin" data-action="pinMessage">
|
||||
<div class="icon pin"></div>
|
||||
<span>Pin</span>
|
||||
</li>
|
||||
<li class="action delete error" data-action="deleteMessage">
|
||||
<div class="icon delete"></div>
|
||||
<span>Delete</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="holder">
|
||||
<div class="header">
|
||||
<div class="left">
|
||||
<div class="icon menu"></div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div class="logo">
|
||||
<img draggable="false" src="/assets/img/handshake">
|
||||
</div>
|
||||
<div class="messageHeader">
|
||||
<table></table>
|
||||
<div class="pinnedMessage flex">
|
||||
<div class="icon pin"></div>
|
||||
<div class="message"></div>
|
||||
<div class="action icon delete" data-action="pinMessage"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="end">
|
||||
<div id="me"></div>
|
||||
<div class="domains">
|
||||
<select></select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right">
|
||||
<div class="icon users"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="chats">
|
||||
<div id="conversations" class="sidebar">
|
||||
<div class="title">
|
||||
<div class="tabs">
|
||||
<div class="tab" data-tab="channels">Channels</div>
|
||||
<div class="tab" data-tab="pms">Private</div>
|
||||
</div>
|
||||
<div class="actionHolder">
|
||||
<div class="action icon compose" data-action="newConversation"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sections">
|
||||
<div class="section channels">
|
||||
<table></table>
|
||||
</div>
|
||||
<div class="section pms">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer">
|
||||
<div class="action link" data-action="settings">Settings</div>
|
||||
<div class="action link" data-action="docs">Docs</div>
|
||||
<div class="action link" data-action="donate">Donate</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
<div class="pinnedMessage flex">
|
||||
<div class="icon pin"></div>
|
||||
<div class="message"></div>
|
||||
<div class="action icon delete" data-action="pinMessage"></div>
|
||||
</div>
|
||||
<div id="closeMenu"></div>
|
||||
<div id="videoInfo" class="flex">
|
||||
<div class="info">
|
||||
<div class="users"></div>
|
||||
<div class="title flex">
|
||||
<span>LIVE</span>
|
||||
<div class="icon audio"></div>
|
||||
</div>
|
||||
<div class="watching flex">
|
||||
<div class="watchers"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<div class="link" data-action="viewVideo">Watch</div>
|
||||
<div class="link" data-action="startVideo">Stream</div>
|
||||
<div class="link" data-action="joinVideo">Join</div>
|
||||
<div class="link destructive" data-action="leaveVideo">Leave</div>
|
||||
<div class="link destructive" data-action="endVideo">End</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="videoContainer" class="flex">
|
||||
<div class="controls">
|
||||
<div class="button outline muted" data-action="toggleScreen">
|
||||
<div class="icon screen"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="toggleAudio">
|
||||
<div class="icon voice"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="toggleVideo">
|
||||
<div class="icon video"></div>
|
||||
</div>
|
||||
<div class="button outline muted" data-action="leaveVideo">
|
||||
<div class="icon leave"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="messageHolder">
|
||||
<div class="popover" id="completions" data-name="completions">
|
||||
<div class="head">
|
||||
<div class="title"></div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<table class="list"></table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="popover" id="react" data-name="react">
|
||||
<div class="head">
|
||||
<div class="title">
|
||||
<div class="tabs">
|
||||
<div class="tab" data-name="gifs">Gifs</div>
|
||||
<div class="tab" data-name="emojis">Emojis</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icon action close" data-action="close"></div>
|
||||
</div>
|
||||
<div class="body">
|
||||
<div class="search">
|
||||
<input type="text" name="searchGifs" placeholder="Search Tenor">
|
||||
<input type="text" class="shown" name="searchEmojis" placeholder="Search Emojis">
|
||||
</div>
|
||||
<div class="grids">
|
||||
<div class="grid" data-type="gifs">
|
||||
<div class="section" data-type="categories"></div>
|
||||
<div class="section flex" data-type="gifs">
|
||||
<div class="column" data-column="0"></div>
|
||||
<div class="column" data-column="1"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid shown" data-type="emojis"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="messages"></div>
|
||||
<div id="jumpToPresent" class="hidden">
|
||||
<div class="action" data-action="jumpToPresent">Jump To Present</div>
|
||||
</div>
|
||||
<div class="loading flex">
|
||||
<div class="lds-facebook"><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="inputContainer">
|
||||
<div id="typing" class="flex">
|
||||
<div class="message"></div>
|
||||
</div>
|
||||
<div id="replying" class="flex">
|
||||
<div class="message">Replying to <span class="name"></span></div>
|
||||
<div class="action icon remove" data-action="removeReply"></div>
|
||||
</div>
|
||||
<div id="attachments" class="flex"></div>
|
||||
<div class="inputHolder">
|
||||
<div class="input">
|
||||
<div class="action icon plus" data-action="file">
|
||||
<input id="file" type="file" name="file">
|
||||
</div>
|
||||
<div class="action icon pay" data-action="pay"></div>
|
||||
<div class="inputs">
|
||||
<textarea id="message" placeholder="Message"></textarea>
|
||||
</div>
|
||||
<div class="action icon gif big" data-action="gifs"></div>
|
||||
<div class="action icon emoji big" data-action="emojis"></div>
|
||||
</div>
|
||||
<div class="locked"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="users" class="sidebar">
|
||||
<div class="title">
|
||||
<div class="group normal">
|
||||
<div class="action icon search" data-action="searchUsers"></div>
|
||||
<div>Users</div>
|
||||
</div>
|
||||
<div class="group flex searching">
|
||||
<input type="text" name="search">
|
||||
<div class="action icon close" data-action="searchUsers"></div>
|
||||
</div>
|
||||
<div id="count"></div>
|
||||
</div>
|
||||
<div class="sections">
|
||||
<div class="section users">
|
||||
<table></table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="avatars"></div>
|
||||
</body>
|
||||
26
etc/preview.php
Normal file
26
etc/preview.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
include "includes.php";
|
||||
|
||||
$id = $_GET["id"];
|
||||
$previewFile = $GLOBALS["path"]."/etc/previews/".$id;
|
||||
|
||||
$getImage = @sql("SELECT `image` FROM `previews` WHERE `id` = ? AND `image` IS NOT NULL", [$id])[0];
|
||||
|
||||
if ($getImage) {
|
||||
if (file_exists($previewFile)) {
|
||||
$image = file_get_contents($previewFile);
|
||||
$type = mime_content_type($previewFile);
|
||||
}
|
||||
else {
|
||||
$getImage["image"] = html_entity_decode(html_entity_decode($getImage["image"]));
|
||||
$image = getContents($getImage["image"]);
|
||||
if (validImageWithoutFetch($image)) {
|
||||
file_put_contents($previewFile, $image);
|
||||
$type = mime_content_type($previewFile);
|
||||
}
|
||||
}
|
||||
|
||||
header("Content-Type: ".$type);
|
||||
die($image);
|
||||
}
|
||||
?>
|
||||
68
etc/sql.php
Normal file
68
etc/sql.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
$GLOBALS["sqlInfo"] = [
|
||||
"host" => $GLOBALS["sqlHost"],
|
||||
"user" => $GLOBALS["sqlUser"],
|
||||
"pass" => $GLOBALS["sqlPass"],
|
||||
"db" => $GLOBALS["sqlDatabase"],
|
||||
"options" => [
|
||||
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
|
||||
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
]
|
||||
];
|
||||
$GLOBALS["sqlDSN"] = "mysql:host=".$GLOBALS["sqlInfo"]["host"].";dbname=".$GLOBALS["sqlInfo"]["db"].";charset=utf8mb4";
|
||||
|
||||
function initSQL() {
|
||||
retry:
|
||||
|
||||
try {
|
||||
$GLOBALS["remoteSQL"] = new PDO($GLOBALS["sqlDSN"], $GLOBALS["sqlInfo"]["user"], $GLOBALS["sqlInfo"]["pass"], $GLOBALS["sqlInfo"]["options"]);
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (strpos($message, "Connection refused") !== false) {
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sql($query, $values = []) {
|
||||
if (!@$GLOBALS["remoteSQL"]) {
|
||||
initSQL();
|
||||
}
|
||||
|
||||
retry:
|
||||
try {
|
||||
$statement = $GLOBALS["remoteSQL"]->prepare($query);
|
||||
$success = $statement->execute($values);
|
||||
|
||||
$result = $statement->fetchAll();
|
||||
|
||||
if (count($result) > 1) {
|
||||
return $result;
|
||||
}
|
||||
else if (count($result) == 1) {
|
||||
return [$result[0]];
|
||||
}
|
||||
else if ($success && (substr($query, 0, 12) === "INSERT INTO " || substr($query, 0, 12) === "DELETE FROM " || (substr($query, 0, 7) === "UPDATE "))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
catch (\PDOException $e) {
|
||||
$message = $e->getMessage();
|
||||
|
||||
if (strpos($message, "MySQL server has gone away") !== false || strpos($message, "Communication link failure") !== false) {
|
||||
initSQL();
|
||||
|
||||
goto retry;
|
||||
}
|
||||
else {
|
||||
//var_dump($message);
|
||||
//error
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
Reference in New Issue
Block a user