inital working port of old sdbot from llmtox

This commit is contained in:
Green Sky 2023-12-06 15:12:34 +01:00
commit b6beee1d76
No known key found for this signature in database
12 changed files with 2164 additions and 0 deletions

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
.vs/
*.o
*.swp
~*
*~
.idea/
cmake-build-debug/
cmake-build-debugandtest/
cmake-build-release/
*.stackdump
*.coredump
compile_commands.json
/build*
/result*
.clangd
.cache
.DS_Store
.AppleDouble
.LSOverride
CMakeLists.txt.user*
CMakeCache.txt
*.tox
imgui.ini

73
CMakeLists.txt Normal file
View File

@ -0,0 +1,73 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
# cmake setup begin
project(solanaceae_sdbot-webui)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(SOLANACEAE_SDBOT_WEBUI_STANDALONE ON)
# why the f do i need this >:(
set(NOT_SOLANACEAE_SDBOT_WEBUI_STANDALONE OFF)
else()
set(SOLANACEAE_SDBOT_WEBUI_STANDALONE OFF)
set(NOT_SOLANACEAE_SDBOT_WEBUI_STANDALONE ON)
endif()
message("II SOLANACEAE_SDBOT_WEBUI_STANDALONE " ${SOLANACEAE_SDBOT_WEBUI_STANDALONE})
option(SOLANACEAE_SDBOT_WEBUI_BUILD_PLUGINS "Build the toxic_games plugins" ${SOLANACEAE_SDBOT_WEBUI_STANDALONE})
if (SOLANACEAE_SDBOT_WEBUI_STANDALONE)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
# defaulting to debug mode, if not specified
if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE "Debug")
endif()
# setup my vim ycm :D
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# more paths
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib")
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin")
endif()
# external libs
add_subdirectory(./external) # before increasing warn levels, sad :(
if (SOLANACEAE_SDBOT_WEBUI_STANDALONE)
set(CMAKE_CXX_EXTENSIONS OFF)
# bump up warning levels appropriately for clang, gcc & msvc
if (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU" OR ${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
add_compile_options(
-Wall -Wextra # Reasonable and standard
-Wpedantic # Warn if non-standard C++ is used
-Wunused # Warn on anything being unused
#-Wconversion # Warn on type conversions that may lose data
#-Wsign-conversion # Warn on sign conversions
-Wshadow # Warn if a variable declaration shadows one from a parent context
)
if (NOT WIN32)
#link_libraries(-fsanitize=address,undefined)
#link_libraries(-fsanitize=undefined)
endif()
elseif (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC")
if (CMAKE_CXX_FLAGS MATCHES "/W[0-4]")
string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4")
endif()
endif()
endif()
# cmake setup end
add_subdirectory(./src)
if (SOLANACEAE_SDBOT_WEBUI_BUILD_PLUGINS)
message("II SOLANACEAE_SDBOT_WEBUI_BUILD_PLUGINS " ${SOLANACEAE_SDBOT_WEBUI_BUILD_PLUGINS})
add_subdirectory(./plugins)
endif()

98
external/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,98 @@
cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
include(FetchContent)
add_subdirectory(./happyhttp)
if (
NOT TARGET libsodium AND
NOT TARGET unofficial-sodium::sodium AND
NOT TARGET unofficial-sodium::sodium_config_public AND
NOT TARGET sodium
)
# for find sodium
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
find_package(unofficial-sodium CONFIG QUIET)
find_package(sodium QUIET)
if(unofficial-sodium_FOUND) # vcpkg
if(TARGET unofficial-sodium::sodium)
#TODO: alias can not target another alias
#target_link_libraries(toxcore unofficial-sodium::sodium)
#add_library(libsodium ALIAS unofficial-sodium::sodium)
add_library(libsodium INTERFACE)
target_link_libraries(libsodium INTERFACE unofficial-sodium::sodium)
endif()
if(TARGET unofficial-sodium::sodium_config_public)
#TODO: alias can not target another alias
#target_link_libraries(toxcore unofficial-sodium::sodium_config_public)
#add_library(libsodium ALIAS unofficial-sodium::sodium_config_public)
add_library(libsodium INTERFACE)
target_link_libraries(libsodium INTERFACE unofficial-sodium::sodium_config_public)
endif()
elseif(sodium_FOUND)
#add_library(libsodium ALIAS sodium)
add_library(libsodium INTERFACE)
target_link_libraries(libsodium INTERFACE sodium)
else()
message(SEND_ERROR "missing libsodium")
endif()
endif()
# TODO: move entt dep into solanaceae_contact
if (NOT TARGET EnTT::EnTT)
FetchContent_Declare(EnTT
GIT_REPOSITORY https://github.com/skypjack/entt.git
GIT_TAG v3.12.2
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(EnTT)
endif()
if (NOT TARGET solanaceae_util)
FetchContent_Declare(solanaceae_util
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_util.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_util)
endif()
if (NOT TARGET solanaceae_contact)
FetchContent_Declare(solanaceae_contact
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_contact.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_contact)
endif()
if (NOT TARGET solanaceae_message3)
FetchContent_Declare(solanaceae_message3
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_message3.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_message3)
endif()
if (NOT TARGET solanaceae_plugin)
FetchContent_Declare(solanaceae_plugin
GIT_REPOSITORY https://github.com/Green-Sky/solanaceae_plugin.git
GIT_TAG master
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(solanaceae_plugin)
endif()
if (NOT TARGET nlohmann_json::nlohmann_json)
FetchContent_Declare(json
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
URL_HASH SHA256=d6c65aca6b1ed68e7a182f4757257b107ae403032760ed6ef121c9d55e81757d
EXCLUDE_FROM_ALL
)
FetchContent_MakeAvailable(json)
endif()

297
external/cmake/Findsodium.cmake vendored Normal file
View File

@ -0,0 +1,297 @@
# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
#
# To the extent possible under law, the author(s) have dedicated all
# copyright and related and neighboring rights to this software to the
# public domain worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication
# along with this software. If not, see
#
# http://creativecommons.org/publicdomain/zero/1.0/
#
########################################################################
# Tries to find the local libsodium installation.
#
# On Windows the sodium_DIR environment variable is used as a default
# hint which can be overridden by setting the corresponding cmake variable.
#
# Once done the following variables will be defined:
#
# sodium_FOUND
# sodium_INCLUDE_DIR
# sodium_LIBRARY_DEBUG
# sodium_LIBRARY_RELEASE
#
#
# Furthermore an imported "sodium" target is created.
#
if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
set(_GCC_COMPATIBLE 1)
endif()
# static library option
if (NOT DEFINED sodium_USE_STATIC_LIBS)
option(sodium_USE_STATIC_LIBS "enable to statically link against sodium" OFF)
endif()
if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
unset(sodium_LIBRARY CACHE)
unset(sodium_LIBRARY_DEBUG CACHE)
unset(sodium_LIBRARY_RELEASE CACHE)
unset(sodium_DLL_DEBUG CACHE)
unset(sodium_DLL_RELEASE CACHE)
set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
endif()
########################################################################
# UNIX
if (UNIX)
# import pkg-config
find_package(PkgConfig QUIET)
if (PKG_CONFIG_FOUND)
pkg_check_modules(sodium_PKG QUIET libsodium)
endif()
if(sodium_USE_STATIC_LIBS)
foreach(_libname ${sodium_PKG_STATIC_LIBRARIES})
if (NOT _libname MATCHES "^lib.*\\.a$") # ignore strings already ending with .a
list(INSERT sodium_PKG_STATIC_LIBRARIES 0 "lib${_libname}.a")
endif()
endforeach()
list(REMOVE_DUPLICATES sodium_PKG_STATIC_LIBRARIES)
# if pkgconfig for libsodium doesn't provide
# static lib info, then override PKG_STATIC here..
if (NOT sodium_PKG_STATIC_FOUND)
set(sodium_PKG_STATIC_LIBRARIES libsodium.a)
endif()
set(XPREFIX sodium_PKG_STATIC)
else()
if (NOT sodium_PKG_FOUND)
set(sodium_PKG_LIBRARIES sodium)
endif()
set(XPREFIX sodium_PKG)
endif()
find_path(sodium_INCLUDE_DIR sodium.h
HINTS ${${XPREFIX}_INCLUDE_DIRS}
)
find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES}
HINTS ${${XPREFIX}_LIBRARY_DIRS}
)
find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES}
HINTS ${${XPREFIX}_LIBRARY_DIRS}
)
########################################################################
# Windows
elseif (WIN32)
set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
mark_as_advanced(sodium_DIR)
find_path(sodium_INCLUDE_DIR sodium.h
HINTS ${sodium_DIR}
PATH_SUFFIXES include
)
if (MSVC)
# detect target architecture
file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp" [=[
#if defined _M_IX86
#error ARCH_VALUE x86_32
#elif defined _M_X64
#error ARCH_VALUE x86_64
#endif
#error ARCH_VALUE unknown
]=])
try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.cpp"
OUTPUT_VARIABLE _COMPILATION_LOG
)
string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
# construct library path
if (_TARGET_ARCH STREQUAL "x86_32")
string(APPEND _PLATFORM_PATH "Win32")
elseif(_TARGET_ARCH STREQUAL "x86_64")
string(APPEND _PLATFORM_PATH "x64")
else()
message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
endif()
string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
if (MSVC_VERSION LESS 1900)
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
else()
math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
endif()
string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
if (sodium_USE_STATIC_LIBS)
string(APPEND _PLATFORM_PATH "/static")
else()
string(APPEND _PLATFORM_PATH "/dynamic")
endif()
string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
find_library(sodium_LIBRARY_DEBUG libsodium.lib
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
)
find_library(sodium_LIBRARY_RELEASE libsodium.lib
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
)
if (NOT sodium_USE_STATIC_LIBS)
set(CMAKE_FIND_LIBRARY_SUFFIXES_BCK ${CMAKE_FIND_LIBRARY_SUFFIXES})
set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
find_library(sodium_DLL_DEBUG libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
)
find_library(sodium_DLL_RELEASE libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
)
set(CMAKE_FIND_LIBRARY_SUFFIXES ${CMAKE_FIND_LIBRARY_SUFFIXES_BCK})
endif()
elseif(_GCC_COMPATIBLE)
if (sodium_USE_STATIC_LIBS)
find_library(sodium_LIBRARY_DEBUG libsodium.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
find_library(sodium_LIBRARY_RELEASE libsodium.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
else()
find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
HINTS ${sodium_DIR}
PATH_SUFFIXES lib
)
file(GLOB _DLL
LIST_DIRECTORIES false
RELATIVE "${sodium_DIR}/bin"
"${sodium_DIR}/bin/libsodium*.dll"
)
find_library(sodium_DLL_DEBUG ${_DLL} libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES bin
)
find_library(sodium_DLL_RELEASE ${_DLL} libsodium
HINTS ${sodium_DIR}
PATH_SUFFIXES bin
)
endif()
else()
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
endif()
########################################################################
# unsupported
else()
message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
endif()
########################################################################
# common stuff
# extract sodium version
if (sodium_INCLUDE_DIR)
set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
if (EXISTS _VERSION_HEADER)
file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
sodium_VERSION "${_VERSION_HEADER_CONTENT}")
set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
endif()
endif()
# communicate results
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(
sodium # The name must be either uppercase or match the filename case.
REQUIRED_VARS
sodium_LIBRARY_RELEASE
sodium_LIBRARY_DEBUG
sodium_INCLUDE_DIR
VERSION_VAR
sodium_VERSION
)
if(Sodium_FOUND)
set(sodium_LIBRARIES
optimized ${sodium_LIBRARY_RELEASE} debug ${sodium_LIBRARY_DEBUG})
endif()
# mark file paths as advanced
mark_as_advanced(sodium_INCLUDE_DIR)
mark_as_advanced(sodium_LIBRARY_DEBUG)
mark_as_advanced(sodium_LIBRARY_RELEASE)
if (WIN32)
mark_as_advanced(sodium_DLL_DEBUG)
mark_as_advanced(sodium_DLL_RELEASE)
endif()
# create imported target
if(sodium_USE_STATIC_LIBS)
set(_LIB_TYPE STATIC)
else()
set(_LIB_TYPE SHARED)
endif()
if(NOT TARGET sodium)
add_library(sodium ${_LIB_TYPE} IMPORTED)
endif()
set_target_properties(sodium PROPERTIES
INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
IMPORTED_LINK_INTERFACE_LANGUAGES "C"
)
if (sodium_USE_STATIC_LIBS)
set_target_properties(sodium PROPERTIES
INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
)
else()
if (UNIX)
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
)
elseif (WIN32)
set_target_properties(sodium PROPERTIES
IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
)
if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
)
endif()
if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
set_target_properties(sodium PROPERTIES
IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
)
endif()
endif()
endif()

10
external/happyhttp/CMakeLists.txt vendored Normal file
View File

@ -0,0 +1,10 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
add_library(happyhttp STATIC
./happyhttp/happyhttp.h
./happyhttp/happyhttp.cpp
)
target_include_directories(happyhttp PUBLIC .)
target_compile_features(happyhttp PUBLIC cxx_std_11)

View File

@ -0,0 +1,940 @@
/*
* HappyHTTP - a simple HTTP library
* Version 0.1
*
* Copyright (c) 2006 Ben Campbell
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
*/
#include "happyhttp.h"
#ifndef _WIN32
// #include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h> // for gethostbyname()
#include <errno.h>
#else
#include <WinSock2.h>
#define vsnprintf _vsnprintf
#endif
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <assert.h>
#include <string>
#include <vector>
#include <string>
#include <algorithm>
#ifndef _WIN32
#include <strings.h>
#define _stricmp strcasecmp
#endif
using namespace std;
namespace happyhttp
{
#ifdef WIN32
const char* GetWinsockErrorString( int err );
#endif
//---------------------------------------------------------------------
// Helper functions
//---------------------------------------------------------------------
void BailOnSocketError( const char* context )
{
#ifdef WIN32
int e = WSAGetLastError();
const char* msg = GetWinsockErrorString( e );
#else
const char* msg = strerror( errno );
#endif
throw Wobbly( "%s: %s", context, msg );
}
#ifdef WIN32
const char* GetWinsockErrorString( int err )
{
switch( err)
{
case 0: return "No error";
case WSAEINTR: return "Interrupted system call";
case WSAEBADF: return "Bad file number";
case WSAEACCES: return "Permission denied";
case WSAEFAULT: return "Bad address";
case WSAEINVAL: return "Invalid argument";
case WSAEMFILE: return "Too many open sockets";
case WSAEWOULDBLOCK: return "Operation would block";
case WSAEINPROGRESS: return "Operation now in progress";
case WSAEALREADY: return "Operation already in progress";
case WSAENOTSOCK: return "Socket operation on non-socket";
case WSAEDESTADDRREQ: return "Destination address required";
case WSAEMSGSIZE: return "Message too long";
case WSAEPROTOTYPE: return "Protocol wrong type for socket";
case WSAENOPROTOOPT: return "Bad protocol option";
case WSAEPROTONOSUPPORT: return "Protocol not supported";
case WSAESOCKTNOSUPPORT: return "Socket type not supported";
case WSAEOPNOTSUPP: return "Operation not supported on socket";
case WSAEPFNOSUPPORT: return "Protocol family not supported";
case WSAEAFNOSUPPORT: return "Address family not supported";
case WSAEADDRINUSE: return "Address already in use";
case WSAEADDRNOTAVAIL: return "Can't assign requested address";
case WSAENETDOWN: return "Network is down";
case WSAENETUNREACH: return "Network is unreachable";
case WSAENETRESET: return "Net connection reset";
case WSAECONNABORTED: return "Software caused connection abort";
case WSAECONNRESET: return "Connection reset by peer";
case WSAENOBUFS: return "No buffer space available";
case WSAEISCONN: return "Socket is already connected";
case WSAENOTCONN: return "Socket is not connected";
case WSAESHUTDOWN: return "Can't send after socket shutdown";
case WSAETOOMANYREFS: return "Too many references, can't splice";
case WSAETIMEDOUT: return "Connection timed out";
case WSAECONNREFUSED: return "Connection refused";
case WSAELOOP: return "Too many levels of symbolic links";
case WSAENAMETOOLONG: return "File name too long";
case WSAEHOSTDOWN: return "Host is down";
case WSAEHOSTUNREACH: return "No route to host";
case WSAENOTEMPTY: return "Directory not empty";
case WSAEPROCLIM: return "Too many processes";
case WSAEUSERS: return "Too many users";
case WSAEDQUOT: return "Disc quota exceeded";
case WSAESTALE: return "Stale NFS file handle";
case WSAEREMOTE: return "Too many levels of remote in path";
case WSASYSNOTREADY: return "Network system is unavailable";
case WSAVERNOTSUPPORTED: return "Winsock version out of range";
case WSANOTINITIALISED: return "WSAStartup not yet called";
case WSAEDISCON: return "Graceful shutdown in progress";
case WSAHOST_NOT_FOUND: return "Host not found";
case WSANO_DATA: return "No host data of that type was found";
}
return "unknown";
};
#endif // WIN32
// return true if socket has data waiting to be read
bool datawaiting( int sock )
{
fd_set fds;
FD_ZERO( &fds );
FD_SET( sock, &fds );
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 0;
int r = select( sock+1, &fds, NULL, NULL, &tv);
if (r < 0)
BailOnSocketError( "select" );
if( FD_ISSET( sock, &fds ) )
return true;
else
return false;
}
// Try to work out address from string
// returns 0 if bad
struct in_addr *atoaddr( const char* address)
{
struct hostent *host;
static struct in_addr saddr;
// First try nnn.nnn.nnn.nnn form
saddr.s_addr = inet_addr(address);
if (saddr.s_addr != -1)
return &saddr;
host = gethostbyname(address);
if( host )
return (struct in_addr *) *host->h_addr_list;
return 0;
}
//---------------------------------------------------------------------
//
// Exception class
//
//---------------------------------------------------------------------
Wobbly::Wobbly( const char* fmt, ... )
{
va_list ap;
va_start( ap,fmt);
int n = vsnprintf( m_Message, MAXLEN, fmt, ap );
va_end( ap );
if(n==MAXLEN)
m_Message[MAXLEN-1] = '\0';
}
//---------------------------------------------------------------------
//
// Connection
//
//---------------------------------------------------------------------
Connection::Connection( const char* host, int port ) :
m_ResponseBeginCB(0),
m_ResponseDataCB(0),
m_ResponseCompleteCB(0),
m_UserData(0),
m_State( IDLE ),
m_Host( host ),
m_Port( port ),
m_Sock(-1)
{
}
void Connection::setcallbacks(
ResponseBegin_CB begincb,
ResponseData_CB datacb,
ResponseComplete_CB completecb,
void* userdata )
{
m_ResponseBeginCB = begincb;
m_ResponseDataCB = datacb;
m_ResponseCompleteCB = completecb;
m_UserData = userdata;
}
void Connection::connect()
{
in_addr* addr = atoaddr( m_Host.c_str() );
if( !addr )
throw Wobbly( "Invalid network address" );
sockaddr_in address;
memset( (char*)&address, 0, sizeof(address) );
address.sin_family = AF_INET;
address.sin_port = htons( m_Port );
address.sin_addr.s_addr = addr->s_addr;
m_Sock = socket( AF_INET, SOCK_STREAM, 0 );
if( m_Sock < 0 )
BailOnSocketError( "socket()" );
// printf("Connecting to %s on port %d.\n",inet_ntoa(*addr), port);
if( ::connect( m_Sock, (sockaddr const*)&address, sizeof(address) ) < 0 )
BailOnSocketError( "connect()" );
}
void Connection::close()
{
#ifdef WIN32
if( m_Sock >= 0 )
::closesocket( m_Sock );
#else
if( m_Sock >= 0 )
::close( m_Sock );
#endif
m_Sock = -1;
// discard any incomplete responses
while( !m_Outstanding.empty() )
{
delete m_Outstanding.front();
m_Outstanding.pop_front();
}
}
Connection::~Connection()
{
close();
}
void Connection::request( const char* method,
const char* url,
const char* headers[],
const unsigned char* body,
int bodysize )
{
bool gotcontentlength = false; // already in headers?
// check headers for content-length
// TODO: check for "Host" and "Accept-Encoding" too
// and avoid adding them ourselves in putrequest()
if( headers )
{
const char** h = headers;
while( *h )
{
const char* name = *h++;
const char* value = *h++;
assert( value != 0 ); // name with no value!
if( 0==_stricmp( name, "content-length" ) )
gotcontentlength = true;
}
}
putrequest( method, url );
if( body && !gotcontentlength )
putheader( "Content-Length", bodysize );
if( headers )
{
const char** h = headers;
while( *h )
{
const char* name = *h++;
const char* value = *h++;
putheader( name, value );
}
}
endheaders();
if( body )
send( body, bodysize );
}
void Connection::putrequest( const char* method, const char* url )
{
if( m_State != IDLE )
throw Wobbly( "Request already issued" );
m_State = REQ_STARTED;
char req[ 512 ];
sprintf( req, "%s %s HTTP/1.1", method, url );
m_Buffer.push_back( req );
putheader( "Host", m_Host.c_str() ); // required for HTTP1.1
// don't want any fancy encodings please
putheader("Accept-Encoding", "identity");
// Push a new response onto the queue
Response *r = new Response( method, *this );
m_Outstanding.push_back( r );
}
void Connection::putheader( const char* header, const char* value )
{
if( m_State != REQ_STARTED )
throw Wobbly( "putheader() failed" );
m_Buffer.push_back( string(header) + ": " + string( value ) );
}
void Connection::putheader( const char* header, int numericvalue )
{
char buf[32];
sprintf( buf, "%d", numericvalue );
putheader( header, buf );
}
void Connection::endheaders()
{
if( m_State != REQ_STARTED )
throw Wobbly( "Cannot send header" );
m_State = IDLE;
m_Buffer.push_back( "" );
string msg;
vector< string>::const_iterator it;
for( it = m_Buffer.begin(); it != m_Buffer.end(); ++it )
msg += (*it) + "\r\n";
m_Buffer.clear();
// printf( "%s", msg.c_str() );
send( (const unsigned char*)msg.c_str(), msg.size() );
}
void Connection::send( const unsigned char* buf, int numbytes )
{
// fwrite( buf, 1,numbytes, stdout );
if( m_Sock < 0 )
connect();
while( numbytes > 0 )
{
#ifdef WIN32
int n = ::send( m_Sock, (const char*)buf, numbytes, 0 );
#else
int n = ::send( m_Sock, buf, numbytes, 0 );
#endif
if( n<0 )
BailOnSocketError( "send()" );
numbytes -= n;
buf += n;
}
}
void Connection::pump(int milisec)
{
if( m_Outstanding.empty() )
return; // no requests outstanding
assert( m_Sock >0 ); // outstanding requests but no connection!
if( !datawaiting( m_Sock ) )
return; // recv will block
unsigned char buf[ 2048 ];
int a = recv( m_Sock, (char*)buf, sizeof(buf), 0 );
if( a<0 )
BailOnSocketError( "recv()" );
if( a== 0 )
{
// connection has closed
Response* r = m_Outstanding.front();
r->notifyconnectionclosed();
assert( r->completed() );
delete r;
m_Outstanding.pop_front();
// any outstanding requests will be discarded
close();
}
else
{
int used = 0;
while( used < a && !m_Outstanding.empty() )
{
Response* r = m_Outstanding.front();
int u = r->pump( &buf[used], a-used );
// delete response once completed
if( r->completed() )
{
delete r;
m_Outstanding.pop_front();
}
used += u;
}
// NOTE: will lose bytes if response queue goes empty
// (but server shouldn't be sending anything if we don't have
// anything outstanding anyway)
assert( used == a ); // all bytes should be used up by here.
}
}
//---------------------------------------------------------------------
//
// Response
//
//---------------------------------------------------------------------
Response::Response( const char* method, Connection& conn ) :
m_Connection( conn ),
m_State( STATUSLINE ),
m_Method( method ),
m_Version( 0 ),
m_Status(0),
m_BytesRead(0),
m_Chunked(false),
m_ChunkLeft(0),
m_Length(-1),
m_WillClose(false)
{
}
const char* Response::getheader( const char* name ) const
{
std::string lname( name );
#ifdef _MSC_VER
std::transform( lname.begin(), lname.end(), lname.begin(), tolower );
#else
std::transform( lname.begin(), lname.end(), lname.begin(), ::tolower );
#endif
std::map< std::string, std::string >::const_iterator it = m_Headers.find( lname );
if( it == m_Headers.end() )
return 0;
else
return it->second.c_str();
}
int Response::getstatus() const
{
// only valid once we've got the statusline
assert( m_State != STATUSLINE );
return m_Status;
}
const char* Response::getreason() const
{
// only valid once we've got the statusline
assert( m_State != STATUSLINE );
return m_Reason.c_str();
}
// Connection has closed
void Response::notifyconnectionclosed()
{
if( m_State == COMPLETE )
return;
// eof can be valid...
if( m_State == BODY &&
!m_Chunked &&
m_Length == -1 )
{
Finish(); // we're all done!
}
else
{
throw Wobbly( "Connection closed unexpectedly" );
}
}
int Response::pump( const unsigned char* data, int datasize )
{
assert( datasize != 0 );
int count = datasize;
while( count > 0 && m_State != COMPLETE )
{
if( m_State == STATUSLINE ||
m_State == HEADERS ||
m_State == TRAILERS ||
m_State == CHUNKLEN ||
m_State == CHUNKEND )
{
// we want to accumulate a line
while( count > 0 )
{
char c = (char)*data++;
--count;
if( c == '\n' )
{
// now got a whole line!
switch( m_State )
{
case STATUSLINE:
ProcessStatusLine( m_LineBuf );
break;
case HEADERS:
ProcessHeaderLine( m_LineBuf );
break;
case TRAILERS:
ProcessTrailerLine( m_LineBuf );
break;
case CHUNKLEN:
ProcessChunkLenLine( m_LineBuf );
break;
case CHUNKEND:
// just soak up the crlf after body and go to next state
assert( m_Chunked == true );
m_State = CHUNKLEN;
break;
default:
break;
}
m_LineBuf.clear();
break; // break out of line accumulation!
}
else
{
if( c != '\r' ) // just ignore CR
m_LineBuf += c;
}
}
}
else if( m_State == BODY )
{
int bytesused = 0;
if( m_Chunked )
bytesused = ProcessDataChunked( data, count );
else
bytesused = ProcessDataNonChunked( data, count );
data += bytesused;
count -= bytesused;
}
}
// return number of bytes used
return datasize - count;
}
void Response::ProcessChunkLenLine( std::string const& line )
{
// chunklen in hex at beginning of line
m_ChunkLeft = strtol( line.c_str(), NULL, 16 );
if( m_ChunkLeft == 0 )
{
// got the whole body, now check for trailing headers
m_State = TRAILERS;
m_HeaderAccum.clear();
}
else
{
m_State = BODY;
}
}
// handle some body data in chunked mode
// returns number of bytes used.
int Response::ProcessDataChunked( const unsigned char* data, int count )
{
assert( m_Chunked );
int n = count;
if( n>m_ChunkLeft )
n = m_ChunkLeft;
// invoke callback to pass out the data
if( m_Connection.m_ResponseDataCB )
(m_Connection.m_ResponseDataCB)( this, m_Connection.m_UserData, data, n );
m_BytesRead += n;
m_ChunkLeft -= n;
assert( m_ChunkLeft >= 0);
if( m_ChunkLeft == 0 )
{
// chunk completed! now soak up the trailing CRLF before next chunk
m_State = CHUNKEND;
}
return n;
}
// handle some body data in non-chunked mode.
// returns number of bytes used.
int Response::ProcessDataNonChunked( const unsigned char* data, int count )
{
int n = count;
if( m_Length != -1 )
{
// we know how many bytes to expect
int remaining = m_Length - m_BytesRead;
if( n > remaining )
n = remaining;
}
// invoke callback to pass out the data
if( m_Connection.m_ResponseDataCB )
(m_Connection.m_ResponseDataCB)( this, m_Connection.m_UserData, data, n );
m_BytesRead += n;
// Finish if we know we're done. Else we're waiting for connection close.
if( m_Length != -1 && m_BytesRead == m_Length )
Finish();
return n;
}
void Response::Finish()
{
m_State = COMPLETE;
// invoke the callback
if( m_Connection.m_ResponseCompleteCB )
(m_Connection.m_ResponseCompleteCB)( this, m_Connection.m_UserData );
}
void Response::ProcessStatusLine( std::string const& line )
{
const char* p = line.c_str();
// skip any leading space
while( *p && *p == ' ' )
++p;
// get version
while( *p && *p != ' ' )
m_VersionString += *p++;
while( *p && *p == ' ' )
++p;
// get status code
std::string status;
while( *p && *p != ' ' )
status += *p++;
while( *p && *p == ' ' )
++p;
// rest of line is reason
while( *p )
m_Reason += *p++;
m_Status = atoi( status.c_str() );
if( m_Status < 100 || m_Status > 999 )
throw Wobbly( "BadStatusLine (%s)", line.c_str() );
/*
printf( "version: '%s'\n", m_VersionString.c_str() );
printf( "status: '%d'\n", m_Status );
printf( "reason: '%s'\n", m_Reason.c_str() );
*/
if( m_VersionString == "HTTP:/1.0" )
m_Version = 10;
else if( 0==m_VersionString.compare( 0,7,"HTTP/1." ) )
m_Version = 11;
else
throw Wobbly( "UnknownProtocol (%s)", m_VersionString.c_str() );
// TODO: support for HTTP/0.9
// OK, now we expect headers!
m_State = HEADERS;
m_HeaderAccum.clear();
}
// process accumulated header data
void Response::FlushHeader()
{
if( m_HeaderAccum.empty() )
return; // no flushing required
const char* p = m_HeaderAccum.c_str();
std::string header;
std::string value;
while( *p && *p != ':' )
header += tolower( *p++ );
// skip ':'
if( *p )
++p;
// skip space
while( *p && (*p ==' ' || *p=='\t') )
++p;
value = p; // rest of line is value
m_Headers[ header ] = value;
// printf("header: ['%s': '%s']\n", header.c_str(), value.c_str() );
m_HeaderAccum.clear();
}
void Response::ProcessHeaderLine( std::string const& line )
{
const char* p = line.c_str();
if( line.empty() )
{
FlushHeader();
// end of headers
// HTTP code 100 handling (we ignore 'em)
if( m_Status == CONTINUE )
m_State = STATUSLINE; // reset parsing, expect new status line
else
BeginBody(); // start on body now!
return;
}
if( isspace(*p) )
{
// it's a continuation line - just add it to previous data
++p;
while( *p && isspace( *p ) )
++p;
m_HeaderAccum += ' ';
m_HeaderAccum += p;
}
else
{
// begin a new header
FlushHeader();
m_HeaderAccum = p;
}
}
void Response::ProcessTrailerLine( std::string const& line )
{
// TODO: handle trailers?
// (python httplib doesn't seem to!)
if( line.empty() )
Finish();
// just ignore all the trailers...
}
// OK, we've now got all the headers read in, so we're ready to start
// on the body. But we need to see what info we can glean from the headers
// first...
void Response::BeginBody()
{
m_Chunked = false;
m_Length = -1; // unknown
m_WillClose = false;
// using chunked encoding?
const char* trenc = getheader( "transfer-encoding" );
if( trenc && 0==_stricmp( trenc, "chunked") )
{
m_Chunked = true;
m_ChunkLeft = -1; // unknown
}
m_WillClose = CheckClose();
// length supplied?
const char* contentlen = getheader( "content-length" );
if( contentlen && !m_Chunked )
{
m_Length = atoi( contentlen );
}
// check for various cases where we expect zero-length body
if( m_Status == NO_CONTENT ||
m_Status == NOT_MODIFIED ||
( m_Status >= 100 && m_Status < 200 ) || // 1xx codes have no body
m_Method == "HEAD" )
{
m_Length = 0;
}
// if we're not using chunked mode, and no length has been specified,
// assume connection will close at end.
if( !m_WillClose && !m_Chunked && m_Length == -1 )
m_WillClose = true;
// Invoke the user callback, if any
if( m_Connection.m_ResponseBeginCB )
(m_Connection.m_ResponseBeginCB)( this, m_Connection.m_UserData );
/*
printf("---------BeginBody()--------\n");
printf("Length: %d\n", m_Length );
printf("WillClose: %d\n", (int)m_WillClose );
printf("Chunked: %d\n", (int)m_Chunked );
printf("ChunkLeft: %d\n", (int)m_ChunkLeft );
printf("----------------------------\n");
*/
// now start reading body data!
if( m_Chunked )
m_State = CHUNKLEN;
else
m_State = BODY;
}
// return true if we think server will automatically close connection
bool Response::CheckClose()
{
if( m_Version == 11 )
{
// HTTP1.1
// the connection stays open unless "connection: close" is specified.
const char* conn = getheader( "connection" );
if( conn && 0==_stricmp( conn, "close" ) )
return true;
else
return false;
}
// Older HTTP
// keep-alive header indicates persistant connection
if( getheader( "keep-alive" ) )
return false;
// TODO: some special case handling for Akamai and netscape maybe?
// (see _check_close() in python httplib.py for details)
return true;
}
} // end namespace happyhttp

333
external/happyhttp/happyhttp/happyhttp.h vendored Normal file
View File

@ -0,0 +1,333 @@
/*
* HappyHTTP - a simple HTTP library
* Version 0.1
*
* Copyright (c) 2006 Ben Campbell
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software in a
* product, an acknowledgment in the product documentation would be
* appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source distribution.
*
*/
#ifndef HAPPYHTTP_H
#define HAPPYHTTP_H
#include <string>
#include <map>
#include <vector>
#include <deque>
// forward decl
struct in_addr;
namespace happyhttp
{
class Response;
// Helper Functions
void BailOnSocketError( const char* context );
struct in_addr *atoaddr( const char* address);
typedef void (*ResponseBegin_CB)( const Response* r, void* userdata );
typedef void (*ResponseData_CB)( const Response* r, void* userdata, const unsigned char* data, int numbytes );
typedef void (*ResponseComplete_CB)( const Response* r, void* userdata );
// HTTP status codes
enum {
// 1xx informational
CONTINUE = 100,
SWITCHING_PROTOCOLS = 101,
PROCESSING = 102,
// 2xx successful
OK = 200,
CREATED = 201,
ACCEPTED = 202,
NON_AUTHORITATIVE_INFORMATION = 203,
NO_CONTENT = 204,
RESET_CONTENT = 205,
PARTIAL_CONTENT = 206,
MULTI_STATUS = 207,
IM_USED = 226,
// 3xx redirection
MULTIPLE_CHOICES = 300,
MOVED_PERMANENTLY = 301,
FOUND = 302,
SEE_OTHER = 303,
NOT_MODIFIED = 304,
USE_PROXY = 305,
TEMPORARY_REDIRECT = 307,
// 4xx client error
BAD_REQUEST = 400,
UNAUTHORIZED = 401,
PAYMENT_REQUIRED = 402,
FORBIDDEN = 403,
NOT_FOUND = 404,
METHOD_NOT_ALLOWED = 405,
NOT_ACCEPTABLE = 406,
PROXY_AUTHENTICATION_REQUIRED = 407,
REQUEST_TIMEOUT = 408,
CONFLICT = 409,
GONE = 410,
LENGTH_REQUIRED = 411,
PRECONDITION_FAILED = 412,
REQUEST_ENTITY_TOO_LARGE = 413,
REQUEST_URI_TOO_LONG = 414,
UNSUPPORTED_MEDIA_TYPE = 415,
REQUESTED_RANGE_NOT_SATISFIABLE = 416,
EXPECTATION_FAILED = 417,
UNPROCESSABLE_ENTITY = 422,
LOCKED = 423,
FAILED_DEPENDENCY = 424,
UPGRADE_REQUIRED = 426,
// 5xx server error
INTERNAL_SERVER_ERROR = 500,
NOT_IMPLEMENTED = 501,
BAD_GATEWAY = 502,
SERVICE_UNAVAILABLE = 503,
GATEWAY_TIMEOUT = 504,
HTTP_VERSION_NOT_SUPPORTED = 505,
INSUFFICIENT_STORAGE = 507,
NOT_EXTENDED = 510,
};
// Exception class
class Wobbly
{
public:
Wobbly( const char* fmt, ... );
const char* what() const
{ return m_Message; }
protected:
enum { MAXLEN=256 };
char m_Message[ MAXLEN ];
};
//-------------------------------------------------
// Connection
//
// Handles the socket connection, issuing of requests and managing
// responses.
// ------------------------------------------------
class Connection
{
friend class Response;
public:
// doesn't connect immediately
Connection( const char* host, int port );
~Connection();
// Set up the response handling callbacks. These will be invoked during
// calls to pump().
// begincb - called when the responses headers have been received
// datacb - called repeatedly to handle body data
// completecb - response is completed
// userdata is passed as a param to all callbacks.
void setcallbacks(
ResponseBegin_CB begincb,
ResponseData_CB datacb,
ResponseComplete_CB completecb,
void* userdata );
// Don't need to call connect() explicitly as issuing a request will
// call it automatically if needed.
// But it could block (for name lookup etc), so you might prefer to
// call it in advance.
void connect();
// close connection, discarding any pending requests.
void close();
// Update the connection (non-blocking)
// Just keep calling this regularly to service outstanding requests.
void pump(int milisec=10); //10 miliseconds to prevent high cpu load
// any requests still outstanding?
bool outstanding() const
{ return !m_Outstanding.empty(); }
// ---------------------------
// high-level request interface
// ---------------------------
// method is "GET", "POST" etc...
// url is only path part: eg "/index.html"
// headers is array of name/value pairs, terminated by a null-ptr
// body & bodysize specify body data of request (eg values for a form)
void request( const char* method, const char* url, const char* headers[]=0,
const unsigned char* body=0, int bodysize=0 );
// ---------------------------
// low-level request interface
// ---------------------------
// begin request
// method is "GET", "POST" etc...
// url is only path part: eg "/index.html"
void putrequest( const char* method, const char* url );
// Add a header to the request (call after putrequest() )
void putheader( const char* header, const char* value );
void putheader( const char* header, int numericvalue ); // alternate version
// Finished adding headers, issue the request.
void endheaders();
// send body data if any.
// To be called after endheaders()
void send( const unsigned char* buf, int numbytes );
protected:
// some bits of implementation exposed to Response class
// callbacks
ResponseBegin_CB m_ResponseBeginCB;
ResponseData_CB m_ResponseDataCB;
ResponseComplete_CB m_ResponseCompleteCB;
void* m_UserData;
private:
enum { IDLE, REQ_STARTED, REQ_SENT } m_State;
std::string m_Host;
int m_Port;
int m_Sock;
std::vector< std::string > m_Buffer; // lines of request
std::deque< Response* > m_Outstanding; // responses for outstanding requests
};
//-------------------------------------------------
// Response
//
// Handles parsing of response data.
// ------------------------------------------------
class Response
{
friend class Connection;
public:
// retrieve a header (returns 0 if not present)
const char* getheader( const char* name ) const;
bool completed() const
{ return m_State == COMPLETE; }
// get the HTTP status code
int getstatus() const;
// get the HTTP response reason string
const char* getreason() const;
// true if connection is expected to close after this response.
bool willclose() const
{ return m_WillClose; }
protected:
// interface used by Connection
// only Connection creates Responses.
Response( const char* method, Connection& conn );
// pump some data in for processing.
// Returns the number of bytes used.
// Will always return 0 when response is complete.
int pump( const unsigned char* data, int datasize );
// tell response that connection has closed
void notifyconnectionclosed();
private:
enum {
STATUSLINE, // start here. status line is first line of response.
HEADERS, // reading in header lines
BODY, // waiting for some body data (all or a chunk)
CHUNKLEN, // expecting a chunk length indicator (in hex)
CHUNKEND, // got the chunk, now expecting a trailing blank line
TRAILERS, // reading trailers after body.
COMPLETE, // response is complete!
} m_State;
Connection& m_Connection; // to access callback ptrs
std::string m_Method; // req method: "GET", "POST" etc...
// status line
std::string m_VersionString; // HTTP-Version
int m_Version; // 10: HTTP/1.0 11: HTTP/1.x (where x>=1)
int m_Status; // Status-Code
std::string m_Reason; // Reason-Phrase
// header/value pairs
std::map<std::string,std::string> m_Headers;
int m_BytesRead; // body bytes read so far
bool m_Chunked; // response is chunked?
int m_ChunkLeft; // bytes left in current chunk
int m_Length; // -1 if unknown
bool m_WillClose; // connection will close at response end?
std::string m_LineBuf; // line accumulation for states that want it
std::string m_HeaderAccum; // accumulation buffer for headers
void FlushHeader();
void ProcessStatusLine( std::string const& line );
void ProcessHeaderLine( std::string const& line );
void ProcessTrailerLine( std::string const& line );
void ProcessChunkLenLine( std::string const& line );
int ProcessDataChunked( const unsigned char* data, int count );
int ProcessDataNonChunked( const unsigned char* data, int count );
void BeginBody();
bool CheckClose();
void Finish();
};
} // end namespace happyhttp
#endif // HAPPYHTTP_H

11
plugins/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.14...3.24 FATAL_ERROR)
add_library(plugin_sdbot-webui SHARED
./plugin_sdbot-webui.cpp
)
target_link_libraries(plugin_sdbot-webui PUBLIC
solanaceae_plugin
solanaceae_sdbot-webui
)

View File

@ -0,0 +1,78 @@
#include <solanaceae/plugin/solana_plugin_v1.h>
#include "../src/sd_bot.hpp"
#include <memory>
#include <iostream>
#define RESOLVE_INSTANCE(x) static_cast<x*>(solana_api->resolveInstance(#x))
#define PROVIDE_INSTANCE(x, p, v) solana_api->provideInstance(#x, p, static_cast<x*>(v))
static std::unique_ptr<SDBot> g_sdbot = nullptr;
extern "C" {
SOLANA_PLUGIN_EXPORT const char* solana_plugin_get_name(void) {
return "SDBot-webui";
}
SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_get_version(void) {
return SOLANA_PLUGIN_VERSION;
}
SOLANA_PLUGIN_EXPORT uint32_t solana_plugin_start(struct SolanaAPI* solana_api) {
std::cout << "PLUGIN SDB START()\n";
if (solana_api == nullptr) {
return 1;
}
Contact3Registry* cr;
RegistryMessageModel* rmm = nullptr;
ConfigModelI* conf = nullptr;
{ // make sure required types are loaded
cr = RESOLVE_INSTANCE(Contact3Registry);
rmm = RESOLVE_INSTANCE(RegistryMessageModel);
conf = RESOLVE_INSTANCE(ConfigModelI);
if (cr == nullptr) {
std::cerr << "PLUGIN SDB missing Contact3Registry\n";
return 2;
}
if (rmm == nullptr) {
std::cerr << "PLUGIN SDB missing RegistryMessageModel\n";
return 2;
}
if (conf == nullptr) {
std::cerr << "PLUGIN SDB missing ConfigModelI\n";
return 2;
}
}
// static store, could be anywhere tho
// construct with fetched dependencies
g_sdbot = std::make_unique<SDBot>(*cr, *rmm, *conf);
// register types
PROVIDE_INSTANCE(SDBot, "SDBot", g_sdbot.get());
return 0;
}
SOLANA_PLUGIN_EXPORT void solana_plugin_stop(void) {
std::cout << "PLUGIN SDB STOP()\n";
g_sdbot.reset();
}
SOLANA_PLUGIN_EXPORT void solana_plugin_tick(float delta) {
(void)delta;
//std::cout << "PLUGIN SDB TICK()\n";
g_sdbot->iterate();
}
} // extern C

16
src/CMakeLists.txt Normal file
View File

@ -0,0 +1,16 @@
cmake_minimum_required(VERSION 3.9 FATAL_ERROR)
add_library(solanaceae_sdbot-webui STATIC
./sd_bot.hpp
./sd_bot.cpp
)
target_compile_features(solanaceae_sdbot-webui PUBLIC cxx_std_17)
target_link_libraries(solanaceae_sdbot-webui PUBLIC
happyhttp
solanaceae_contact
solanaceae_message3
nlohmann_json::nlohmann_json
libsodium
)

224
src/sd_bot.cpp Normal file
View File

@ -0,0 +1,224 @@
#include "./sd_bot.hpp"
#include <solanaceae/contact/components.hpp>
#include <solanaceae/message3/components.hpp>
#include <nlohmann/json.hpp>
#include <sodium.h>
#include <vector>
#include <fstream>
#include <filesystem>
#include <cstdint>
#include <iostream>
SDBot::SDBot(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ConfigModelI& conf
) : _cr(cr), _rmm(rmm), _conf(conf) {
_rmm.subscribe(this, RegistryMessageModel_Event::message_construct);
}
SDBot::~SDBot(void) {
}
void SDBot::iterate(void) {
if (static_cast<bool>(_con) && _con->outstanding()) {
_con->pump();
} else if (!_prompt_queue.empty()) { // dequeue new task
const auto& [task_id, prompt] = _prompt_queue.front();
_current_task = task_id;
// TODO: reuse connection?
// TODO: read from config
_con = std::make_unique<happyhttp::Connection>("127.0.0.1", 7860);
_con->setcallbacks(
+[](const happyhttp::Response* r, void* ud) { static_cast<SDBot*>(ud)->onHttpBegin(r); },
+[](const happyhttp::Response* r, void* ud, const uint8_t* data, int n) { static_cast<SDBot*>(ud)->onHttpData(r, data, n); },
+[](const happyhttp::Response* r, void* ud) { static_cast<SDBot*>(ud)->onHttpComplete(r); },
this
);
static const char* headers [] {
"accept: application/json",
"Content-Type: application/json",
nullptr,
};
nlohmann::json j_body;
// TODO: read from config
#if 1
j_body["width"] = 512;
j_body["height"] = 512;
#elif 0
j_body["width"] = 768;
j_body["height"] = 768;
#else
j_body["width"] = 128;
j_body["height"] = 128;
#endif
j_body["prompt"] = prompt;
j_body["seed"] = -1;
j_body["steps"] = 20;
//j_body["steps"] = 5;
j_body["cfg_scale"] = 6.5;
j_body["sampler_index"] = "Euler a";
j_body["batch_size"] = 1;
j_body["n_iter"] = 1;
j_body["restore_faces"] = false;
j_body["tiling"] = false;
j_body["enable_hr"] = false;
std::string body = j_body.dump();
try {
_con->request("POST", "/sdapi/v1/txt2img", headers, reinterpret_cast<const uint8_t*>(body.data()), body.size());
} catch (const happyhttp::Wobbly& e) {
std::cerr << "SDB http request error: " << e.what() << "\n";
// cleanup
_task_map.erase(_current_task.value());
_current_task = std::nullopt;
_con.reset();
}
_prompt_queue.pop();
}
}
bool SDBot::onEvent(const Message::Events::MessageConstruct& e) {
if (!e.e.all_of<Message::Components::ContactTo, Message::Components::ContactFrom, Message::Components::MessageText, Message::Components::TagUnread>()) {
std::cout << "SDB: got message that is not";
if (!e.e.all_of<Message::Components::ContactTo>()) {
std::cout << " contact_to";
}
if (!e.e.all_of<Message::Components::ContactFrom>()) {
std::cout << " contact_from";
}
if (!e.e.all_of<Message::Components::MessageText>()) {
std::cout << " text";
}
if (!e.e.all_of<Message::Components::TagUnread>()) {
std::cout << " unread";
}
std::cout << "\n";
return false;
}
if (e.e.any_of<Message::Components::TagMessageIsAction>()) {
std::cout << "SDB: got message that is";
if (e.e.all_of<Message::Components::TagMessageIsAction>()) {
std::cout << " action";
}
std::cout << "\n";
return false;
}
std::string_view message_text = e.e.get<Message::Components::MessageText>().text;
if (message_text.empty()) {
// empty message?
return false;
}
const auto contact_to = e.e.get<Message::Components::ContactTo>().c;
const auto contact_from = e.e.get<Message::Components::ContactFrom>().c;
const bool is_private = _cr.any_of<Contact::Components::TagSelfWeak, Contact::Components::TagSelfStrong>(contact_to);
if (is_private) {
std::cout << "SDB private message " << message_text << " (l:" << message_text.size() << ")\n";
{ // queue task
const auto id = ++_last_task_counter;
_task_map[id] = contact_from; // reply privately
_prompt_queue.push(std::make_pair(uint64_t{id}, std::string{message_text}));
}
} else {
assert(_cr.all_of<Contact::Components::Self>(contact_to));
const auto contact_self = _cr.get<Contact::Components::Self>(contact_to).self;
if (!_cr.all_of<Contact::Components::Name>(contact_self)) {
std::cerr << "SDB error: dont have self name\n";
return false;
}
const auto& self_name = _cr.get<Contact::Components::Name>(contact_self).name;
const auto self_prefix = self_name + ": ";
// check if for us. (starts with <botname>: )
if (message_text.substr(0, self_prefix.size()) == self_prefix) {
std::cout << "SDB public message " << message_text << " (l:" << message_text.size() << ")\n";
const auto id = ++_last_task_counter;
_task_map[id] = contact_to; // reply publicly
_prompt_queue.push(std::make_pair(uint64_t{id}, std::string{message_text.substr(self_prefix.size())}));
}
}
// TODO: mark message read?
return true;
}
void SDBot::onHttpBegin(const happyhttp::Response* r) {
std::cout << "SDB http begin " << r->getstatus() << " " << r->getreason() << "\n";
// TODO: handle errors
_con_data.clear();
}
void SDBot::onHttpData(const happyhttp::Response* /*r*/, const unsigned char* data, int n) {
//std::cout << "SDB http data\n";
// TODO: handle errors
for (int i = 0; i < n; i++) {
_con_data.push_back(data[i]);
}
}
void SDBot::onHttpComplete(const happyhttp::Response* r) {
std::cout << "SDB http complete " << r->getstatus() << " " << r->getreason() << "\n";
if (r->getstatus() == happyhttp::OK) {
std::cout << "SDB data\n";
//std::cout << std::string_view{reinterpret_cast<const char*>(_con_data.data()), _con_data.size()} << "\n";
// extract json result
const auto j = nlohmann::json::parse(std::string_view{reinterpret_cast<const char*>(_con_data.data()), _con_data.size()});
if (j.count("images") && !j.at("images").empty() && j.at("images").is_array()) {
for (const auto& i_j : j.at("images").items()) {
// decode data (base64)
std::vector<uint8_t> png_data(_con_data.size()); // just init to upper bound
size_t decoded_size {0};
sodium_base642bin(
png_data.data(), png_data.size(),
i_j.value().get<std::string>().data(), i_j.value().get<std::string>().size(),
" \n\t",
&decoded_size,
nullptr,
sodium_base64_VARIANT_ORIGINAL
);
png_data.resize(decoded_size);
// hand png to download manager
const auto& contact = _task_map.at(_current_task.value());
std::filesystem::create_directories("sdbot_img_send");
const std::string tmp_img_file_name = "sdbot_img_" + std::to_string(_current_task.value()) + ".png";
const std::string tmp_img_file_path = "sdbot_img_send/" + tmp_img_file_name;
std::ofstream(tmp_img_file_path).write(reinterpret_cast<const char*>(png_data.data()), png_data.size());
_rmm.sendFilePath(contact, tmp_img_file_name, tmp_img_file_path);
}
} else {
std::cerr << "SDB json response did not contain images?\n";
}
_task_map.erase(_current_task.value());
_current_task = std::nullopt;
_con.reset();
}
}

58
src/sd_bot.hpp Normal file
View File

@ -0,0 +1,58 @@
#pragma once
#include <solanaceae/message3/registry_message_model.hpp>
#include <solanaceae/contact/contact_model3.hpp>
#include <happyhttp/happyhttp.h>
#include <map>
#include <queue>
#include <string>
#include <memory>
#include <optional>
// fwd
struct ConfigModelI;
class SDBot : public RegistryMessageModelEventI {
Contact3Registry& _cr;
RegistryMessageModel& _rmm;
ConfigModelI& _conf;
//TransferManager& _tm;
//std::map<uint64_t, std::variant<ContactFriend, ContactConference, ContactGroupPeer>> _task_map;
std::map<uint64_t, Contact3> _task_map;
std::queue<std::pair<uint64_t, std::string>> _prompt_queue;
uint64_t _last_task_counter = 0;
std::optional<uint64_t> _current_task;
std::unique_ptr<happyhttp::Connection> _con;
std::vector<uint8_t> _con_data;
public:
SDBot(
Contact3Registry& cr,
RegistryMessageModel& rmm,
ConfigModelI& conf
);
~SDBot(void);
void iterate(void);
public: // conf
bool use_webp_for_friends = true;
bool use_webp_for_groups = true;
//protected: // tox events
//bool onToxEvent(const Tox_Event_Friend_Message* e) override;
//bool onToxEvent(const Tox_Event_Group_Message* e) override;
protected: // mm
bool onEvent(const Message::Events::MessageConstruct& e) override;
public: // http cb
void onHttpBegin(const happyhttp::Response* r);
void onHttpData(const happyhttp::Response* r, const unsigned char* data, int n);
void onHttpComplete(const happyhttp::Response* r);
};