diff --git a/.gitignore b/.gitignore index 743cd06..7586c35 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ __history/ backup/ bin/ -lib/ \ No newline at end of file +lib/ +piper/ diff --git a/Player.dpr b/Player.dpr new file mode 100644 index 0000000..9d93d64 --- /dev/null +++ b/Player.dpr @@ -0,0 +1,23 @@ +program Player; +{$APPTYPE GUI} + +uses + System.StartUpCopy, + FMX.Forms, + Web.WebReq, + IdHTTPWebBrokerBridge, + uPlayer in 'uPlayer.pas' {fPlayer}, + uOBS_Doc_Player in 'uOBS_Doc_Player.pas' {OBS_Doc_Player: TDataModule}, + uPlayerThread in 'uPlayerThread.pas', + uPlayerWeb in 'uPlayerWeb.pas' {frPlayerWeb: TFrame}; + +{$R *.res} + +begin + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := OBS_Doc_Player; + Application.Initialize; + Application.CreateForm(TfPlayer, fPlayer); + Application.Run; + +end. diff --git a/Player.dproj b/Player.dproj new file mode 100644 index 0000000..72715c7 --- /dev/null +++ b/Player.dproj @@ -0,0 +1,1319 @@ +п»ї + + {386B0AB8-06BA-4E2F-8B56-1307143E8EDF} + 20.3 + FMX + True + Debug + Win32 + 3 + Application + Player.dpr + Player + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + Player + true + true + true + true + true + true + true + true + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35 + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35 + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPad\FM_NotificationIcon_40x40.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPad\FM_NotificationIcon_40x40.png + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface + Debug + true + + + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface + Debug + true + + + vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;Skia.Package.RTL;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;WebView4DelphiVCLRTL;FireDACASADriver;JclVcl;soapmidas;vclactnband;fmxFireDAC;dbexpress;Python;Jcl;FireDACInfxDriver;CEF4DelphiVCLRTL;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;PipesRunTime;PythonVcl;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;Skia.Package.FMX;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;CEF4DelphiFMXRTL;JclDeveloperTools;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;OverbyteIcsD104Run;OpenAIPackage;JclContainers;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;PythonFmx;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + vclwinx;DataSnapServer;fmx;emshosting;vclie;DbxCommonDriver;bindengine;IndyIPCommon;VCLRESTComponents;DBXMSSQLDriver;FireDACCommonODBC;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;IndyIPClient;dbxcds;vcledge;bindcompvclwinx;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;FireDACSqliteDriver;DbxClientDriver;WebView4DelphiVCLRTL;FireDACASADriver;JclVcl;soapmidas;vclactnband;fmxFireDAC;dbexpress;Python;Jcl;FireDACInfxDriver;CEF4DelphiVCLRTL;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;PythonVcl;vcltouch;fmxase;DBXOdbcDriver;dbrtl;FireDACDBXDriver;FireDACOracleDriver;fmxdae;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;CEF4DelphiFMXRTL;JclDeveloperTools;DataSnapConnectors;vcldsnap;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;OverbyteIcsD104Run;JclContainers;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;IndyIPServer;dsnapxml;dsnapcon;DataSnapClient;DataSnapProviderClient;adortl;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;bindcompvcl;dsnap;CloudService;DBXSybaseASADriver;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;fmxobj;bindcompvclsmp;DataSnapNativeClient;PythonFmx;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + PerMonitorV2 + + + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + true + 1049 + CompanyName=PTyTb;FileDescription=$(MSBuildProjectName);FileVersion=1.1.2.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=ru.ptytb.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + Player_Icon.ico + p44.png + p150.png + 1 + 2 + + + PerMonitorV2 + + + + MainSource + + +
fPlayer
+ fmx +
+ +
OBS_Doc_Player
+ dfm + TDataModule +
+ + +
frPlayerWeb
+ fmx + TFrame +
+ + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Application + + + + Player.dpr + + + Microsoft Office 2000 Sample Automation Server Wrapper Components + Microsoft Office XP Sample Automation Server Wrapper Components + + + + + + true + + + + + true + + + + + true + + + + + + Player.exe + true + + + + + + Player.exe + true + + + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + False + False + False + False + False + False + False + True + True + + + 12 + + + + +
diff --git a/Player_Icon.ico b/Player_Icon.ico new file mode 100644 index 0000000..bba255a Binary files /dev/null and b/Player_Icon.ico differ diff --git a/ProjectGroup1.groupproj b/ProjectGroup1.groupproj new file mode 100644 index 0000000..608305a --- /dev/null +++ b/ProjectGroup1.groupproj @@ -0,0 +1,48 @@ +п»ї + + {4668C1BF-7804-4469-B989-F2A5607035A1} + + + + + + + + + + + Default.Personality.12 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ProjectGroup1.groupproj.local b/ProjectGroup1.groupproj.local new file mode 100644 index 0000000..aff781c --- /dev/null +++ b/ProjectGroup1.groupproj.local @@ -0,0 +1,9 @@ +п»ї + + + 2025.08.10 08:44:51.964,C:\Users\PTyTb\Documents\Embarcadero\Studio\Projects\ttw_fmx_v10\ProjectGroup1.groupproj=C:\Users\PTyTb\Documents\Embarcadero\Studio\Projects\ProjectGroup1.groupproj + + + + + diff --git a/uAPIDA.pas b/Services/uAPIDA.pas similarity index 100% rename from uAPIDA.pas rename to Services/uAPIDA.pas diff --git a/Services/uChatAPI.pas b/Services/uChatAPI.pas new file mode 100644 index 0000000..be6761d --- /dev/null +++ b/Services/uChatAPI.pas @@ -0,0 +1,195 @@ +unit uChatAPI; + +interface + +uses + Classes, SysUtils, IdHTTP, System.JSON, IdSSLOpenSSL, IdGlobal; + +type + TMessage = procedure(s: string) of object; + +type + TChatAPI = class(TObject) + protected + FToken_api: string; + FPrefix: string; + FOnError: TMessage; + function GetOtvetFromJson(jsonString: string; isOllama: boolean = false) + : string; virtual; + function CreateHTTPRequest(const url: string; const params: TStringStream; + isOllama: boolean = false): string; + + public + constructor Create(Sender: TObject; aToken: string; + aprefix: string = ''); virtual; + destructor Destroy; override; + function GetGPTRequest(url: string; model: string; q: string; + isOllama: boolean = false): string; + property OnError: TMessage read FOnError write FOnError; + end; + +implementation + +{ TChatAPI } + +constructor TChatAPI.Create(Sender: TObject; aToken: string; + aprefix: string = ''); +begin + FPrefix := aprefix; + FToken_api := aToken; +end; + +function TChatAPI.CreateHTTPRequest(const url: string; + const params: TStringStream; isOllama: boolean = false): string; +var + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; + otv: string; +begin + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.Clear; + // http.Request.CustomHeaders.Add('Content-Type: application/json; charset=utf-8'); + http.Request.ContentType := 'application/json; charset=utf-8'; + + if FToken_api <> '' then + http.Request.CustomHeaders.Add('Authorization: Bearer ' + FToken_api); + http.Request.Accept := 'application/json; charset=utf-8'; + http.Request.CharSet := 'utf-8'; + http.Response.CharSet := 'utf-8'; + // http.Request.CustomHeaders.Add('Accept: application/json; charset=utf-8'); + http.Response.ContentEncoding := 'utf-8'; + http.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8; + http.Request.ContentEncoding := 'utf-8'; + + try + otv := http.Post(url, params); + Result := GetOtvetFromJson(otv, isOllama); + except + on E: Exception do + if Assigned(OnError) then + OnError(E.Message); + end; + finally + params.Free; + http.Free; + ssl.Free; + end; +end; + +destructor TChatAPI.Destroy; +begin + inherited; +end; + +function ReplaceDelphiHexCodes(const InputStr: string): string; +var + I, Start, HexVal: Integer; + HexStr: string; +begin + Result := ''; + I := 1; + while I <= Length(InputStr) do + begin + if (I <= Length(InputStr) - 5) and (InputStr[I] = '#') and + (InputStr[I + 1] = '$') then + begin + HexStr := Copy(InputStr, I + 2, 4); + if TryStrToInt('$' + HexStr, HexVal) then + begin + Result := Result + WideChar(HexVal); + Inc(I, 6); // Пропускаем #$XXXX + Continue; + end; + end; + Result := Result + InputStr[I]; + Inc(I); + end; +end; + +function ConvertAnsiToUtf8(const AStr: string): string; +var + AnsiBytes: TBytes; +begin + AnsiBytes := TEncoding.ANSI.GetBytes(AStr); + Result := TEncoding.UTF8.GetString(AnsiBytes); +end; + +function TChatAPI.GetOtvetFromJson(jsonString: string; + isOllama: boolean = false): string; +var + JSON: TJSONObject; + dataArray: TJSONArray; + JSONValue: TJSONValue; + JsonParts: TStringList; + I: Integer; + CleanedJson: string; + JsonObj: TJSONObject; + ResponseStr, FullResponse: string; +begin + + Result := 'Произошла какая то ошибка, попробуйте спрашивать по очереди!'; + if isOllama then + begin + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + JSONValue := TJSONObject(JSON); + if JSONValue.TryGetValue('response', JSONValue) then + Result := JSONValue.Value; + end; + finally + JSON.Free; + end; + end + else + begin + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + if JSON.TryGetValue('messages', JSONValue) then + begin + dataArray := JSONValue as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + Result := dataArray.Items[0].GetValue('content'); + end; + end; + finally + JSON.Free; + end; + end; +end; + +function TChatAPI.GetGPTRequest(url: string; model: string; q: string; + isOllama: boolean = false): string; +var + params: TStringStream; + r: string; +begin + + q := StringReplace(q, '"', '''', [rfReplaceAll]); + if isOllama then + params := TStringStream.Create('{ "model": "' + model + '", "prompt": "' + + FPrefix + q + '", "stream": false }', TEncoding.UTF8) + else + params := TStringStream.Create('{ "model": "' + model + + '", "messages": [{ "role": "user", "content": "' + FPrefix + q + + '" }], "stream": false }', CP_UTF8); + + try + r := CreateHTTPRequest(url, params, isOllama); + finally + // params.Free; + end; + Result := r; + +end; + +end. diff --git a/Services/uCustomEmoties.pas b/Services/uCustomEmoties.pas new file mode 100644 index 0000000..d3079a0 --- /dev/null +++ b/Services/uCustomEmoties.pas @@ -0,0 +1,410 @@ +unit uCustomEmoties; + +interface + +uses + Classes, System.Generics.Collections, System.JSON, uRecords, IdHTTP, IdSSLOpenSSL, + System.Net.HttpClient, System.SysUtils; + + type + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; + + +type + TBTTV = class(TObject) + private + list: TList; + FOnLog: TOnLog; + procedure AddEmotesGlobalJson(const JsonStr: string); + procedure AddEmotesUserJson(const JsonStr: string); + function GetHTTP(aMethod: string): string; + procedure toLog(alevel: integer; amethod: string; amessage: string); + public + constructor Create; + destructor Destroy; override; + procedure GetGlobal; + procedure GetCustom(uid: string); + function GenerateURL(emoteName: string): string; + property OnLog: TOnLog read FOnLog write FOnLog; + end; + +type + T7TV = class(TObject) + private + list7: TList; + FOnLog: TOnLog; + procedure AddEmotesGlobalJson(const JsonStr: string); + procedure AddEmotesUserJson(const JsonStr: string); + function GetHTTP(aMethod: string): string; + public + constructor Create; + destructor Destroy; override; + procedure GetGlobal; + procedure GetCustom(uid: string); + function GenerateURL(emoteName: string): string; + procedure toLog(alevel: integer; amethod: string; amessage: string); + property OnLog: TOnLog read FOnLog write FOnLog; + end; + +implementation + +{ TBTTV } + +constructor TBTTV.Create; +begin + inherited; + list := TList.Create; +end; + +destructor TBTTV.Destroy; +begin + FreeAndNil(list); + inherited; +end; + +procedure TBTTV.AddEmotesGlobalJson(const JsonStr: string); +var + JSONValue: TJSONValue; + JSONArray: TJSONArray; + EmoteObj: TJSONObject; + NewEmote: TBTTVr; + i: Integer; +begin + JSONValue := TJSONObject.ParseJSONValue(JsonStr); + if not Assigned(JSONValue) then Exit; + + try + if not (JSONValue is TJSONArray) then Exit; + JSONArray := TJSONArray(JSONValue); + + for i := 0 to JSONArray.Count - 1 do + begin + if not (JSONArray.Items[i] is TJSONObject) then Continue; + + EmoteObj := TJSONObject(JSONArray.Items[i]); + NewEmote := Default(TBTTVr); + + if Assigned(EmoteObj.GetValue('id')) then + NewEmote.id := EmoteObj.GetValue('id').Value; + if Assigned(EmoteObj.GetValue('code')) then + NewEmote.code := EmoteObj.GetValue('code').Value; + + if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then + list.Add(NewEmote); + end; + finally + JSONValue.Free; + end; +end; + +procedure TBTTV.AddEmotesUserJson(const JsonStr: string); +var + JSONValue, ChannelEmotes: TJSONValue; + JSONArray: TJSONArray; + EmoteObj: TJSONObject; + NewEmote: TBTTVr; + i: Integer; +begin + JSONValue := TJSONObject.ParseJSONValue(JsonStr); + if not Assigned(JSONValue) then Exit; + + try + // Обработка channelEmotes + ChannelEmotes := TJSONObject(JSONValue).GetValue('channelEmotes'); + if (ChannelEmotes is TJSONArray) then + begin + JSONArray := TJSONArray(ChannelEmotes); + for i := 0 to JSONArray.Count - 1 do + begin + if not (JSONArray.Items[i] is TJSONObject) then Continue; + + EmoteObj := TJSONObject(JSONArray.Items[i]); + NewEmote := Default(TBTTVr); + + if Assigned(EmoteObj.GetValue('id')) then + NewEmote.id := EmoteObj.GetValue('id').Value; + if Assigned(EmoteObj.GetValue('code')) then + NewEmote.code := EmoteObj.GetValue('code').Value; + + if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then + list.Add(NewEmote); + end; + end; + + // Обработка sharedEmotes + ChannelEmotes := TJSONObject(JSONValue).GetValue('sharedEmotes'); + if (ChannelEmotes is TJSONArray) then + begin + JSONArray := TJSONArray(ChannelEmotes); + for i := 0 to JSONArray.Count - 1 do + begin + if not (JSONArray.Items[i] is TJSONObject) then Continue; + + EmoteObj := TJSONObject(JSONArray.Items[i]); + NewEmote := Default(TBTTVr); + + if Assigned(EmoteObj.GetValue('id')) then + NewEmote.id := EmoteObj.GetValue('id').Value; + if Assigned(EmoteObj.GetValue('code')) then + NewEmote.code := EmoteObj.GetValue('code').Value; + + if not NewEmote.id.IsEmpty and not NewEmote.code.IsEmpty then + list.Add(NewEmote); + end; + end; + finally + JSONValue.Free; + end; +end; + +function TBTTV.GenerateURL(emoteName: string): string; +var + emote: TBTTVr; +begin + Result := ''; + for emote in list do + begin + if emote.code = emoteName then + begin + Result := 'https://cdn.betterttv.net/emote/' + emote.id + '/1x'; + Exit; + end; + end; +end; + +procedure TBTTV.GetCustom(uid: string); +begin + if not uid.IsEmpty then + AddEmotesUserJson(GetHTTP('users/twitch/' + uid)); +end; + +procedure TBTTV.GetGlobal; +begin + AddEmotesGlobalJson(GetHTTP('emotes/global')); +end; + +function TBTTV.GetHTTP(aMethod: string): string; +var + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + Result := ''; + http := TIdHTTP.Create(nil); + try + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + http.IOHandler := ssl; + ssl.SSLOptions.SSLVersions := [sslvTLSv1_2]; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + Result := http.Get('https://api.betterttv.net/3/cached/' + aMethod); + finally + ssl.Free; + end; + except + on E: Exception do + begin + toLog(2,'GetCustom',e.Message); + Result := ''; + end; + end; + http.Free; +end; + +procedure TBTTV.toLog(alevel: integer; amethod, amessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uCustomEmoties.TBTTV', amethod, amessage, alevel); +end; + +{ T7TV } + +constructor T7TV.Create; +begin + inherited; + list7 := TList.Create; +end; + +destructor T7TV.Destroy; +begin + FreeAndNil(list7); + inherited; +end; + +procedure T7TV.AddEmotesGlobalJson(const JsonStr: string); +var + Root: TJSONObject; + EmotesArray: TJSONArray; + EmoteObj, DataObj, HostObj: TJSONObject; + FilesArray: TJSONArray; + i: Integer; + Emote: T7TVr; + BaseUrl: string; +begin + Root := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject; + if not Assigned(Root) then Exit; + + try + EmotesArray := Root.GetValue('emotes') as TJSONArray; + if not Assigned(EmotesArray) then Exit; + + for i := 0 to EmotesArray.Count - 1 do + begin + if not (EmotesArray.Items[i] is TJSONObject) then Continue; + + EmoteObj := EmotesArray.Items[i] as TJSONObject; + Emote := Default(T7TVr); + + // Получение базовых данных + if Assigned(EmoteObj.GetValue('id')) then + Emote.id := EmoteObj.GetValue('id').Value; + if Assigned(EmoteObj.GetValue('name')) then + Emote.code := EmoteObj.GetValue('name').Value; + + // Получение URL + DataObj := EmoteObj.GetValue('data') as TJSONObject; + if Assigned(DataObj) then + begin + HostObj := DataObj.GetValue('host') as TJSONObject; + if Assigned(HostObj) then + begin + if Assigned(HostObj.GetValue('url')) then + begin + BaseUrl := 'https:' + HostObj.GetValue('url').Value; + + FilesArray := HostObj.GetValue('files') as TJSONArray; + if Assigned(FilesArray) and (FilesArray.Count > 0) and + (FilesArray.Items[0] is TJSONObject) then + begin + Emote.url := BaseUrl + '/' + + (FilesArray.Items[0] as TJSONObject).GetValue('name').Value; + end; + end; + end; + end; + + if not Emote.id.IsEmpty and not Emote.code.IsEmpty and not Emote.url.IsEmpty then + list7.Add(Emote); + end; + finally + Root.Free; + end; +end; + +procedure T7TV.AddEmotesUserJson(const JsonStr: string); +var + Root, EmoteSet, EmoteObj, DataObj, HostObj: TJSONObject; + EmotesArr, FilesArr: TJSONArray; + i: Integer; + Emote: T7TVr; + BaseUrl: string; +begin + Root := TJSONObject.ParseJSONValue(JsonStr) as TJSONObject; + if not Assigned(Root) then Exit; + + try + if not Root.TryGetValue('emote_set', EmoteSet) then Exit; + + EmotesArr := EmoteSet.GetValue('emotes') as TJSONArray; + if not Assigned(EmotesArr) then Exit; + + for i := 0 to EmotesArr.Count - 1 do + begin + if not (EmotesArr.Items[i] is TJSONObject) then Continue; + + EmoteObj := EmotesArr.Items[i] as TJSONObject; + Emote := Default(T7TVr); + + // Получение базовых данных + if Assigned(EmoteObj.GetValue('id')) then + Emote.id := EmoteObj.GetValue('id').Value; + if Assigned(EmoteObj.GetValue('name')) then + Emote.code := EmoteObj.GetValue('name').Value; + + // Получение URL + DataObj := EmoteObj.GetValue('data') as TJSONObject; + if Assigned(DataObj) then + begin + HostObj := DataObj.GetValue('host') as TJSONObject; + if Assigned(HostObj) then + begin + if Assigned(HostObj.GetValue('url')) then + begin + BaseUrl := 'https:' + HostObj.GetValue('url').Value; + + FilesArr := HostObj.GetValue('files') as TJSONArray; + if Assigned(FilesArr) and (FilesArr.Count > 0) and + (FilesArr.Items[0] is TJSONObject) then + begin + Emote.url := BaseUrl + '/' + + (FilesArr.Items[0] as TJSONObject).GetValue('name').Value; + end; + end; + end; + end; + + if not Emote.id.IsEmpty and not Emote.code.IsEmpty and not Emote.url.IsEmpty then + list7.Add(Emote); + end; + finally + Root.Free; + end; +end; + +function T7TV.GenerateURL(emoteName: string): string; +var + emote: T7TVr; +begin + Result := ''; + for emote in list7 do + begin + if emote.code = emoteName then + begin + Result := emote.url; + Exit; + end; + end; +end; + +procedure T7TV.GetCustom(uid: string); +begin + if not uid.IsEmpty then + AddEmotesUserJson(GetHTTP('users/twitch/' + uid)); +end; + +procedure T7TV.GetGlobal; +begin + AddEmotesGlobalJson(GetHTTP('emote-sets/global')); +end; + +function T7TV.GetHTTP(aMethod: string): string; +var + HttpClient: THTTPClient; + Response: IHTTPResponse; +begin + Result := ''; + HttpClient := THTTPClient.Create; + try + try + HttpClient.UserAgent := 'Mozilla/5.0'; + Response := HttpClient.Get('https://api.7tv.app/v3/' + aMethod); + Result := Response.ContentAsString; + except + on E: Exception do + begin + toLog(2,'GetHTTP',e.Message); + Result := ''; + end; + end; + finally + HttpClient.Free; + end; +end; + +procedure T7TV.toLog(alevel: integer; amethod, amessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uCustomEmoties.T7TV', amethod, amessage, alevel); +end; + +end. diff --git a/Services/uGigaChat.pas b/Services/uGigaChat.pas new file mode 100644 index 0000000..6e7b4c2 --- /dev/null +++ b/Services/uGigaChat.pas @@ -0,0 +1,132 @@ +unit uGigaChat; + +interface + +uses + uChatAPI, SysUtils, IdHTTP, System.JSON, IdSSLOpenSSL, IdGlobal, classes; + +type + TGigaChat = class(TChatAPI) + private + ClientID: string; + AutorizationCode: string; + function getAPIKey: string; + function GetTokenFromJson(jsonString: string): string; + protected + function GetOtvetFromJson(jsonString: string; isOllama:boolean = false): string; override; + public + constructor Create(Sender: TObject; aClientID: string; aAutorizationCode: string; aprefix: string = ''); reintroduce; + end; + +implementation + +{ TGigaChat } + +constructor TGigaChat.Create(Sender: TObject; aClientID: string; aAutorizationCode: string; aprefix: string = ''); +var AT:string; +begin +ClientID := aClientID; + AutorizationCode:=aAutorizationCode; + AT:= getAPIKey; + + inherited Create(Sender, at, aprefix); + + // Дополнительная инициализация, если необходимо +end; + +function TGigaChat.getAPIKey: string; +const + url = 'https://ngw.devices.sberbank.ru:9443/api/v2/oauth'; +var + params: TStringStream; + http: TIdHTTP; + ssl: TIdSSLIOHandlerSocketOpenSSL; +begin + http := TIdHTTP.Create(nil); + ssl := TIdSSLIOHandlerSocketOpenSSL.Create(nil); + try + http.IOHandler := ssl; + ssl.SSLOptions.method := sslvSSLv23; + http.Request.UserAgent := + 'Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'; + http.Request.CustomHeaders.Clear; + http.Request.CustomHeaders.Add + ('Content-Type: application/x-www-form-urlencoded'); + http.Request.CustomHeaders.Add('Accept: application/json'); + http.Request.CustomHeaders.Add('RqUID: ' + ClientID); + http.Request.CustomHeaders.Add('Authorization: Basic ' + AutorizationCode); + params := TStringStream.Create(' scope=GIGACHAT_API_PERS'); + result := GetTokenFromJson(http.Post(url, params)); + finally + params.Free; + http.Free; + ssl.Free; + end; +end; + + +function TGigaChat.GetOtvetFromJson(jsonString: string; isOllama:boolean = false): string; +var + JSON: TJSONObject; + choicesArray: TJSONArray; + choiceObject, messageObject: TJSONObject; + JSONValue: TJSONValue; +begin + Result := 'Произошла какая-то ошибка, попробуйте спрашивать по очереди!'; + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + // Проверяем наличие ключа "choices" + if JSON.TryGetValue('choices', JSONValue) then + begin + choicesArray := JSONValue as TJSONArray; + if Assigned(choicesArray) and (choicesArray.Count > 0) then + begin + // Получаем первый элемент массива "choices" + choiceObject := choicesArray.Items[0] as TJSONObject; + if Assigned(choiceObject) then + begin + // Проверяем наличие ключа "message" в первом элементе "choices" + if choiceObject.TryGetValue('message', JSONValue) then + begin + messageObject := JSONValue as TJSONObject; + if Assigned(messageObject) then + begin + // Извлекаем значение "content" из объекта "message" + Result := messageObject.GetValue('content'); + end; + end; + end; + end; + end; + end; + finally + JSON.Free; + end; +end; + +function TGigaChat.GetTokenFromJson(jsonString: string): string; +var + JSON: TJSONObject; + dataArray: TJSONString; +begin + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try + if Assigned(JSON) then + begin + if pos('access_token', jsonString) <> 0 then + begin + dataArray := JSON.GetValue('access_token') as TJSONString; + if Assigned(dataArray) then + Result := dataArray.GetValue(); + end + else + Result := ''; + end; + finally + JSON.Free; + end; +end; + +end. diff --git a/Services/uKandinskyAPI.pas b/Services/uKandinskyAPI.pas new file mode 100644 index 0000000..7b8db08 --- /dev/null +++ b/Services/uKandinskyAPI.pas @@ -0,0 +1,212 @@ +unit uKandinskyAPI; + +interface + +uses + System.SysUtils, System.Classes, System.JSON, System.Net.HttpClient, + System.Net.URLClient, System.NetConsts, StrUtils, System.Net.Mime, + System.NetEncoding, System.Threading; + +type + TGenerationDoneEvent = procedure(Sender: TObject; const FileName: string) of object; + TStatusUpdateEvent = procedure(Sender: TObject; const Message: string) of object; + TErrorEvent = procedure(Sender: TObject; const ErrorMessage: string) of object; + + TFusionBrainAPI = class(TComponent) + private + FBaseURL: string; + FApiKey: string; + FSecretKey: string; + FClient: THTTPClient; + FOnGenerationDone: TGenerationDoneEvent; + FOnStatusUpdate: TStatusUpdateEvent; + FOnError: TErrorEvent; + + procedure DoStatusUpdate(const AMessage: string); + procedure DoGenerationDone(const AFileName: string); + procedure DoError(const AErrorMessage: string); + function GetAuthHeaders: TNetHeaders; + function GetPipeline: string; + function Generate(const Prompt, PipelineId: string): string; + function CheckGeneration(const RequestId: string): TArray; + procedure SaveBase64Image(const Base64Str, FileName: string); + public + constructor Create(AOwner: TComponent; aKey:string; aSecret:string); + destructor Destroy; override; + procedure StartGeneration(const APrompt: string); + + property OnGenerationDone: TGenerationDoneEvent read FOnGenerationDone write FOnGenerationDone; + property OnStatusUpdate: TStatusUpdateEvent read FOnStatusUpdate write FOnStatusUpdate; + property OnError: TErrorEvent read FOnError write FOnError; + end; + +implementation + +uses ugeneral; + +constructor TFusionBrainAPI.Create(AOwner: TComponent; aKey:string; aSecret:string); +begin + inherited Create(AOwner); + FClient := THTTPClient.Create; + FBaseURL := 'https://api-key.fusionbrain.ai/'; + FApiKey :=aKey; + // FApiKey := '28C9C30489D635732FB04AA6B85F0671'; + FSecretKey := aSecret; + // FSecretKey := '805CB624C052202A05E3F40C0582045A'; +end; + +destructor TFusionBrainAPI.Destroy; +begin + FClient.Free; + inherited; +end; + +procedure TFusionBrainAPI.StartGeneration(const APrompt: string); +begin + TTask.Run(procedure + var + PipelineID, UUID, FileName: string; + Links: TArray; + begin + try + TThread.Queue(nil, procedure begin DoStatusUpdate('Получение конвейера...'); end); + PipelineID := GetPipeline; + + TThread.Queue(nil, procedure begin DoStatusUpdate('Генерация изображения...'); end); + UUID := Generate(APrompt, PipelineID); + + TThread.Queue(nil, procedure begin DoStatusUpdate('Проверка статуса...'); end); + Links := CheckGeneration(UUID); + + FileName := myConst.AppDataPath + 'imgs\kandinsky_' + FormatDateTime('yyyymmddhhnnss', Now) + '.jpg'; + SaveBase64Image(Links[0], FileName); + + TThread.Queue(nil, procedure begin DoGenerationDone(FileName); end); + except + on E: Exception do + TThread.Queue(nil, procedure begin DoError(E.Message); end); + end; + end); +end; + +function TFusionBrainAPI.GetAuthHeaders: TNetHeaders; +begin + SetLength(Result, 2); + Result[0] := TNetHeader.Create('X-Key', 'Key ' + FApiKey); + Result[1] := TNetHeader.Create('X-Secret', 'Secret ' + FSecretKey); +end; + +function TFusionBrainAPI.GetPipeline: string; +var + Response: IHTTPResponse; + Json: TJSONArray; +begin + Response := FClient.Get(FBaseURL + 'key/api/v1/pipelines', nil, GetAuthHeaders); + Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONArray; + try + Result := Json.Items[0].GetValue('id'); + finally + Json.Free; + end; +end; + +function TFusionBrainAPI.Generate(const Prompt, PipelineId: string): string; +var + Params, Root: TJSONObject; + Multipart: TMultipartFormData; + Response: IHTTPResponse; + Json: TJSONObject; +begin + Root := TJSONObject.Create; + try + Params := TJSONObject.Create; + Params.AddPair('query', Prompt); + + Root.AddPair('type', 'GENERATE'); + Root.AddPair('numImages', TJSONNumber.Create(1)); + Root.AddPair('width', TJSONNumber.Create(512)); + Root.AddPair('height', TJSONNumber.Create(512)); + Root.AddPair('generateParams', Params); + + Multipart := TMultipartFormData.Create; + try + Multipart.AddField('pipeline_id', PipelineId); + Multipart.AddField('params', Root.ToString, 'application/json'); + + Response := FClient.Post(FBaseURL + 'key/api/v1/pipeline/run', Multipart, nil, GetAuthHeaders); + Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject; + try + Result := Json.GetValue('uuid'); + finally + Json.Free; + end; + finally + Multipart.Free; + end; + finally + Root.Free; + end; +end; + +function TFusionBrainAPI.CheckGeneration(const RequestId: string): TArray; +var + Response: IHTTPResponse; + Json, ResultObj: TJSONObject; + Files: TJSONArray; + i: Integer; +begin + repeat + Sleep(5000); + Response := FClient.Get(FBaseURL + 'key/api/v1/pipeline/status/' + RequestId, nil, GetAuthHeaders); + Json := TJSONObject.ParseJSONValue(Response.ContentAsString) as TJSONObject; + try + if Json.GetValue('status') = 'DONE' then + begin + ResultObj := Json.GetValue('result'); + Files := ResultObj.GetValue('files'); + SetLength(Result, Files.Count); + for i := 0 to Files.Count - 1 do + Result[i] := Files.Items[i].Value; + Exit; + end; + finally + Json.Free; + end; + until False; +end; + +procedure TFusionBrainAPI.SaveBase64Image(const Base64Str, FileName: string); +var + DecodedStream: TMemoryStream; + InputStr: TStringStream; +begin + DecodedStream := TMemoryStream.Create; + InputStr := TStringStream.Create(Base64Str); + try + TNetEncoding.Base64.Decode(InputStr, DecodedStream); + DecodedStream.SaveToFile(FileName); + finally + DecodedStream.Free; + InputStr.Free; + end; +end; + +procedure TFusionBrainAPI.DoStatusUpdate(const AMessage: string); +begin + if Assigned(FOnStatusUpdate) then + FOnStatusUpdate(Self, AMessage); +end; + +procedure TFusionBrainAPI.DoGenerationDone(const AFileName: string); +begin + if Assigned(FOnGenerationDone) then + FOnGenerationDone(Self, AFileName); +end; + +procedure TFusionBrainAPI.DoError(const AErrorMessage: string); +begin + if Assigned(FOnError) then + FOnError(Self, AErrorMessage); +end; + +end. diff --git a/uTTWAPI.pas b/Services/uTTWAPI.pas similarity index 65% rename from uTTWAPI.pas rename to Services/uTTWAPI.pas index 65dcdae..692f1e8 100644 --- a/uTTWAPI.pas +++ b/Services/uTTWAPI.pas @@ -7,8 +7,8 @@ uses IdMultipartFormData, DateUtils, uDataBase, System.Generics.Collections, uRecords; - type - TOnLog = procedure(aModul: string; aMethod: string; aMessage:string; aLevel:integer) of object; +type + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; type TTTW_API = class(TObject) @@ -18,8 +18,8 @@ type ClientID: string; channel_name_api: string; BotName_api: string; - FChatBadges:tlist; - FOnLog:TOnLog; + FChatBadges: TList; + FOnLog: TOnLog; function GetFollowedAtFromJson(jsonString: string): string; function getTTW(method: string; ClientID: string; @@ -35,7 +35,7 @@ type function patchTTW(method: string; ClientID: string; params: TStringStream; isStreamer: boolean = false): string; overload; - procedure toLog(alevel:integer; amethod:string; amessage:string); + procedure toLog(alevel: integer; amethod: string; amessage: string); public constructor Create(Sender: TObject); destructor Destroy; override; @@ -57,25 +57,24 @@ type procedure delModerator(id: string); procedure setVIP(id: string); procedure delVIP(id: string); - procedure getCustomReward(var acr: Tlist); + procedure getCustomReward(var acr: TList); function createCustomReward(title: string; cost: string; - promt: string = ''; isUserInput: boolean = false):TCustomRevards; + promt: string = ''; isUserInput: boolean = false): TCustomRevards; procedure UpdateCustomReward(ahr: TCustomRevards); procedure UpdateRedemptionStatus(ahr: TCustomRewardEvent); procedure deleteCustomReward(id: string); - function getRoomAndBot():string; + function getRoomAndBot(): string; function getUserbyLogin(login: string): TUser; - function getFollow(id: string): tdatetime; + function getFollow(id: string): TDateTime; function GetRoomID: string; - procedure getGlobalChatBadges(var gcb: Tlist); - procedure getCustomChatBadges(var ccb: Tlist); - procedure GetChannelEmotes(var ce: Tlist); - procedure GetGlobalEmotes(var ge: Tlist); - function ValidateTwitchToken(const TokenName, TokenValue: string; var DayOfLive:integer): Boolean; + procedure getGlobalChatBadges(var gcb: TList); + procedure getCustomChatBadges(var ccb: TList); + procedure GetChannelEmotes(var ce: TList); + procedure GetGlobalEmotes(var ge: TList); + function ValidateTwitchToken(const TokenName, TokenValue: string; var DayOfLive: integer): Boolean; property OnLog: TOnLog read FOnLog write FOnLog; end; - TChatBadges = TList; - TEmotesList = TList; + var room_id: string = ''; bot_id: string = ''; @@ -86,19 +85,23 @@ uses uGeneral; constructor TTTW_API.Create(Sender: TObject); begin - // Инициализация + FChatBadges := TList.Create; end; function TTTW_API.createCustomReward(title: string; cost: string; - promt: string = ''; isUserInput: boolean = false):TCustomRevards; + promt: string = ''; isUserInput: boolean = false): TCustomRevards; var RequestData: TStringStream; - s, s1, json: string; i:integer; - cr:TCustomRevards; JSONData:TJSONObject; JSONArray:TJSONArray; + s, s1, json: string; + i: integer; + cr: TCustomRevards; + JSONData: TJSONObject; + JSONArray: TJSONArray; begin + Result := Default(TCustomRevards); try if room_id = '' then - room_id:=GetRoomID ; + room_id := GetRoomID; s := ''; s1 := ''; if isUserInput then @@ -106,46 +109,53 @@ begin if promt <> '' then s1 := ' "prompt":"' + promt + '",'; RequestData := TStringStream.Create('{ "title":"' + title + '", ' + s + s1 - + ' "cost":' + cost + ' }', CP_UTF8); - json := postTTW('channel_points/custom_rewards?broadcaster_id=' + room_id, ClientID, - RequestData, true); - if json = '' then - // fLog.toLog(1,'TTW_API','createCustomReward','Награда не создалась, запрос врнул пустой ответ'); - JSONData := TJSONObject.ParseJSONValue(JSON) as TJSONObject; + + ' "cost":' + cost + ' }', TEncoding.UTF8); try - if Assigned(JSONData) then + json := postTTW('channel_points/custom_rewards?broadcaster_id=' + room_id, ClientID, + RequestData, true); + if json = '' then begin + toLog(1, 'createCustomReward', 'Награда не создалась, запрос вернул пустой ответ'); + Exit; + end; + + JSONData := TJSONObject.ParseJSONValue(json) as TJSONObject; + if not Assigned(JSONData) then + begin + toLog(2, 'createCustomReward', 'Ошибка парсинга JSON'); + Exit; + end; + + try JSONArray := JSONData.GetValue('data'); - for i := 0 to JSONArray.Count - 1 do + if JSONArray.Count > 0 then begin - cr.id := JSONArray.Items[i].GetValue('id'); - cr.title := JSONArray.Items[i].GetValue('title'); - cr.promt := JSONArray.Items[i].GetValue('prompt'); - cr.cost := JSONArray.Items[i].GetValue('cost'); - cr.is_user_input_required := JSONArray.Items[i].GetValue + cr.id := JSONArray.Items[0].GetValue('id'); + cr.title := JSONArray.Items[0].GetValue('title'); + cr.promt := JSONArray.Items[0].GetValue('prompt'); + cr.cost := JSONArray.Items[0].GetValue('cost'); + cr.is_user_input_required := JSONArray.Items[0].GetValue ('is_user_input_required'); + Result := cr; end; + finally + JSONData.Free; end; finally - JSONData.Free; + RequestData.Free; end; - result:=cr; except on E: Exception do - // Form1.Log(2, 'TTTW_API.createCustomReward', E.Message); - // flog.toLog(2,'TTW_API','createCustomReward',E.Message); + toLog(2, 'createCustomReward', E.Message); end; - end; destructor TTTW_API.Destroy; begin - // Освобождение ресурсов + FChatBadges.Free; inherited; end; - - procedure TTTW_API.Init(myClient, myToken, streamerToken, Channel, myBotName: string); begin @@ -154,7 +164,6 @@ begin Token_api_streamer := streamerToken; channel_name_api := Channel; BotName_api := myBotName; - end; procedure TTTW_API.banUser(id: string); @@ -162,14 +171,13 @@ var RequestData: TStringStream; begin try - if bot_id = '' then - exit; - if room_id = '' then - exit; + bot_id := getRoomAndBot; + if (bot_id = '') or (room_id = '') then + Exit; RequestData := TStringStream.Create('{"data": {"user_id":"' + id + - '","reason":"no reason"}}'); + '","reason":"no reason"}}', TEncoding.UTF8); try postTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + bot_id, ClientID, RequestData); @@ -178,24 +186,22 @@ begin end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.banUser', E.Message); - // flog.toLog(2,'TTW_API','banUser',E.Message); + toLog(2, 'banUser', E.Message); end; end; procedure TTTW_API.banUserTime(id: string; aTime: integer); var - RequestData: TStringStream; begin try if bot_id = '' then - exit; - if room_id = '' then - exit; + bot_id := getRoomAndBot; + if (bot_id = '') or (room_id = '') then + Exit; RequestData := TStringStream.Create('{"data": {"user_id":"' + id + - '","duration":' + inttostr(aTime) + '}}'); + '","duration":' + IntToStr(aTime) + '}}', TEncoding.UTF8); try postTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + bot_id, ClientID, RequestData); @@ -204,8 +210,7 @@ begin end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.banUserTime', E.Message); - // flog.toLog(2,'TTW_API','banUserTime',E.Message); + toLog(2, 'banUserTime', E.Message); end; end; @@ -213,15 +218,14 @@ procedure TTTW_API.deleteCustomReward(id: string); begin try if Token_api_streamer = '' then - exit; + Exit; if room_id = '' then - exit; + Exit; DelTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + '&id=' + id, ClientID, true); except on E: Exception do - ///Form1.Log(2, 'TTTW_API.deleteCustomReward', E.Message); - // flog.toLog(2,'TTW_API','deleteCustomReward',E.Message); + toLog(2, 'deleteCustomReward', E.Message); end; end; @@ -229,17 +233,14 @@ procedure TTTW_API.delModerator(id: string); begin try if Token_api_streamer = '' then - exit; - + Exit; if room_id = '' then - exit; - + Exit; DelTTW('moderation/moderators?broadcaster_id=' + room_id + '&user_id=' + id, ClientID, true); except on E: Exception do - //Form1.Log(2, 'TTTW_API.delModerator', E.Message); - // flog.toLog(2,'TTW_API','delModerator',E.Message); + toLog(2, 'delModerator', E.Message); end; end; @@ -272,8 +273,7 @@ begin Result := response1; except on E: Exception do - //Form1.Log(2, 'TTTW_API.DelTTW', E.Message+' ['+method+']'); - // flog.toLog(2,'TTW_API','DelTTW',E.Message+' ['+method+']'); + toLog(2, 'DelTTW', E.Message + ' [' + method + ']'); end; finally http.Free; @@ -285,75 +285,81 @@ procedure TTTW_API.delVIP(id: string); begin try if Token_api_streamer = '' then - exit; - + Exit; if room_id = '' then - exit; - + Exit; DelTTW('channels/vips?broadcaster_id=' + room_id + '&user_id=' + id, ClientID, true); except on E: Exception do - //Form1.Log(2, 'TTTW_API.delVIP', E.Message); - // flog.toLog(2,'TTW_API','delVIP',E.Message); + toLog(2, 'delVIP', E.Message); end; end; -procedure TTTW_API.getCustomReward(var acr: Tlist); +procedure TTTW_API.getCustomReward(var acr: TList); var JSON: string; sl: TCustomRevards; JSONData: TJSONObject; JSONArray: TJSONArray; i: integer; - begin - try if Token_api_streamer = '' then begin - exit; + toLog(1, 'getCustomReward', 'Token_api_streamer пуст'); + Exit; end; if room_id = '' then - exit; + Exit; JSON := getTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + '&only_manageable_rewards=true', ClientID, true); - JSONData := TJSONObject.ParseJSONValue(JSON) as TJSONObject; - try - if Assigned(JSONData) then - begin - JSONArray := JSONData.GetValue('data'); - for i := 0 to JSONArray.Count - 1 do - begin - sl.id := JSONArray.Items[i].GetValue('id'); - sl.title := JSONArray.Items[i].GetValue('title'); - sl.promt := JSONArray.Items[i].GetValue('prompt'); - sl.cost := JSONArray.Items[i].GetValue('cost'); - sl.is_user_input_required := JSONArray.Items[i].GetValue - ('is_user_input_required'); - acr.add(sl); - end; + if JSON = '' then + begin + toLog(1, 'getCustomReward', 'Пустой ответ от API'); + Exit; + end; + JSONData := TJSONObject.ParseJSONValue(JSON) as TJSONObject; + if not Assigned(JSONData) then + begin + toLog(2, 'getCustomReward', 'Ошибка парсинга JSON'); + Exit; + end; + + try + JSONArray := JSONData.GetValue('data'); + for i := 0 to JSONArray.Count - 1 do + begin + sl.id := JSONArray.Items[i].GetValue('id'); + sl.title := JSONArray.Items[i].GetValue('title'); + sl.promt := JSONArray.Items[i].GetValue('prompt'); + sl.cost := JSONArray.Items[i].GetValue('cost'); + sl.is_user_input_required := JSONArray.Items[i].GetValue + ('is_user_input_required'); + acr.Add(sl); end; finally JSONData.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.getCustomReward', E.Message); - // flog.toLog(2,'TTW_API','getCustomReward',E.Message); + toLog(2, 'getCustomReward', E.Message); end; - end; -function TTTW_API.getFollow(id: string): tdatetime; +function TTTW_API.getFollow(id: string): TDateTime; var s: string; + followedAt: string; begin + Result := 0; s := getTTW('channels/followers?user_id=' + id + '&broadcaster_id=' + room_id, ClientID); - Result := strToDate(GetFollowedAtFromJson(s)); + followedAt := GetFollowedAtFromJson(s); + if followedAt <> '' then + Result := StrToDate(followedAt); end; function TTTW_API.GetFollowedAtFromJson(jsonString: string): string; @@ -362,15 +368,15 @@ var dataArray: TJSONArray; begin Result := ''; + if jsonString = '' then Exit; + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + if not Assigned(JSON) then Exit; + try - if Assigned(JSON) then - begin - dataArray := JSON.GetValue('data') as TJSONArray; - if Assigned(dataArray) and (dataArray.Count > 0) then - Result := DateToStr - (ISO8601ToDate(dataArray.Items[0].GetValue('followed_at'))); - end; + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then + Result := DateToStr(ISO8601ToDate(dataArray.Items[0].GetValue('followed_at'))); finally JSON.Free; end; @@ -404,8 +410,7 @@ begin Result := response1; except on E: Exception do - ////Form1.Log(2, 'TTTW_API.getTTW', E.Message+' ['+method+']'); - // flog.toLog(2,'TTW_API','getTTW',E.Message+' ['+method+']'); + toLog(2, 'getTTW', E.Message + ' [' + method + ']'); end; finally http.Free; @@ -433,26 +438,26 @@ begin url := 'https://twitchtracker.com/api/channels/summary/' + Channel; response1 := http.Get(url); jsonObject := TJSONObject.ParseJSONValue(response1) as TJSONObject; + if not Assigned(jsonObject) then + begin + toLog(2, 'getTTWStat', 'Ошибка парсинга JSON'); + Exit; + end; + try - if Assigned(jsonObject) then - begin - avg_viewers := jsonObject.GetValue('avg_viewers'); - max_viewers := jsonObject.GetValue('max_viewers'); - hours_watched := jsonObject.GetValue('hours_watched'); - followers := jsonObject.GetValue('followers'); - followers_total := jsonObject.GetValue('followers_total'); - end; + avg_viewers := jsonObject.GetValue('avg_viewers'); + max_viewers := jsonObject.GetValue('max_viewers'); + hours_watched := jsonObject.GetValue('hours_watched'); + followers := jsonObject.GetValue('followers'); + followers_total := jsonObject.GetValue('followers_total'); finally - if Assigned(jsonObject) then - jsonObject.Free; + jsonObject.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.getTTWStat', E.Message); - // flog.toLog(2,'TTW_API','getTTWStat',E.Message); + toLog(2, 'getTTWStat', E.Message); end; finally - http.Free; ssl.Free; end; @@ -460,7 +465,6 @@ end; function ParseBadgeVersion(VersionObj: TJSONObject): TBadgeVersion; begin - // Добавьте проверки на nil для каждого поля Result.Id := VersionObj.GetValue('id').Value; Result.ImageUrl1x := VersionObj.GetValue('image_url_1x').Value; Result.ImageUrl2x := VersionObj.GetValue('image_url_2x').Value; @@ -476,107 +480,102 @@ var VersionsArray: TJSONArray; I: Integer; begin - // Добавьте проверки на nil для каждого поля Result.SetId := BadgeObj.GetValue('set_id').Value; - VersionsArray := BadgeObj.GetValue('versions') as TJSONArray; SetLength(Result.Versions, VersionsArray.Count); - for I := 0 to VersionsArray.Count - 1 do Result.Versions[I] := ParseBadgeVersion(VersionsArray.Items[I] as TJSONObject); end; -procedure ParseBadgesFromApi(const JSONResponse: string; Badges: TChatBadges); +procedure ParseBadgesFromApi(const JSONResponse: string; Badges: TList); var RootObj: TJSONObject; DataArray: TJSONArray; I: Integer; - Badge: TChatBadge; begin + if JSONResponse = '' then Exit; RootObj := TJSONObject.ParseJSONValue(JSONResponse) as TJSONObject; + if not Assigned(RootObj) then Exit; + try DataArray := RootObj.GetValue('data') as TJSONArray; - for I := 0 to DataArray.Count - 1 do - begin - Badge := ParseChatBadge(DataArray.Items[I] as TJSONObject); - Badges.Add(Badge); - end; + Badges.Add(ParseChatBadge(DataArray.Items[I] as TJSONObject)); finally RootObj.Free; end; end; -procedure TTTW_API.getGlobalChatBadges(var gcb: Tlist); -var - jsonString: string; - global:TChatBadge; -begin - JSONString:=getTTW('chat/badges/global',ClientID,false); - ParseBadgesFromApi(JSONString,gcb); -end; - - - -procedure TTTW_API.getCustomChatBadges(var ccb: Tlist); +procedure TTTW_API.getGlobalChatBadges(var gcb: TList); var jsonString: string; begin - JSONString:=getTTW('chat/badges?broadcaster_id='+room_id,ClientID,false); - ParseBadgesFromApi(JSONString,ccb); + jsonString := getTTW('chat/badges/global', ClientID, false); + ParseBadgesFromApi(jsonString, gcb); end; -function TTTW_API.getRoomAndBot():string; +procedure TTTW_API.getCustomChatBadges(var ccb: TList); +var + jsonString: string; +begin + if room_id = '' then Exit; + jsonString := getTTW('chat/badges?broadcaster_id=' + room_id, ClientID, false); + ParseBadgesFromApi(jsonString, ccb); +end; + +function TTTW_API.getRoomAndBot(): string; var jsonString: string; JSON: TJSONObject; dataArray: TJSONArray; begin -try - jsonString := getTTW('users?login=' + LowerCase(BotName_api), ClientID); - JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; try - if Assigned(JSON) then + jsonString := getTTW('users?login=' + LowerCase(BotName_api), ClientID); + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + if not Assigned(JSON) then begin + toLog(2, 'getRoomAndBot.GetBotID', 'Ошибка парсинга JSON'); + Exit; + end; + + try dataArray := JSON.GetValue('data') as TJSONArray; if Assigned(dataArray) and (dataArray.Count > 0) then - begin bot_id := dataArray.Items[0].GetValue('id'); - end; + finally + JSON.Free; end; - finally - JSON.Free; + except + on E: Exception do + toLog(2, 'getRoomAndBot.GetBotID', E.Message); end; -except - on E: Exception do - // fLog.toLog(2,'TTW_API','getRoomAndBot.GetBotID',e.Message); -end; -try - jsonString := getTTW('users?login=' + LowerCase(channel_name_api), ClientID); - JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + try - if Assigned(JSON) then + jsonString := getTTW('users?login=' + LowerCase(channel_name_api), ClientID); + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + if not Assigned(JSON) then begin + toLog(2, 'getRoomAndBot.GetRoomID', 'Ошибка парсинга JSON'); + Exit; + end; + + try dataArray := JSON.GetValue('data') as TJSONArray; if Assigned(dataArray) and (dataArray.Count > 0) then - begin room_id := dataArray.Items[0].GetValue('id'); - end; + finally + JSON.Free; end; - finally - JSON.Free; + except + on E: Exception do + toLog(2, 'getRoomAndBot.GetRoomID', E.Message); end; -except - on E: Exception do - // fLog.toLog(2,'TTW_API','getRoomAndBot.GetRoomID',e.Message); -end; - result:=room_id; + Result := room_id; end; function TTTW_API.GetRoomID: string; begin - Result := room_id; end; @@ -587,25 +586,26 @@ var dataArray: TJSONArray; jsonString: string; begin + Result := Default(TUser); jsonString := getTTW('users?login=' + LowerCase(login), ClientID); + if jsonString = '' then Exit; + JSON := TJSONObject.ParseJSONValue(jsonString) as TJSONObject; + if not Assigned(JSON) then Exit; + try - if Assigned(JSON) then + dataArray := JSON.GetValue('data') as TJSONArray; + if Assigned(dataArray) and (dataArray.Count > 0) then begin - dataArray := JSON.GetValue('data') as TJSONArray; - if Assigned(dataArray) and (dataArray.Count > 0) then - begin - u.id := dataArray.Items[0].GetValue('id'); - u.login := dataArray.Items[0].GetValue('login'); - u.DisplayName := dataArray.Items[0].GetValue('display_name'); - u.created_at := ISO8601ToDate - (dataArray.Items[0].GetValue('created_at')); - end; + u.id := dataArray.Items[0].GetValue('id'); + u.login := dataArray.Items[0].GetValue('login'); + u.DisplayName := dataArray.Items[0].GetValue('display_name'); + u.created_at := ISO8601ToDate(dataArray.Items[0].GetValue('created_at')); + Result := u; end; finally JSON.Free; end; - Result := u; end; function TTTW_API.postTTW(method, ClientID: string; @@ -638,8 +638,7 @@ begin Result := response1; except on E: Exception do - //Form1.Log(2, 'TTTW_API.postTTW', E.Message+' ['+method+']'); - // flog.toLog(2,'TTW_API','postTTW',E.Message+' ['+method+']'); + toLog(2, 'postTTW', E.Message + ' [' + method + ']'); end; finally http.Free; @@ -677,8 +676,7 @@ begin Result := response1; except on E: Exception do - //Form1.Log(2, 'TTTW_API.patchTTW', E.Message+' ['+method+']'); - //flog.toLog(2,'TTW_API','patchTTW',E.Message+' ['+method+']'); + toLog(2, 'patchTTW', E.Message + ' [' + method + ']'); end; finally http.Free; @@ -716,8 +714,7 @@ begin Result := response1; except on E: Exception do - //Form1.Log(2, 'TTTW_API.postTTW', E.Message+' ['+method+']'); - //flog.toLog(2,'TTW_API','postTTW',E.Message+' ['+method+']'); + toLog(2, 'postTTW', E.Message + ' [' + method + ']'); end; finally http.Free; @@ -730,21 +727,17 @@ var p: TIdMultipartFormDataStream; begin try - if room_id = '' then - exit; - + Exit; p := TIdMultipartFormDataStream.Create; try - postTTW('raids?from_broadcaster_id=' + room_id + '&to_broadcaster_id=' + - id, ClientID, p, true); + postTTW('raids?from_broadcaster_id=' + room_id + '&to_broadcaster_id=' + id, ClientID, p, true); finally p.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.raid', E.Message); - // flog.toLog(2,'TTW_API','raid',E.Message); + toLog(2, 'raid', E.Message); end; end; @@ -755,31 +748,29 @@ begin try if bot_id = '' then begin - //flog.toLog(1,'TTW_API','SendNotify','bot_id пуст. Исправляю'); - getRoomAndBot; - if bot_id = '' then + toLog(1, 'SendNotify', 'bot_id пуст. Исправляю'); + getRoomAndBot; + if bot_id = '' then begin - // flog.toLog(2,'TTW_API','SendNotify','bot_id все равно пуст'); - exit; + toLog(2, 'SendNotify', 'bot_id все равно пуст'); + Exit; end; end; if room_id = '' then begin - // flog.toLog(2,'TTW_API','SendNotify','room_id пуст'); - exit; + toLog(2, 'SendNotify', 'room_id пуст'); + Exit; end; - p := TStringStream.Create('{"message":"' + text + - '","color":"primary"}', CP_UTF8); + p := TStringStream.Create('{"message":"' + text + '","color":"primary"}', TEncoding.UTF8); try - postTTW('chat/announcements?broadcaster_id=' + room_id + '&moderator_id=' - + bot_id, ClientID, p); + postTTW('chat/announcements?broadcaster_id=' + room_id + '&moderator_id=' + bot_id, ClientID, p); finally p.Free; end; except on E: Exception do - // flog.toLog(2,'TTW_API','SendNotify',E.Message); + toLog(2, 'SendNotify', E.Message); end; end; @@ -788,47 +779,38 @@ var RequestData: TStringStream; begin try - if Token_api_streamer = '' then - exit; - if room_id = '' then - exit; + if Token_api_streamer = '' then Exit; + if room_id = '' then Exit; - RequestData := TStringStream.Create(''); + RequestData := TStringStream.Create('', TEncoding.UTF8); try - postTTW('moderation/moderators?broadcaster_id=' + room_id + '&user_id=' + - id, ClientID, RequestData, true); + postTTW('moderation/moderators?broadcaster_id=' + room_id + '&user_id=' + id, ClientID, RequestData, true); finally RequestData.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.setModerator', E.Message); - // flog.toLog(2,'TTW_API','setModerator',E.Message); + toLog(2, 'setModerator', E.Message); end; end; procedure TTTW_API.setVIP(id: string); var - userID: string; RequestData: TStringStream; begin try - if Token_api_streamer = '' then - exit; - if room_id = '' then - exit; + if Token_api_streamer = '' then Exit; + if room_id = '' then Exit; - RequestData := TStringStream.Create(''); + RequestData := TStringStream.Create('', TEncoding.UTF8); try - postTTW('channels/vips?broadcaster_id=' + room_id + '&user_id=' + userID, - ClientID, RequestData, true); + postTTW('channels/vips?broadcaster_id=' + room_id + '&user_id=' + id, ClientID, RequestData, true); finally RequestData.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.setVIP', E.Message); - // flog.toLog(2,'TTW_API','setVIP',E.Message); + toLog(2, 'setVIP', E.Message); end; end; @@ -837,59 +819,46 @@ var p: TIdMultipartFormDataStream; begin try - if bot_id = '' then - exit; - if room_id = '' then - exit; - + if (bot_id = '') or (room_id = '') then + Exit; p := TIdMultipartFormDataStream.Create; try - postTTW('chat/shoutouts?from_broadcaster_id=' + room_id + - '&to_broadcaster_id=' + id + '&moderator_id=' + bot_id, ClientID, p); + postTTW('chat/shoutouts?from_broadcaster_id=' + room_id + '&to_broadcaster_id=' + id + '&moderator_id=' + bot_id, ClientID, p); finally p.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.shoutouts', E.Message); - // flog.toLog(2,'TTW_API','shoutouts',E.Message); + toLog(2, 'shoutouts', E.Message); end; end; procedure TTTW_API.toLog(alevel: integer; amethod, amessage: string); begin if Assigned(FOnLog) then - FOnLog('uTTWAPI', aMethod, aMessage, aLevel); + FOnLog('uTTWAPI', amethod, amessage, alevel); end; procedure TTTW_API.unBanUser(id: string); begin try - if bot_id = '' then - exit; - if room_id = '' then - exit; - - DelTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + - bot_id + '&user_id=' + id, ClientID); + if bot_id = '' then bot_id := getRoomAndBot; + if (bot_id = '') or (room_id = '') then Exit; + DelTTW('moderation/bans?broadcaster_id=' + room_id + '&moderator_id=' + bot_id + '&user_id=' + id, ClientID); except on E: Exception do - //Form1.Log(2, 'TTTW_API.unBanUser', E.Message); - toLog(2,'unBanUser',E.Message); + toLog(2, 'unBanUser', E.Message); end; end; procedure TTTW_API.unRaid; begin try - if room_id = '' then - exit; - + if room_id = '' then Exit; DelTTW('raids?broadcaster_id=' + room_id, ClientID); except on E: Exception do - //Form1.Log(2, 'TTTW_API.unRaid', E.Message); - toLog(2,'unRaid',E.Message); + toLog(2, 'unRaid', E.Message); end; end; @@ -899,21 +868,17 @@ var qid: string; begin try - if room_id = '' then - exit; + if room_id = '' then Exit; qid := ahr.id; - RequestData := TStringStream.Create('{"cost": ' + inttostr(ahr.cost) + - '}', CP_UTF8); + RequestData := TStringStream.Create('{"cost": ' + IntToStr(ahr.cost) + '}', TEncoding.UTF8); try - patchTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + - '&id=' + qid, ClientID, RequestData, true); + patchTTW('channel_points/custom_rewards?broadcaster_id=' + room_id + '&id=' + qid, ClientID, RequestData, true); finally RequestData.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.UpdateCustomReward', E.Message); - toLog(2,'UpdateCustomReward',E.Message); + toLog(2, 'UpdateCustomReward', E.Message); end; end; @@ -923,35 +888,26 @@ var RequestData: TStringStream; begin try - if bot_id = '' then - exit; - if room_id = '' then - exit; + if (bot_id = '') or (room_id = '') then + Exit; qbid := ahr.event.broadcaster_user_id; qrid := ahr.event.revard.id; qid := ahr.event.id; - //Form1.Log(1, 'TTTW_API.UpdateRedemptionStatus', 'ChannelId: ' + qbid + - // '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); + toLog(0, 'UpdateRedemptionStatus', 'ChannelId: ' + qbid + '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); - toLog(1,'UpdateRedemptionStatus','ChannelId: ' + qbid + - '; Reward.id: ' + qrid + '; Redemption.id: ' + qid); - RequestData := TStringStream.Create('{"status":"CANCELED"}', CP_UTF8); + RequestData := TStringStream.Create('{"status":"CANCELED"}', TEncoding.UTF8); try - patchTTW('channel_points/custom_rewards/redemptions?broadcaster_id=' + - qbid + '&reward_id=' + qrid + '&id=' + qid, ClientID, - RequestData, true); + patchTTW('channel_points/custom_rewards/redemptions?broadcaster_id=' + qbid + '&reward_id=' + qrid + '&id=' + qid, ClientID, RequestData, true); finally RequestData.Free; end; except on E: Exception do - // Form1.Log(2, 'TTTW_API.UpdateRedemptionStatus', E.Message); - toLog(2,'UpdateRedemptionStatus',E.Message); + toLog(2, 'UpdateRedemptionStatus', E.Message); end; end; -function TTTW_API.ValidateTwitchToken(const TokenName, - TokenValue: string; var DayOfLive:integer): Boolean; +function TTTW_API.ValidateTwitchToken(const TokenName, TokenValue: string; var DayOfLive: integer): Boolean; var HTTP: TIdHTTP; SSLHandler: TIdSSLIOHandlerSocketOpenSSL; @@ -961,8 +917,10 @@ var ResponseText: string; begin Result := False; + DayOfLive := 0; if Trim(TokenValue) = '' then begin + toLog(1, 'ValidateTwitchToken', 'Пустой токен'); Exit; end; @@ -988,6 +946,7 @@ begin end; on E: Exception do begin + toLog(2, 'ValidateTwitchToken', E.Message); Exit; end; end; @@ -996,14 +955,17 @@ begin begin try ResponseJSON := TJSONObject.ParseJSONValue(ResponseText) as TJSONObject; + if not Assigned(ResponseJSON) then + begin + toLog(2, 'ValidateTwitchToken', 'Ошибка парсинга JSON'); + Exit; + end; + try if ResponseJSON.GetValue('expires_in') <> nil then begin - toLog(0, 'ValidateTwitchToken', - Format('Токен действителен. Осталось: %d сек. Клиент: %s', - [ResponseJSON.GetValue('expires_in').Value.ToInteger, - ResponseJSON.GetValue('client_id').Value])); - DayOfLive:=round(ResponseJSON.GetValue('expires_in').Value.ToInteger/60/60/24); + DayOfLive := Round(ResponseJSON.GetValue('expires_in').Value.ToInteger / 86400); + toLog(0, 'ValidateTwitchToken', Format('Токен действителен. Осталось: %d дней', [DayOfLive])); end; Result := True; finally @@ -1017,15 +979,11 @@ begin else if StatusCode = 401 then begin toLog(2, 'ValidateTwitchToken', 'Invalid token'); - DayOfLive:=0; end else begin - DayOfLive:=0; - toLog(2, 'ValidateTwitchToken', - Format('HTTP %d: %s', [StatusCode, ResponseText])); + toLog(2, 'ValidateTwitchToken', Format('HTTP %d: %s', [StatusCode, ResponseText])); end; - finally ResponseStream.Free; SSLHandler.Free; @@ -1038,28 +996,21 @@ var RequestData: TStringStream; begin try - if bot_id = '' then - exit; - if room_id = '' then - exit; + if bot_id = '' then bot_id := getRoomAndBot; + if (bot_id = '') or (room_id = '') then Exit; - RequestData := TStringStream.Create('{"data": {"user_id":"' + id + - '","reason":"Вам вынесено предупреждение!"}}', CP_UTF8); + RequestData := TStringStream.Create('{"data": {"user_id":"' + id + '","reason":"Вам вынесено предупреждение!"}}', TEncoding.UTF8); try - postTTW('moderation/warnings?broadcaster_id=' + room_id + '&moderator_id=' - + bot_id, ClientID, RequestData); + postTTW('moderation/warnings?broadcaster_id=' + room_id + '&moderator_id=' + bot_id, ClientID, RequestData); finally RequestData.Free; end; except on E: Exception do - //Form1.Log(2, 'TTTW_API.warnUser', E.Message); - toLog(2,'warnUser',E.Message); + toLog(2, 'warnUser', E.Message); end; end; - -// Вспомогательные функции для парсинга function GetStringValue(JSONObj: TJSONObject; const Name: string): string; var Val: TJSONValue; @@ -1078,14 +1029,12 @@ var begin SetLength(Result, 0); if not JSONObj.TryGetValue(Name, Arr) then Exit; - SetLength(Result, Arr.Count); for I := 0 to Arr.Count - 1 do Result[I] := Arr.Items[I].Value; end; -// Функция для преобразования JSON в список эмодзи -procedure ParseEmotes(const JSONString: string; EmotesList: TEmotesList); +procedure ParseEmotes(const JSONString: string; EmotesList: TList); var RootObj: TJSONObject; DataArr: TJSONArray; @@ -1094,23 +1043,22 @@ var Emote: TEmotes; I: Integer; begin + if JSONString = '' then Exit; RootObj := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(RootObj) then Exit; + try if not RootObj.TryGetValue('data', DataArr) then Exit; - for I := 0 to DataArr.Count - 1 do begin EmoteObj := DataArr.Items[I] as TJSONObject; - - // Заполняем основную информацию Emote.id := GetStringValue(EmoteObj, 'id'); Emote.name := GetStringValue(EmoteObj, 'name'); Emote.tier := GetStringValue(EmoteObj, 'tier'); Emote.emote_type := GetStringValue(EmoteObj, 'emote_type'); Emote.emote_set_id := GetStringValue(EmoteObj, 'emote_set_id'); - // Парсим изображения if EmoteObj.TryGetValue('images', ImagesObj) then begin Emote.images.Url1x := GetStringValue(ImagesObj, 'url_1x'); @@ -1118,11 +1066,9 @@ begin Emote.images.Url4x := GetStringValue(ImagesObj, 'url_4x'); end; - // Парсим массивы Emote.format := GetStringArray(EmoteObj, 'format'); Emote.scale := GetStringArray(EmoteObj, 'scale'); Emote.theme_mode := GetStringArray(EmoteObj, 'theme_mode'); - EmotesList.Add(Emote); end; finally @@ -1130,18 +1076,21 @@ begin end; end; - procedure TTTW_API.GetChannelEmotes(var ce: Tlist); -var jsonres:string; +procedure TTTW_API.GetChannelEmotes(var ce: TList); +var + jsonres: string; begin - jsonres:=getTTW('chat/emotes?broadcaster_id='+room_id,ClientID,false); - ParseEmotes(jsonres, ce); + if room_id = '' then Exit; + jsonres := getTTW('chat/emotes?broadcaster_id=' + room_id, ClientID, false); + ParseEmotes(jsonres, ce); end; -procedure TTTW_API.GetGlobalEmotes(var ge: Tlist); -var jsonres:string; +procedure TTTW_API.GetGlobalEmotes(var ge: TList); +var + jsonres: string; begin - jsonres:=getTTW('chat/emotes/global',ClientID,false); - ParseEmotes(jsonres, ge); + jsonres := getTTW('chat/emotes/global', ClientID, false); + ParseEmotes(jsonres, ge); end; end. diff --git a/Services/uTTWEventSub.pas b/Services/uTTWEventSub.pas new file mode 100644 index 0000000..7271620 --- /dev/null +++ b/Services/uTTWEventSub.pas @@ -0,0 +1,660 @@ +unit uTTWEventSub; + +interface + +uses + System.SysUtils, System.JSON, System.Types, System.UITypes, System.Classes, + WinInet, ComObj, IdException, + ipwcore, ipwtypes, ipwwsclient, ipwping, idhttp, IdSSLOpenSSL, uRecords, + fmx.Types, System.Net.HttpClient, System.Net.HttpClientComponent; + +type + TNotifyEvent = procedure(s: string) of object; + TGetCustomRewardEvent = procedure(s: TCustomRewardEvent) of object; + TGetFollowEvent = procedure(s: TFollowEvent) of object; + TGetGiftEvent = procedure(s: TGiftEvent) of object; + TGetSubEvent = procedure(s: TSubEvent) of object; + TGetRaidEvent = procedure(s: TRaidEvent) of object; + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; + + TOnStatus = procedure(Sender: TObject; const ConnectionEvent: String; + StatusCode: Integer; const Description: String) of Object; + +type + TTTW_ES = class(TObject) + FTimer: ttimer; + wss: TipwWSClient; + + private + + BroadcasterID: string; + FAccessToken: string; + FClientID: string; + FOnError: TNotifyEvent; + FOnMessage: TNotifyEvent; + FOnSubOk: TNotifyEvent; + FOnRAW: TNotifyEvent; + FOnGetCustomReward: TGetCustomRewardEvent; + FOnFollow: TGetFollowEvent; + FOnGift: TGetGiftEvent; + FOnSub: TGetSubEvent; + FOnLog: TOnLog; + FOnRaid: TGetRaidEvent; + FOnStatus: TOnStatus; + SW: TWelcomMessage; + procedure HandleTimer(Sender: TObject); + procedure ipwWSClient1DataIn(Sender: TObject; DataFormat: Integer; + const Text: string; const TextB: TBytes; EOM, EOL: Boolean); + procedure ipwWSPing(Sender: TObject; const Payload: String; + const PayloadB: TBytes; Response: Boolean); + procedure ipwWSClient1ConnectionStatus(Sender: TObject; + const ConnectionEvent: String; StatusCode: Integer; + const Description: String); + procedure ipwWSClientError(Sender: TObject; ErrorCode: Integer; + const Description: string); + procedure ipwWSClientDisconnected(Sender: TObject; StatusCode: Integer; + const Description: String); + procedure ipwWSClientHeader(Sender: TObject; const Field: String; + const Value: String); + procedure ipwWSClientLog(Sender: TObject; LogLevel: Integer; + const aMessage, aLog: string); + function subscribeTo(const EventType, Version: string; const Condition: string): Boolean; + procedure subscribe(); + // function ParseRewardRedeemed(const AJsonString: string): TRewardRedeemed; + procedure EventMSG(const AText: string); + function ParseWelcomMessage(const JSONString: string): TWelcomMessage; + function ParseCustomRewardEvent(const JSONString: string) + : TCustomRewardEvent; + function ParseFollowEvent(const JSONString: string): TFollowEvent; + function ParseSubEvent(const JSONString: string): TSubEvent; + function ParseGiftEvent(const JSONString: string): TGiftEvent; + function ParseRaidEvent(const JSONString: string): TRaidEvent; + procedure toLog(aLevel: integer; aMethod: string; aMessage: string); + function ParseMetadata(const JSONString: string): TMetadata; + public + + constructor Create(Sender: TObject; + aTokenWS, aClientID, aBroadcasterID: string); + destructor Destroy; override; + procedure Connect(); + procedure Disconnect; + + property OnMessage: TNotifyEvent read FOnMessage write FOnMessage; + property OnError: TNotifyEvent read FOnError write FOnError; + property OnSubOk: TNotifyEvent read FOnSubOk write FOnSubOk; + property OnRAW: TNotifyEvent read FOnRAW write FOnRAW; + property OnGetCustomReward: TGetCustomRewardEvent read FOnGetCustomReward + write FOnGetCustomReward; + property OnStatus: TOnStatus read FOnStatus write FOnStatus; + property OnFollow: TGetFollowEvent read FOnFollow write FOnFollow; + property OnSub: TGetSubEvent read FOnSub write FOnSub; + property OnGift: TGetGiftEvent read FOnGift write FOnGift; + property OnRaid: TGetRaidEvent read FOnRaid write FOnRaid; + property OnLog: TOnLog read FOnLog write FOnLog; + end; + +implementation + +uses ugeneral; + +function SafeGetObj(Parent: TJSONObject; const Name: string): TJSONObject; +begin + Result := Parent.GetValue(Name); + if not Assigned(Result) then + raise Exception.CreateFmt('JSON object "%s" not found', [Name]); +end; + +function SafeGetStr(Parent: TJSONObject; const Name: string): string; +var + V: TJSONValue; +begin + V := Parent.GetValue(Name); + if Assigned(V) then + Result := V.Value + else + Result := ''; +end; + +function SafeGetInt(Parent: TJSONObject; const Name: string): Integer; +var + V: TJSONValue; +begin + V := Parent.GetValue(Name); + if Assigned(V) then + Result := StrToIntDef(V.Value, 0) + else + Result := 0; +end; + +function SafeGetBool(Parent: TJSONObject; const Name: string): Boolean; +var + V: TJSONValue; +begin + V := Parent.GetValue(Name); + if Assigned(V) then + Result := SameText(V.Value, 'true') + else + Result := False; +end; + +procedure TTTW_ES.toLog(aLevel: integer; aMethod: string; aMessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uTTWEvenSub', aMethod, aMessage, aLevel); +end; + + + +procedure TTTW_ES.Connect; +begin + + if wss.Connected then + wss.Disconnect; + + try + wss.ConnectTo('wss://eventsub.wss.twitch.tv/ws?keepalive_timeout_seconds=60'); + toLog(0, 'Connect', 'Подключение к WebSocket выполнено'); + FTimer.Enabled := True; + except + on E: Exception do + toLog(2, 'Connect', E.Message); + end; +end; + +constructor TTTW_ES.Create(Sender: TObject; + aTokenWS, aClientID, aBroadcasterID: string); +begin + FAccessToken := aTokenWS; + FClientID := aClientID; + BroadcasterID := aBroadcasterID; + + wss := TipwWSClient.Create(nil); + wss.Timeout := 30; + wss.OnPing := ipwWSPing; + wss.OnDataIn := ipwWSClient1DataIn; + wss.OnConnectionStatus := ipwWSClient1ConnectionStatus; + wss.OnError := ipwWSClientError; + wss.OnLog := ipwWSClientLog; + wss.OnDisconnected := ipwWSClientDisconnected; + wss.OnHeader := ipwWSClientHeader; + + FTimer := TTimer.Create(nil); + FTimer.Interval := 9000; + FTimer.OnTimer := HandleTimer; + FTimer.Enabled := False; + + toLog(0, 'Create', 'Инициализация EventSub'); +end; + +destructor TTTW_ES.Destroy; +begin + toLog(0, 'Destroy', 'Завершение работы EventSub'); + try + if Assigned(FTimer) then + FreeAndNil(FTimer); + + if Assigned(wss) then + begin + if wss.Connected then + Disconnect; + FreeAndNil(wss); + end; + finally + inherited Destroy; + end; +end; + +procedure TTTW_ES.Disconnect; +begin + toLog(1, 'Disconnect', 'Отключение от WebSocket'); + try + if wss.Connected then + wss.Disconnect; + except + on E: Exception do + toLog(2, 'Disconnect', E.ClassName + ': ' + E.Message); + end; +end; + +procedure TTTW_ES.EventMSG(const AText: string); +var + md: TMetadata; +begin + if Assigned(FOnRAW) then + FOnRAW(AText); + + md := ParseMetadata(AText); + toLog(0, 'EventMSG', 'Тип сообщения: ' + md.message_type + ', Тип подписки: ' + md.subscription_type); + + if md.message_type = 'session_welcome' then + begin + toLog(0, 'EventMSG', 'Получен session_welcome'); + SW := ParseWelcomMessage(AText); + if Assigned(FOnMessage) then + FOnMessage('Welcome message'); + subscribe; + end + else if md.message_type = 'notification' then + begin + if md.subscription_type = 'channel.channel_points_custom_reward_redemption.add' then + if Assigned(FOnGetCustomReward) then + FOnGetCustomReward(ParseCustomRewardEvent(AText)); + + if md.subscription_type = 'channel.follow' then + if Assigned(FOnFollow) then + FOnFollow(ParseFollowEvent(AText)); + + // Тут аналогично можно вызывать ParseSubEvent, ParseGiftEvent, ParseRaidEvent + end + else if md.message_type = 'session_keepalive' then + toLog(3, 'EventMSG', 'Получен keepalive'); + +end; + +procedure TTTW_ES.HandleTimer(Sender: TObject); +begin +if wss.Connected then + begin + toLog(3, 'HandleTimer', 'Отправка ping'); + wss.Ping; + end; +end; + +procedure TTTW_ES.ipwWSClient1ConnectionStatus(Sender: TObject; + const ConnectionEvent: String; StatusCode: Integer; + const Description: String); +begin + toLog(0, 'ConnectionStatus', + Format('%s | %d | %s', [ConnectionEvent, StatusCode, Description])); + if Assigned(FOnStatus) then + FOnStatus(Sender, ConnectionEvent, StatusCode, Description); +end; + +procedure TTTW_ES.ipwWSClient1DataIn(Sender: TObject; DataFormat: Integer; + const Text: string; const TextB: TBytes; EOM, EOL: Boolean); +begin +toLog(3, 'ipwWSClient1DataIn', Text); + EventMSG(Text); +end; + +procedure TTTW_ES.ipwWSClientDisconnected(Sender: TObject; StatusCode: Integer; + const Description: String); +begin + toLog(1, 'ipwWSClientDisconnected', Description); +end; + +procedure TTTW_ES.ipwWSClientError(Sender: TObject; ErrorCode: Integer; + const Description: string); +begin + toLog(2, 'ipwWSClientError', Format('Код: %d | %s', [ErrorCode, Description])); + if Assigned(FOnError) then + FOnError(Description); +end; + +procedure TTTW_ES.ipwWSClientHeader(Sender: TObject; + const Field, Value: String); +begin + // toLog(3, 'ipwWSClientHeader', + // 'Field: ' + Field + ' | Value: ' + Value); + +end; + +procedure TTTW_ES.ipwWSClientLog(Sender: TObject; LogLevel: Integer; + const aMessage, aLog: string); +begin + // toLog(3, 'ipwWSClientLog', 'Level: ' + IntToStr(LogLevel) + // + ' | ' + aMessage + ' | ' + aLog); + // form1.log(1, 'ipwWSClientLog', 'Level: ' + inttostr(LogLevel) + ' Message: ' + + // aMessage + ' Log: ' + aLog); + +end; + +procedure TTTW_ES.ipwWSPing(Sender: TObject; const Payload: String; + const PayloadB: TBytes; Response: Boolean); +begin + toLog(3, 'ipwWSPing', 'PING ' + Payload); +end; + +function TTTW_ES.ParseMetadata(const JSONString: string): TMetadata; +var + Root, Metadata: TJSONObject; +begin + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Metadata := SafeGetObj(Root, 'metadata'); + Result.message_id := SafeGetStr(Metadata, 'message_id'); + Result.message_type := SafeGetStr(Metadata, 'message_type'); + Result.message_timestamp := SafeGetStr(Metadata, 'message_timestamp'); + Result.subscription_type := SafeGetStr(Metadata, 'subscription_type'); + finally + Root.Free; + end; +end; + +function TTTW_ES.ParseWelcomMessage(const JSONString: string): TWelcomMessage; +var + Root, Payload, Session: TJSONObject; +begin + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Session := SafeGetObj(Payload, 'session'); + Result.Payload.session.id := SafeGetStr(Session, 'id'); + Result.Payload.session.status := SafeGetStr(Session, 'status'); + Result.Payload.session.connected_at := SafeGetStr(Session, 'connected_at'); + Result.Payload.session.keepalive_timeout_seconds := SafeGetInt(Session, 'keepalive_timeout_seconds'); + Result.Payload.session.reconnect_url := SafeGetStr(Session, 'reconnect_url'); + finally + Root.Free; + end; +end; + +function TTTW_ES.ParseCustomRewardEvent(const JSONString: string) + : TCustomRewardEvent; +var + Root, Payload, Subscription, mCondition, mTransport, Event, mReward: TJSONObject; +begin + toLog(3, 'ParseCustomRewardEvent', 'Начало парсинга награды'); + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Subscription := SafeGetObj(Payload, 'subscription'); + with Result.Subscription do + begin + id := SafeGetStr(Subscription, 'id'); + subscription_type := SafeGetStr(Subscription, 'type'); + version := SafeGetStr(Subscription, 'version'); + status := SafeGetStr(Subscription, 'status'); + cost := SafeGetInt(Subscription, 'cost'); + created_at := SafeGetStr(Subscription, 'created_at'); + mCondition := SafeGetObj(Subscription, 'condition'); + condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id'); + condition.reward_id := SafeGetStr(mCondition, 'reward_id'); + mTransport := SafeGetObj(Subscription, 'transport'); + transport.method := SafeGetStr(mTransport, 'method'); + end; + + Event := SafeGetObj(Payload, 'event'); + with Result.Event do + begin + id := SafeGetStr(Event, 'id'); + broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id'); + broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login'); + broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name'); + user_id := SafeGetStr(Event, 'user_id'); + user_login := SafeGetStr(Event, 'user_login'); + user_name := SafeGetStr(Event, 'user_name'); + user_input := SafeGetStr(Event, 'user_input'); + mReward := SafeGetObj(Event, 'reward'); + revard.id := SafeGetStr(mReward, 'id'); + revard.title := SafeGetStr(mReward, 'title'); + revard.cost := SafeGetInt(mReward, 'cost'); + revard.prompt := SafeGetStr(mReward, 'prompt'); + end; + finally + Root.Free; + end; +end; + +function TTTW_ES.ParseFollowEvent(const JSONString: string): TFollowEvent; +var + Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject; +begin + toLog(3, 'ParseFollowEvent', 'Парсинг подписки'); + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Subscription := SafeGetObj(Payload, 'subscription'); + with Result.Subscription do + begin + id := SafeGetStr(Subscription, 'id'); + subscription_type := SafeGetStr(Subscription, 'type'); + version := SafeGetStr(Subscription, 'version'); + status := SafeGetStr(Subscription, 'status'); + cost := SafeGetInt(Subscription, 'cost'); + created_at := SafeGetStr(Subscription, 'created_at'); + mCondition := SafeGetObj(Subscription, 'condition'); + condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id'); + mTransport := SafeGetObj(Subscription, 'transport'); + transport.method := SafeGetStr(mTransport, 'method'); + end; + + Event := SafeGetObj(Payload, 'event'); + with Result.Event do + begin + broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id'); + broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login'); + broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name'); + user_id := SafeGetStr(Event, 'user_id'); + user_login := SafeGetStr(Event, 'user_login'); + user_name := SafeGetStr(Event, 'user_name'); + followed_at := SafeGetStr(Event, 'followed_at'); + end; + finally + Root.Free; + end; +end; + +function TTTW_ES.ParseGiftEvent(const JSONString: string): TGiftEvent; +var + Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject; +begin + toLog(3, 'ParseGiftEvent', 'Парсинг подарочной подписки'); + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Subscription := SafeGetObj(Payload, 'subscription'); + with Result.Subscription do + begin + id := SafeGetStr(Subscription, 'id'); + subscription_type := SafeGetStr(Subscription, 'type'); + version := SafeGetStr(Subscription, 'version'); + status := SafeGetStr(Subscription, 'status'); + cost := SafeGetInt(Subscription, 'cost'); + created_at := SafeGetStr(Subscription, 'created_at'); + mCondition := SafeGetObj(Subscription, 'condition'); + condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id'); + mTransport := SafeGetObj(Subscription, 'transport'); + transport.method := SafeGetStr(mTransport, 'method'); + end; + + Event := SafeGetObj(Payload, 'event'); + with Result.Event do + begin + broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id'); + broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login'); + broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name'); + user_id := SafeGetStr(Event, 'user_id'); + user_login := SafeGetStr(Event, 'user_login'); + user_name := SafeGetStr(Event, 'user_name'); + total := SafeGetInt(Event, 'total'); + tier := SafeGetStr(Event, 'tier'); + cumulative_total := SafeGetInt(Event, 'cumulative_total'); + is_anonymous := SafeGetBool(Event, 'anonymous'); + end; + finally + Root.Free; + end; +end; + + +function TTTW_ES.ParseRaidEvent(const JSONString: string): TRaidEvent; +var + Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject; +begin + toLog(3, 'ParseRaidEvent', 'Парсинг рейда'); + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Subscription := SafeGetObj(Payload, 'subscription'); + with Result.Subscription do + begin + id := SafeGetStr(Subscription, 'id'); + subscription_type := SafeGetStr(Subscription, 'type'); + version := SafeGetStr(Subscription, 'version'); + status := SafeGetStr(Subscription, 'status'); + cost := SafeGetInt(Subscription, 'cost'); + created_at := SafeGetStr(Subscription, 'created_at'); + mCondition := SafeGetObj(Subscription, 'condition'); + condition.broadcaster_user_id := SafeGetStr(mCondition, 'to_broadcaster_user_id'); + mTransport := SafeGetObj(Subscription, 'transport'); + transport.method := SafeGetStr(mTransport, 'method'); + end; + + Event := SafeGetObj(Payload, 'event'); + with Result.Event do + begin + from_broadcaster_user_id := SafeGetStr(Event, 'from_broadcaster_user_id'); + from_broadcaster_user_login := SafeGetStr(Event, 'from_broadcaster_user_login'); + from_broadcaster_user_name := SafeGetStr(Event, 'from_broadcaster_user_name'); + to_broadcaster_user_id := SafeGetStr(Event, 'to_broadcaster_user_id'); + to_broadcaster_user_login := SafeGetStr(Event, 'to_broadcaster_user_login'); + to_broadcaster_user_name := SafeGetStr(Event, 'to_broadcaster_user_name'); + viewers := SafeGetInt(Event, 'viewers'); + end; + finally + Root.Free; + end; +end; + +function TTTW_ES.ParseSubEvent(const JSONString: string): TSubEvent; +var + Root, Payload, Subscription, mCondition, mTransport, Event: TJSONObject; +begin + toLog(3, 'ParseSubEvent', 'Парсинг подписки'); + Root := TJSONObject.ParseJSONValue(JSONString) as TJSONObject; + if not Assigned(Root) then + raise Exception.Create('Invalid JSON'); + try + Payload := SafeGetObj(Root, 'payload'); + Subscription := SafeGetObj(Payload, 'subscription'); + with Result.Subscription do + begin + id := SafeGetStr(Subscription, 'id'); + subscription_type := SafeGetStr(Subscription, 'type'); + version := SafeGetStr(Subscription, 'version'); + status := SafeGetStr(Subscription, 'status'); + cost := SafeGetInt(Subscription, 'cost'); + created_at := SafeGetStr(Subscription, 'created_at'); + mCondition := SafeGetObj(Subscription, 'condition'); + condition.broadcaster_user_id := SafeGetStr(mCondition, 'broadcaster_user_id'); + mTransport := SafeGetObj(Subscription, 'transport'); + transport.method := SafeGetStr(mTransport, 'method'); + end; + + Event := SafeGetObj(Payload, 'event'); + with Result.Event do + begin + broadcaster_user_id := SafeGetStr(Event, 'broadcaster_user_id'); + broadcaster_user_login := SafeGetStr(Event, 'broadcaster_user_login'); + broadcaster_user_name := SafeGetStr(Event, 'broadcaster_user_name'); + user_id := SafeGetStr(Event, 'user_id'); + user_login := SafeGetStr(Event, 'user_login'); + user_name := SafeGetStr(Event, 'user_name'); + tier := SafeGetStr(Event, 'tier'); + is_gift := SafeGetBool(Event, 'is_gift'); + end; + finally + Root.Free; + end; +end; + +function TTTW_ES.subscribeTo(const EventType, Version: string; const Condition: string): Boolean; +var + Json: TStringStream; + Resp: string; + HTTP: TNetHTTPClient; +begin + Result := False; + toLog(0, 'subscribeTo', 'Подписка на ' + EventType); + + HTTP := TNetHTTPClient.Create(nil); + try + HTTP.ContentType := 'application/json'; + HTTP.CustomHeaders['Authorization'] := 'Bearer ' + FAccessToken; + HTTP.CustomHeaders['Client-Id'] := FClientID; + + Json := TStringStream.Create( + TJSONObject.Create + .AddPair('type', EventType) + .AddPair('version', Version) + .AddPair('condition', TJSONObject.ParseJSONValue(Condition) as TJSONObject) + .AddPair('transport', + TJSONObject.Create + .AddPair('method', 'websocket') + .AddPair('session_id', SW.Payload.session.id) + ).ToJSON, TEncoding.UTF8 + ); + try + Resp := HTTP.Post('https://api.twitch.tv/helix/eventsub/subscriptions', Json).ContentAsString(); + toLog(3, 'subscribeTo', 'Ответ Twitch: ' + Resp); + + if Pos('"status":"enabled"', Resp) > 0 then + begin + toLog(0, 'subscribeTo', 'Подписка успешна'); + Result := True; + end + else + toLog(1, 'subscribeTo', 'Подписка не подтверждена: ' + Resp); + finally + Json.Free; + end; + except + on E: Exception do + toLog(2, 'subscribeTo', 'Ошибка подписки: ' + E.Message); + end; +end; + +procedure TTTW_ES.subscribe; +begin + // channel.channel_points_custom_reward.add (1) + // channel.follow (2) moderator:read:followers + // channel.subscribe (1) channel:read:subscriptions + // channel.subscription.gift (1) channel:read:subscriptions + // channel.raid (1) + if subscribeTo('channel.channel_points_custom_reward_redemption.add', '1', + '{"broadcaster_user_id":"' + BroadcasterID + '"}') then + toLog(0, 'subscribe', + 'channel.channel_points_custom_reward_redemption.add OK') + else + toLog(2, 'subscribe', + 'channel.channel_points_custom_reward_redemption.add'); + + if subscribeTo('channel.raid', '1', '{"to_broadcaster_user_id":"' + + BroadcasterID + '"}') then + toLog(0, 'subscribe', 'channel.raid OK') + else + toLog(2, 'subscribe', 'channel.raid'); + + if subscribeTo('channel.follow', '2', '{"broadcaster_user_id":"' + + BroadcasterID + '","moderator_user_id":"' + BroadcasterID + '"}') then + toLog(0, 'subscribe', 'channel.follow OK') + else + toLog(2, 'subscribe', 'channel.follow'); + + if subscribeTo('channel.subscribe', '1', '{"broadcaster_user_id":"' + + BroadcasterID + '"}') then + toLog(0, 'subscribe', 'channel.subscribe OK') + else + toLog(2, 'subscribe', 'channel.subscribe'); + + if subscribeTo('channel.subscription.gift', '1', '{"broadcaster_user_id":"' + + BroadcasterID + '"}') then + toLog(0, 'subscribe', 'channel.subscription.gift OK') + else + toLog(2, 'subscribe', 'channel.subscription.gift'); +end; + +end. diff --git a/Services/uTTWIRC.pas b/Services/uTTWIRC.pas new file mode 100644 index 0000000..04c9920 --- /dev/null +++ b/Services/uTTWIRC.pas @@ -0,0 +1,364 @@ +п»їunit uTTWIRC; + +interface + +uses + System.Classes, System.SysUtils, IdIRC, IdSSLOpenSSL, IdContext, + FMX.Forms, IdGlobal, IdComponent, System.StrUtils, uRecords; + + + +type + TNotifyEvent = procedure(s: string) of object; + TJoinEvent = procedure(aNick: string) of object; + TMyStatusEvent = procedure(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string) of object; + tOnMessageRecord = procedure(aRecord: TTwitchChatMessage) of object; + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; + + TTTW = class + private + ws: TIdIRC; + ssl: TIdSSLIOHandlerSocketOpenSSL; + FOnLog: TOnLog; + FOnStatus: TMyStatusEvent; + FOnDisConnect: TNotifyEvent; + FOnJoin: TJoinEvent; + FOnMessage: TNotifyEvent; + FOnMessageRecord: tOnMessageRecord; + channel_name: string; + room_id: string; + channel_id: string; + procedure wsConnected(Sender: TObject); + procedure wsStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); + procedure wsDisconnected(Sender: TObject); + procedure wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string); + procedure Join(ASender: TIdContext; const ANickname, AHost, AChannel: string); + procedure se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String); + procedure RAW(text: string); + procedure toLog(aLevel: integer; aMethod: string; aMessage: string); + procedure toParse(t: string); + public + constructor Create(Sender: TObject); + destructor Destroy; override; + procedure Init(a_oauth, a_channel, a_username: string); + procedure Connect; + procedure Disconnect; + procedure sendMessage(text: string); + function ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage; + function GetRoom_ID: string; + function Pars(T_, text, _T: string): string; + property OnLog: TOnLog read FOnLog write FOnLog; + property OnStatus: TMyStatusEvent read FOnStatus write FOnStatus; + property OnDisConnect: TNotifyEvent read FOnDisConnect write FOnDisConnect; + property OnJoin: TJoinEvent read FOnJoin write FOnJoin; + property OnMessage: TNotifyEvent read FOnMessage write FOnMessage; + property OnMessageRecord: tOnMessageRecord read FOnMessageRecord write FOnMessageRecord; + end; + +implementation + +uses uGeneral; // пїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ Log + +const + LOG_INFO = 0; + LOG_WARNING = 1; + LOG_ERROR = 2; + LOG_DEBUG = 3; + +procedure TTTW.toLog(aLevel: integer; aMethod: string; aMessage: string); +begin + if aLevel < 0 then + aLevel := LOG_INFO + else if aLevel > LOG_DEBUG then + aLevel := LOG_DEBUG; + + if Assigned(FOnLog) then + FOnLog('uTTWIRC', aMethod, aMessage, aLevel); +end; + +constructor TTTW.Create(Sender: TObject); +begin + try + ws := TIdIRC.Create; + ssl := TIdSSLIOHandlerSocketOpenSSL.Create; + ws.IOHandler := ssl; + ws.OnConnected := wsConnected; + ws.OnDisconnected := wsDisconnected; + ws.OnStatus := wsStatus; + ws.OnRaw := wsDataIn; + ws.OnJoin := Join; + ws.OnServerError := se; + except + on E: Exception do + toLog(LOG_ERROR, 'Create', E.Message); + end; +end; + +destructor TTTW.Destroy; +begin + try + if Assigned(ws) then + begin + ws.OnConnected := nil; + ws.OnDisconnected := nil; + ws.OnStatus := nil; + ws.OnRaw := nil; + ws.OnJoin := nil; + ws.OnServerError := nil; + ws.IOHandler := nil; + ws.Free; + end; + if Assigned(ssl) then + ssl.Free; + except + on E: Exception do + ; + end; + inherited; +end; + +function TTTW.ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage; +var + s: string; + LSpacePos: Integer; + LParamStr, LRestStr: string; + LParams: TArray; + I: Integer; + LKeyValue: TArray; + LUsernamePart: string; + LMessagePos: Integer; +begin + Result := Default(TTwitchChatMessage); + s := AMessage; + + // пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ + LSpacePos := Pos(' ', s); + if LSpacePos = 0 then + Exit; + + LParamStr := Copy(s, 1, LSpacePos - 1); + LRestStr := Copy(s, LSpacePos + 1, Length(s) - LSpacePos); + + // пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ + LParams := LParamStr.Split([';']); + for I := 0 to High(LParams) do + begin + LKeyValue := LParams[I].Split(['=']); + if Length(LKeyValue) = 2 then + begin + case AnsiIndexStr(LKeyValue[0], [ + '@badge-info', 'badges', 'client-nonce', 'color', 'display-name', 'emotes', + 'first-msg', 'id', 'mod', 'returning-chatter', 'room-id', 'subscriber', + 'tmi-sent-ts', 'turbo', 'user-id', 'user-type', 'vip' + ]) of + 0: Result.BadgeInfo := LKeyValue[1]; + 1: Result.Badges := LKeyValue[1]; + 2: Result.ClientNonce := LKeyValue[1]; + 3: Result.Color := LKeyValue[1]; + 4: Result.DisplayName := LKeyValue[1]; + 5: Result.Emotes := LKeyValue[1]; + 6: Result.FirstMsg := StrToIntDef(LKeyValue[1], 0); + 7: Result.Id := LKeyValue[1]; + 8: Result.Moder := StrToIntDef(LKeyValue[1], 0); + 9: Result.ReturningChatter := StrToIntDef(LKeyValue[1], 0); + 10: Result.RoomId := LKeyValue[1]; + 11: Result.Subscriber := StrToIntDef(LKeyValue[1], 0); + 12: Result.TmiSentTs := StrToInt64Def(LKeyValue[1], 0); + 13: Result.Turbo := StrToIntDef(LKeyValue[1], 0); + 14: Result.UserId := LKeyValue[1]; + 15: Result.UserType := LKeyValue[1]; + 16: Result.Vip := StrToIntDef(LKeyValue[1], 0); + end; + end; + end; + + if LRestStr.StartsWith(':') then + begin + LUsernamePart := Copy(LRestStr, 1, Pos('!', LRestStr) - 1); + Result.Username := LUsernamePart.Substring(1); + end + else + Result.Username := ''; + + // пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ + LMessagePos := Pos('PRIVMSG #', LRestStr); + if LMessagePos > 0 then + begin + Inc(LMessagePos, Length('PRIVMSG #')); + Result.Channel := Copy(LRestStr, LMessagePos, PosEx(' ', LRestStr, LMessagePos) - LMessagePos); + end + else + Result.Channel := ''; + + // пїЅпїЅпїЅпїЅпїЅпїЅпїЅпїЅ пїЅпїЅпїЅпїЅпїЅ + LMessagePos := Pos(' :', LRestStr); + if LMessagePos > 0 then + Result.Message := Copy(LRestStr, LMessagePos + 2, Length(LRestStr) - LMessagePos - 1) + else + Result.Message := ''; +end; + +procedure TTTW.Init(a_oauth, a_channel, a_username : string); +begin + try + channel_name := a_channel; + ws.Host := 'irc.chat.twitch.tv'; + ws.Port := 6697; + ssl.SSLOptions.SSLVersions := [sslvSSLv23]; + ws.Password := 'oauth:' + a_oauth; + ws.Nickname := a_username; + channel_name := a_channel; + // Token := a_oauth; + except + on E: Exception do + toLog(LOG_ERROR, 'Init', E.Message); + end; +end; + +procedure TTTW.Connect; +begin + try + if not ws.Connected then + begin + ws.Connect; + ws.Raw('CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands'); + ws.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8; + end; + except + on E: Exception do + toLog(LOG_ERROR, 'Connect', E.Message); + end; +end; + +procedure TTTW.Disconnect; +begin + try + if ws.Connected then + begin + ws.Disconnect; + end; + except + on E: Exception do + toLog(LOG_ERROR, 'Disconnect', E.ClassName + ': ' + E.Message); + end; +end; + +function TTTW.GetRoom_ID: string; +begin +result:=room_id; +end; + +procedure TTTW.sendMessage(text: string); +begin + try + ws.Say('#' + channel_name, text); + except + on E: Exception do + toLog(LOG_ERROR, 'sendMessage', E.Message); + end; +end; + +procedure TTTW.RAW(text: string); +begin + try + ws.Raw(text); + except + on E: Exception do + toLog(LOG_ERROR, 'RAW', E.Message); + end; +end; + +procedure TTTW.wsConnected(Sender: TObject); +begin + if Assigned(FOnStatus) then + FOnStatus(ws, TIdStatus.hsConnected, 'Connected to Twitch IRC'); + toLog(LOG_INFO, 'wsConnected', 'Connected to Twitch IRC'); +end; + +procedure TTTW.wsStatus(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string); +begin + if Assigned(FOnStatus) then + FOnStatus(ASender, AStatus, AStatusText); +end; + +procedure TTTW.wsDisconnected(Sender: TObject); +begin + if Assigned(FOnDisConnect) then + FOnDisConnect('Disconnected'); + toLog(LOG_WARNING, 'wsDisconnected', 'Disconnected from Twitch IRC'); +end; + +procedure TTTW.wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string); +begin + + toLog(LOG_DEBUG, 'wsDataIn', AMessage); + + if Pos('CAP * ACK', AMessage) <> 0 then + begin + Sleep(200); + ws.Raw('JOIN #' + channel_name); + end; + + toParse(AMessage); +end; + +procedure TTTW.toParse(t: string); +var + LTwitchChatMessage:tTwitchChatMessage; +begin + try + if (Pos('room-id=', t) <> 0) and (Pos('ROOMSTATE', t) <> 0) then + room_id := Pars('room-id=', t, ';'); + + if Pos('NOTICE * :Login authentication failed', t) <> 0 then + begin + + toLog(2, 'toParse', 'Токен бота просрочен'); + Disconnect; + Exit; + end; + + if Pos('PRIVMSG', t) <> 0 then + begin + LTwitchChatMessage := ParseTwitchChatMessage(t); + if Assigned(FOnMessageRecord) then + FOnMessageRecord(LTwitchChatMessage); + end; + except + on E: Exception do + toLog(2, 'toParse', E.Message); + end; +end; + +procedure TTTW.Join(ASender: TIdContext; const ANickname, AHost, AChannel: string); +begin + if Assigned(FOnJoin) then + FOnJoin(ANickname); + toLog(LOG_INFO, 'Join', ANickname + ' joined ' + AChannel); +end; + +procedure TTTW.se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String); +begin + toLog(LOG_ERROR, 'se', AErrorMessage); +end; + +function TTTW.Pars(T_, text, _T: string): string; +var + a, b: Integer; +begin + Result := ''; + if (T_ = '') or (text = '') or (_T = '') then + Exit; + a := Pos(T_, text); + if a = 0 then + Exit + else + a := a + Length(T_); + text := Copy(text, a, Length(text) - a + 1); + b := Pos(_T, text); + if b > 0 then + Result := Copy(text, 1, b - 1); +end; + +end. + diff --git a/uTWAuth.pas b/Services/uTWAuth.pas similarity index 100% rename from uTWAuth.pas rename to Services/uTWAuth.pas diff --git a/uWSDA.pas b/Services/uWSDA.pas similarity index 100% rename from uWSDA.pas rename to Services/uWSDA.pas diff --git a/Services/uWebServerKandinsky.pas b/Services/uWebServerKandinsky.pas new file mode 100644 index 0000000..0a53778 --- /dev/null +++ b/Services/uWebServerKandinsky.pas @@ -0,0 +1,227 @@ +unit uWebServerKandinsky; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, System.NetEncoding,IdContext, IdCustomHTTPServer, IdHTTPServer, IdGlobal, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Edit, + FMX.Controls.Presentation, FMX.StdCtrls, uKandinskyAPI, FMX.Memo.Types, json, + FMX.ScrollBox, FMX.Memo, System.IOUtils, System.SyncObjs,System.DateUtils; + + type + TKandinsky_Web = class(TObject) + + IdHTTPServer1: TIdHTTPServer; + procedure IdHTTPServer1CommandGet(AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); + private + FCriticalSection: TCriticalSection; + FCurrentImage: string; + FImageTime: TDateTime; + FCurrentText: string; + ka:TFusionBrainAPI; + function GenerateHTML: string; + function GenerateJSON: string; + procedure CleanupOldMessages; + procedure GenerationDone(Sender: TObject; const FileName: string); + procedure GenerationError(Sender: TObject; const ErrorMessage: string); + procedure GenerationUpdate(Sender: TObject; const Message: string); + public + constructor Create(aKey:string; aSecret:string); + destructor Destroy; + procedure generate(prompt:string; aNick:string); + procedure ActiveServer(aEn: boolean); + end; + +implementation + +{ TKandinsky_Web } + +procedure TKandinsky_Web.ActiveServer(aEn: boolean); +begin + IdHTTPServer1.Active :=aEn; +end; + +procedure TKandinsky_Web.CleanupOldMessages; +begin + if FileExists(FCurrentImage) then + begin + DeleteFile(FCurrentImage); + end; +end; + +constructor TKandinsky_Web.Create(aKey:string; aSecret:string); +begin + IdHTTPServer1 := TIdHTTPServer.Create; + IdHTTPServer1.DefaultPort := 8087; + IdHTTPServer1.OnCommandGet := IdHTTPServer1CommandGet; + ka:=TFusionBrainAPI.Create(nil,aKey, aSecret); + ka.OnGenerationDone := GenerationDone; + ka.OnStatusUpdate:=GenerationUpdate; + ka.OnError:=GenerationError; + FCriticalSection:=TCriticalSection.Create; + //flog.toLog(0,'uWebServerKandinsky','Create','Веб сервер запущен'); +end; + +destructor TKandinsky_Web.Destroy; +begin + IdHTTPServer1.Active := False; + FCriticalSection.Free; + CleanupOldMessages; +end; + +procedure TKandinsky_Web.generate(prompt: string; aNick:string); +begin + //flog.toLog(0,'uWebServerKandinsky','generate','Новый запрос на генерацию'); + FCriticalSection.Enter; + try + FCurrentText := aNick; + finally + FCriticalSection.Leave; + end; + ka.StartGeneration(prompt); + //flog.toLog(0,'uWebServerKandinsky','generate','Запрос на генерацию отправлен'); +end; + +function TKandinsky_Web.GenerateHTML: string; +begin + Result := '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
' + + ' ' + // Начальное состояние hidden + ' ' + // Начальное состояние hidden + '
' + + '' + + ''; +end; + +function TKandinsky_Web.GenerateJSON: string; +var + JSONObject: TJSONObject; +begin + JSONObject := TJSONObject.Create; + try + FCriticalSection.Enter; + try + // Изменили условие проверки времени + if FileExists(FCurrentImage) and (SecondsBetween(Now, FImageTime) <= 5) then + begin + JSONObject.AddPair('imageUrl', '/image?' + IntToStr(DateTimeToUnix(FImageTime))); // Используем время генерации + JSONObject.AddPair('text', FCurrentText) + end + else + begin + JSONObject.AddPair('imageUrl', ''); + JSONObject.AddPair('text', ''); + end; + finally + FCriticalSection.Leave; + end; + Result := JSONObject.ToString; + finally + JSONObject.Free; + end; +end; + +procedure TKandinsky_Web.GenerationDone(Sender: TObject; + const FileName: string); +begin + TThread.Queue(nil, procedure + begin + FCriticalSection.Enter; + try + CleanupOldMessages; + FCurrentImage := FileName; + FImageTime := Now; + //flog.toLog(0,'uWebServerKandinsky','GenerationDone','Файл картинки создан'); + finally + FCriticalSection.Leave; + end; + end); +end; + +procedure TKandinsky_Web.GenerationError(Sender: TObject; + const ErrorMessage: string); +begin + //flog.toLog(2,'uWebServerKandinsky','GenerationError',ErrorMessage); +end; + +procedure TKandinsky_Web.GenerationUpdate(Sender: TObject; + const Message: string); +begin + // flog.toLog(0,'uWebServerKandinsky','GenerationUpdate',Message); +end; + +procedure TKandinsky_Web.IdHTTPServer1CommandGet(AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); +var + FilePath: string; +begin + FCriticalSection.Enter; + try + if ARequestInfo.Document = '/' then + begin + AResponseInfo.ContentType := 'text/html'; + AResponseInfo.ContentText := GenerateHTML; + end + else if ARequestInfo.Document = '/image' then + begin + if FileExists(FCurrentImage) and (SecondsBetween(Now, FImageTime) <= 5) then + begin + AResponseInfo.ContentType := 'image/jpeg'; + AResponseInfo.ContentStream := TFileStream.Create(FCurrentImage, fmOpenRead); + end + else + AResponseInfo.ResponseNo := 404; + end + else if ARequestInfo.Document = '/image-data' then + begin + AResponseInfo.ContentType := 'application/json'; + AResponseInfo.ContentText := GenerateJSON; + end + else + AResponseInfo.ResponseNo := 404; + finally + FCriticalSection.Leave; + end; +end; + + +end. diff --git a/SilentPlayer.dpr b/SilentPlayer.dpr new file mode 100644 index 0000000..1ff8de5 --- /dev/null +++ b/SilentPlayer.dpr @@ -0,0 +1,14 @@ +program SilentPlayer; + +uses + System.StartUpCopy, + FMX.Forms, + uSilentPlayer in 'uSilentPlayer.pas' {fPublicPlayer}; + +{$R *.res} + +begin + Application.Initialize; + Application.CreateForm(TfPublicPlayer, fPublicPlayer); + Application.Run; +end. diff --git a/SilentPlayer.dproj b/SilentPlayer.dproj new file mode 100644 index 0000000..1c15e79 --- /dev/null +++ b/SilentPlayer.dproj @@ -0,0 +1,1326 @@ +п»ї + + {EA01050D-5AE0-4600-9AA7-D00DB15E6E0D} + 20.3 + FMX + True + Debug + Win32 + SilentPlayer + 693395 + Application + SilentPlayer.dpr + + + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Base + true + + + true + Cfg_1 + true + true + + + true + Cfg_1 + true + true + + + true + Base + true + + + true + Cfg_2 + true + true + + + true + Cfg_2 + true + true + + + .\$(Platform)\$(Config) + .\$(Platform)\$(Config) + false + false + false + false + false + System;Xml;Data;Datasnap;Web;Soap;$(DCC_Namespace) + true + true + true + true + true + true + true + true + $(BDS)\bin\delphi_PROJECTICON.ico + $(BDS)\bin\delphi_PROJECTICNS.icns + SilentPlayer + + + emshosting;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;ibmonitor;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;ibxbindings;bindcomp;FireDACCommon;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;ibxpress;dsnap;CloudService;DataSnapNativeClient;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35 + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Monochrome.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Foreground.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Background.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplash.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashDark.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31Dark.xml + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + false + true + $(BDS)\bin\Artwork\Android\FM_VectorizedNotificationIcon.xml + activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + emshosting;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;ibmonitor;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;ibxbindings;bindcomp;FireDACCommon;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;ibxpress;dsnap;CloudService;DataSnapNativeClient;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + package=com.embarcadero.$(MSBuildProjectName);label=$(MSBuildProjectName);versionCode=1;versionName=1.0.0;persistent=False;restoreAnyVersion=False;installLocation=auto;largeHeap=False;theme=TitleBar;hardwareAccelerated=true;apiKey=;minSdkVersion=23;targetSdkVersion=35 + Debug + true + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_96x96.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_144x144.png + $(BDS)\bin\Artwork\Android\FM_LauncherIcon_192x192.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_426x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_470x320.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_640x480.png + $(BDS)\bin\Artwork\Android\FM_SplashImage_960x720.png + true + true + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Monochrome.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Foreground.xml + $(BDS)\bin\Artwork\Android\FM_AdaptiveIcon_Background.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplash.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashDark.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31.xml + $(BDS)\bin\Artwork\Android\FM_VectorizedSplashV31Dark.xml + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_24x24.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_36x36.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_48x48.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_72x72.png + $(BDS)\bin\Artwork\Android\FM_NotificationIcon_96x96.png + false + true + $(BDS)\bin\Artwork\Android\FM_VectorizedNotificationIcon.xml + activity-1.7.2.dex.jar;annotation-experimental-1.4.1.dex.jar;annotation-jvm-1.8.1.dex.jar;annotations-13.0.dex.jar;appcompat-1.2.0.dex.jar;appcompat-resources-1.2.0.dex.jar;billing-7.1.1.dex.jar;biometric-1.1.0.dex.jar;browser-1.4.0.dex.jar;cloud-messaging.dex.jar;collection-jvm-1.4.2.dex.jar;concurrent-futures-1.1.0.dex.jar;core-1.15.0.dex.jar;core-common-2.2.0.dex.jar;core-ktx-1.15.0.dex.jar;core-runtime-2.2.0.dex.jar;cursoradapter-1.0.0.dex.jar;customview-1.0.0.dex.jar;documentfile-1.0.0.dex.jar;drawerlayout-1.0.0.dex.jar;error_prone_annotations-2.9.0.dex.jar;exifinterface-1.3.6.dex.jar;firebase-annotations-16.2.0.dex.jar;firebase-common-20.3.1.dex.jar;firebase-components-17.1.0.dex.jar;firebase-datatransport-18.1.7.dex.jar;firebase-encoders-17.0.0.dex.jar;firebase-encoders-json-18.0.0.dex.jar;firebase-encoders-proto-16.0.0.dex.jar;firebase-iid-interop-17.1.0.dex.jar;firebase-installations-17.1.3.dex.jar;firebase-installations-interop-17.1.0.dex.jar;firebase-measurement-connector-19.0.0.dex.jar;firebase-messaging-23.1.2.dex.jar;fmx.dex.jar;fragment-1.2.5.dex.jar;google-play-licensing.dex.jar;interpolator-1.0.0.dex.jar;javax.inject-1.dex.jar;kotlin-stdlib-1.8.22.dex.jar;kotlin-stdlib-common-1.8.22.dex.jar;kotlin-stdlib-jdk7-1.8.22.dex.jar;kotlin-stdlib-jdk8-1.8.22.dex.jar;kotlinx-coroutines-android-1.6.4.dex.jar;kotlinx-coroutines-core-jvm-1.6.4.dex.jar;legacy-support-core-utils-1.0.0.dex.jar;lifecycle-common-2.6.2.dex.jar;lifecycle-livedata-2.6.2.dex.jar;lifecycle-livedata-core-2.6.2.dex.jar;lifecycle-runtime-2.6.2.dex.jar;lifecycle-service-2.6.2.dex.jar;lifecycle-viewmodel-2.6.2.dex.jar;lifecycle-viewmodel-savedstate-2.6.2.dex.jar;listenablefuture-1.0.dex.jar;loader-1.0.0.dex.jar;localbroadcastmanager-1.0.0.dex.jar;okio-jvm-3.4.0.dex.jar;play-services-ads-22.2.0.dex.jar;play-services-ads-base-22.2.0.dex.jar;play-services-ads-identifier-18.0.0.dex.jar;play-services-ads-lite-22.2.0.dex.jar;play-services-appset-16.0.1.dex.jar;play-services-base-18.5.0.dex.jar;play-services-basement-18.4.0.dex.jar;play-services-cloud-messaging-17.0.1.dex.jar;play-services-location-21.0.1.dex.jar;play-services-maps-18.1.0.dex.jar;play-services-measurement-base-20.1.2.dex.jar;play-services-measurement-sdk-api-20.1.2.dex.jar;play-services-stats-17.0.2.dex.jar;play-services-tasks-18.2.0.dex.jar;print-1.0.0.dex.jar;profileinstaller-1.3.0.dex.jar;room-common-2.2.5.dex.jar;room-runtime-2.2.5.dex.jar;savedstate-1.2.1.dex.jar;sqlite-2.1.0.dex.jar;sqlite-framework-2.1.0.dex.jar;startup-runtime-1.1.1.dex.jar;tracing-1.2.0.dex.jar;transport-api-3.0.0.dex.jar;transport-backend-cct-3.1.8.dex.jar;transport-runtime-3.1.8.dex.jar;user-messaging-platform-2.0.0.dex.jar;vectordrawable-1.1.0.dex.jar;vectordrawable-animated-1.1.0.dex.jar;versionedparcelable-1.1.1.dex.jar;viewpager-1.0.0.dex.jar;work-runtime-2.7.0.dex.jar + + + emshosting;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;ibmonitor;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;ibxbindings;bindcomp;FireDACCommon;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;ibxpress;dsnap;CloudService;DataSnapNativeClient;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + Debug + $(MSBuildProjectName) + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPad\FM_NotificationIcon_40x40.png + + + emshosting;fmx;DbxCommonDriver;bindengine;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;ibmonitor;FireDACSqliteDriver;DbxClientDriver;soapmidas;fmxFireDAC;dbexpress;inet;DataSnapCommon;fmxase;dbrtl;FireDACDBXDriver;CustomIPTransport;DBXInterBaseDriver;IndySystem;ibxbindings;bindcomp;FireDACCommon;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDAC;FireDACDSDriver;xmlrtl;tethering;ibxpress;dsnap;CloudService;DataSnapNativeClient;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + CFBundleName=$(MSBuildProjectName);CFBundleDevelopmentRegion=en;CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleInfoDictionaryVersion=7.1;CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;LSRequiresIPhoneOS=true;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);UIDeviceFamily=iPhone & iPad;NSLocationAlwaysUsageDescription=The reason for accessing the location information of the user;NSLocationWhenInUseUsageDescription=The reason for accessing the location information of the user;NSLocationAlwaysAndWhenInUseUsageDescription=The reason for accessing the location information of the user;UIBackgroundModes=;NSContactsUsageDescription=The reason for accessing the contacts;NSPhotoLibraryUsageDescription=The reason for accessing the photo library;NSPhotoLibraryAddUsageDescription=The reason for adding to the photo library;NSCameraUsageDescription=The reason for accessing the camera;NSFaceIDUsageDescription=The reason for accessing the face id;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSSiriUsageDescription=The reason for accessing Siri;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing bluetooth;NSBluetoothPeripheralUsageDescription=The reason for accessing bluetooth peripherals;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSMotionUsageDescription=The reason for accessing the accelerometer;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers + iPhoneAndiPad + true + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_1024x1024.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_ApplicationIcon_180x180.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImage_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_LaunchImageDark_3x.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SpotlightSearchIcon_120x120.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_SettingIcon_87x87.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_40x40.png + $(BDS)\bin\Artwork\iOS\iPhone\FM_NotificationIcon_60x60.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_152x152.png + $(BDS)\bin\Artwork\iOS\iPad\FM_ApplicationIcon_167x167.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImage_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_LaunchImageDark_2x.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SpotlightSearchIcon_80x80.png + $(BDS)\bin\Artwork\iOS\iPad\FM_SettingIcon_58x58.png + $(BDS)\bin\Artwork\iOS\iPad\FM_NotificationIcon_40x40.png + + + DataSnapServer;emshosting;fmx;DbxCommonDriver;bindengine;FireDACCommonODBC;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;emsedge;inetdb;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;dbexpress;FireDACInfxDriver;inet;DataSnapCommon;dbrtl;FireDACOracleDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;dsnapxml;DataSnapClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;xmlrtl;dsnap;CloudService;FireDACDb2Driver;DataSnapNativeClient;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + + + DataSnapServer;emshosting;fmx;DbxCommonDriver;bindengine;FireDACCommonODBC;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;fmxFireDAC;dbexpress;DBXMySQLDriver;inet;DataSnapCommon;fmxase;dbrtl;FireDACOracleDriver;FireDACDBXDriver;fmxdae;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;ibxbindings;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;dsnap;DBXSybaseASADriver;CloudService;DBXOracleDriver;DBXInformixDriver;DataSnapNativeClient;fmxobj;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface + Debug + true + + + DataSnapServer;emshosting;fmx;DbxCommonDriver;bindengine;FireDACCommonODBC;IndyIPCommon;emsclient;FireDACCommonDriver;IndyProtocols;dbxcds;IndyIPClient;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;soapmidas;fmxFireDAC;dbexpress;DBXMySQLDriver;inet;DataSnapCommon;fmxase;dbrtl;FireDACOracleDriver;FireDACDBXDriver;fmxdae;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DBXInterBaseDriver;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;ibxbindings;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;RESTComponents;DBXSqliteDriver;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;dsnap;DBXSybaseASADriver;CloudService;DBXOracleDriver;DBXInformixDriver;DataSnapNativeClient;fmxobj;FMXTee;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + CFBundleName=$(MSBuildProjectName);CFBundleDisplayName=$(MSBuildProjectName);CFBundleIdentifier=$(MSBuildProjectName);CFBundleVersion=1.0.0;CFBundleShortVersionString=1.0.0;CFBundlePackageType=APPL;CFBundleSignature=????;CFBundleAllowMixedLocalizations=YES;CFBundleExecutable=$(MSBuildProjectName);NSHighResolutionCapable=true;LSApplicationCategoryType=public.app-category.utilities;NSLocationUsageDescription=The reason for accessing the location information of the user;NSContactsUsageDescription=The reason for accessing the contacts;NSCalendarsUsageDescription=The reason for accessing the calendar data;NSRemindersUsageDescription=The reason for accessing the reminders;NSCameraUsageDescription=The reason for accessing the camera;NSMicrophoneUsageDescription=The reason for accessing the microphone;NSMotionUsageDescription=The reason for accessing the accelerometer;NSDesktopFolderUsageDescription=The reason for accessing the Desktop folder;NSDocumentsFolderUsageDescription=The reason for accessing the Documents folder;NSDownloadsFolderUsageDescription=The reason for accessing the Downloads folder;NSNetworkVolumesUsageDescription=The reason for accessing files on a network volume;NSRemovableVolumesUsageDescription=The reason for accessing files on a removable volume;NSSpeechRecognitionUsageDescription=The reason for requesting to send user data to Apple's speech recognition servers;ITSAppUsesNonExemptEncryption=false;NSBluetoothAlwaysUsageDescription=The reason for accessing the Bluetooth interface + Debug + true + + + DataSnapServer;vclwinx;emshosting;fmx;DbxCommonDriver;vclie;bindengine;VCLRESTComponents;FireDACCommonODBC;DBXMSSQLDriver;IndyIPCommon;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;Skia.Package.RTL;dbxcds;vcledge;IndyIPClient;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;fmxase;vcltouch;DBXOdbcDriver;dbrtl;FireDACOracleDriver;FireDACDBXDriver;Skia.Package.FMX;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;DBXInterBaseDriver;vcldsnap;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;ibxbindings;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;adortl;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;dsnapcon;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;bindcompvcl;dsnap;DBXSybaseASADriver;CloudService;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;vclib;DataSnapNativeClient;bindcompvclsmp;fmxobj;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;Bde;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DataSnapServer;vclwinx;emshosting;fmx;DbxCommonDriver;vclie;bindengine;VCLRESTComponents;FireDACCommonODBC;DBXMSSQLDriver;IndyIPCommon;emsclient;FireDACCommonDriver;appanalytics;IndyProtocols;vclx;dbxcds;vcledge;IndyIPClient;bindcompvclwinx;FmxTeeUI;emsedge;bindcompfmx;DBXFirebirdDriver;inetdb;ibmonitor;FireDACSqliteDriver;DbxClientDriver;FireDACASADriver;Tee;soapmidas;vclactnband;TeeUI;fmxFireDAC;dbexpress;FireDACInfxDriver;DBXMySQLDriver;VclSmp;inet;DataSnapCommon;fmxase;vcltouch;DBXOdbcDriver;dbrtl;FireDACOracleDriver;FireDACDBXDriver;fmxdae;TeeDB;FireDACMSAccDriver;CustomIPTransport;FireDACMSSQLDriver;DataSnapIndy10ServerTransport;DataSnapConnectors;DBXInterBaseDriver;vcldsnap;FireDACMongoDBDriver;IndySystem;FireDACTDataDriver;Skia.Package.VCL;vcldb;ibxbindings;vclFireDAC;bindcomp;FireDACCommon;DataSnapServerMidas;FireDACODBCDriver;emsserverresource;inetstn;IndyCore;RESTBackendComponents;bindcompdbx;rtl;FireDACMySQLDriver;FireDACADSDriver;RESTComponents;DBXSqliteDriver;vcl;adortl;dsnapxml;IndyIPServer;DataSnapClient;DataSnapProviderClient;dsnapcon;DBXSybaseASEDriver;DBXDb2Driver;vclimg;DataSnapFireDAC;emsclientfiredac;FireDACPgDriver;FireDAC;FireDACDSDriver;inetdbxpress;xmlrtl;tethering;ibxpress;bindcompvcl;dsnap;DBXSybaseASADriver;CloudService;DBXOracleDriver;FireDACDb2Driver;DBXInformixDriver;vclib;DataSnapNativeClient;bindcompvclsmp;fmxobj;FMXTee;DatasnapConnectorsFreePascal;soaprtl;soapserver;FireDACIBDriver;$(DCC_UsePackage) + Winapi;System.Win;Data.Win;Datasnap.Win;Web.Win;Soap.Win;Xml.Win;$(DCC_Namespace) + Debug + true + CompanyName=;FileDescription=$(MSBuildProjectName);FileVersion=1.0.0.0;InternalName=;LegalCopyright=;LegalTrademarks=;OriginalFilename=;ProgramID=com.embarcadero.$(MSBuildProjectName);ProductName=$(MSBuildProjectName);ProductVersion=1.0.0.0;Comments= + 1033 + $(BDS)\bin\default_app.manifest + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_44.png + $(BDS)\bin\Artwork\Windows\UWP\delphi_UwpDefault_150.png + + + DEBUG;$(DCC_Define) + true + false + true + true + true + true + true + + + false + PerMonitorV2 + + + PerMonitorV2 + + + false + RELEASE;$(DCC_Define) + 0 + 0 + + + PerMonitorV2 + + + PerMonitorV2 + + + + MainSource + + +
fPublicPlayer
+ fmx +
+ + Base + + + Cfg_1 + Base + + + Cfg_2 + Base + +
+ + Delphi.Personality.12 + Application + + + + SilentPlayer.dpr + + + + + + true + + + + + true + + + + + true + + + + + SilentPlayer.exe + true + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + res\xml + 1 + + + res\xml + 1 + + + + + library\lib\armeabi + 1 + + + library\lib\armeabi + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + library\lib\mips + 1 + + + library\lib\mips + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + + + library\lib\armeabi-v7a + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v21 + 1 + + + res\drawable-anydpi-v21 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-v21 + 1 + + + res\values-v21 + 1 + + + + + res\values-v31 + 1 + + + res\values-v31 + 1 + + + + + res\values-v35 + 1 + + + res\values-v35 + 1 + + + + + res\drawable-anydpi-v26 + 1 + + + res\drawable-anydpi-v26 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-anydpi-v33 + 1 + + + res\drawable-anydpi-v33 + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\values-night-v21 + 1 + + + res\values-night-v21 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-ldpi + 1 + + + res\drawable-ldpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-mdpi + 1 + + + res\drawable-mdpi + 1 + + + + + res\drawable-hdpi + 1 + + + res\drawable-hdpi + 1 + + + + + res\drawable-xhdpi + 1 + + + res\drawable-xhdpi + 1 + + + + + res\drawable-xxhdpi + 1 + + + res\drawable-xxhdpi + 1 + + + + + res\drawable-xxxhdpi + 1 + + + res\drawable-xxxhdpi + 1 + + + + + res\drawable-small + 1 + + + res\drawable-small + 1 + + + + + res\drawable-normal + 1 + + + res\drawable-normal + 1 + + + + + res\drawable-large + 1 + + + res\drawable-large + 1 + + + + + res\drawable-xlarge + 1 + + + res\drawable-xlarge + 1 + + + + + res\values + 1 + + + res\values + 1 + + + + + res\drawable-anydpi-v24 + 1 + + + res\drawable-anydpi-v24 + 1 + + + + + res\drawable + 1 + + + res\drawable + 1 + + + + + res\drawable-night-anydpi-v21 + 1 + + + res\drawable-night-anydpi-v21 + 1 + + + + + res\drawable-anydpi-v31 + 1 + + + res\drawable-anydpi-v31 + 1 + + + + + res\drawable-night-anydpi-v31 + 1 + + + res\drawable-night-anydpi-v31 + 1 + + + + + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + Contents\MacOS + 1 + .framework + + + 0 + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .dll;.bpl + + + + + 1 + .dylib + + + 1 + .dylib + + + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + Contents\MacOS + 1 + .dylib + + + 0 + .bpl + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + Contents\Resources\StartUp\ + 0 + + + 0 + + + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + Contents + 1 + + + Contents + 1 + + + Contents + 1 + + + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + Contents\Resources + 1 + + + + + library\lib\armeabi-v7a + 1 + + + library\lib\arm64-v8a + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + Contents\MacOS + 1 + + + 0 + + + + + library\lib\armeabi-v7a + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + ..\$(PROJECTNAME).app.dSYM\Contents\Resources\DWARF + 1 + + + + + ..\ + 1 + + + ..\ + 1 + + + ..\ + 1 + + + + + 1 + + + 1 + + + 1 + + + + + ..\$(PROJECTNAME).launchscreen + 64 + + + ..\$(PROJECTNAME).launchscreen + 64 + + + + + 1 + + + 1 + + + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + Assets + 1 + + + Assets + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\LaunchScreenImage.imageset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + ..\$(PROJECTNAME).launchscreen\Assets\AppIcon.appiconset + 1 + + + + + + + + + + + + + + + + + True + True + False + True + True + True + False + True + True + True + True + + + 12 + + + + +
diff --git a/TTW_Bot_app.dpr b/TTW_Bot_app.dpr index 1ca6deb..2ec8337 100644 --- a/TTW_Bot_app.dpr +++ b/TTW_Bot_app.dpr @@ -1,33 +1,50 @@ program TTW_Bot_app; uses - System.StartUpCopy, SysUtils, + System.StartUpCopy, + SysUtils, FMX.Forms, - uGeneral in 'uGeneral.pas' {TTW_Bot} , - fSettings in 'fSettings.pas' {frSettings: TFrame} , - fAI in 'fAI.pas' {frAI: TFrame} , - fNotify in 'fNotify.pas' {frNotify: TFrame} , - fAutoActions in 'fAutoActions.pas' {frAutoActions: TFrame} , - fOBS in 'fOBS.pas' {frOBS: TFrame} , - fLog in 'fLog.pas' {frLog: TFrame} , - uRecords in 'uRecords.pas', - fCommands in 'fCommands.pas' {frCommands: TFrame} , - uDataBase in 'uDataBase.pas', - fColorSettings in 'fColorSettings.pas' {frColorSettings: TFrame} , - uCreateChat in 'uCreateChat.pas' {fCreateChat} , - fFontSettings in 'fFontSettings.pas' {frFontSettings: TFrame} , - uCreateNotify in 'uCreateNotify.pas' {fCreateNotify} , - uTWAuth in 'uTWAuth.pas', - uTTWAPI in 'uTTWAPI.pas', - uAPIDA in 'uAPIDA.pas', - uShowText in 'uShowText.pas' {fShowText} , - uWSDA in 'uWSDA.pas', - uQ in 'uQ.pas' {frmQ} , - fSimpleGrid in 'fSimpleGrid.pas' {frSimpleGrid: TFrame} , - fContruct in 'fContruct.pas' {frContruct: TFrame} , - fGroupsRequest in 'fGroupsRequest.pas' {frGroupsRequest: TFrame} , - uMyTimer in 'uMyTimer.pas', - uRegExpr in 'uRegExpr.pas'; + Web.WebReq, + IdHTTPWebBrokerBridge, + fAI in 'frames\fAI.pas' {frAI: TFrame}, + fAutoActions in 'frames\fAutoActions.pas' {frAutoActions: TFrame}, + fColorSettings in 'frames\fColorSettings.pas' {frColorSettings: TFrame}, + fCommands in 'frames\fCommands.pas' {frCommands: TFrame}, + fContruct in 'frames\fContruct.pas' {frContruct: TFrame}, + fFontSettings in 'frames\fFontSettings.pas' {frFontSettings: TFrame}, + fGroupsRequest in 'frames\fGroupsRequest.pas' {frGroupsRequest: TFrame}, + fLog in 'frames\fLog.pas' {frLog: TFrame}, + fNotify in 'frames\fNotify.pas' {frNotify: TFrame}, + fOBS in 'frames\fOBS.pas' {frOBS: TFrame}, + fSettings in 'frames\fSettings.pas' {frSettings: TFrame}, + fSimpleGrid in 'frames\fSimpleGrid.pas' {frSimpleGrid: TFrame}, + fTTS in 'frames\fTTS.pas' {frTTS: TFrame}, + uCreateChat in 'forms\uCreateChat.pas' {fCreateChat}, + uCreateNotify in 'forms\uCreateNotify.pas' {fCreateNotify}, + uGeneral in 'forms\uGeneral.pas' {TTW_Bot}, + uQ in 'forms\uQ.pas' {frmQ}, + uShowText in 'forms\uShowText.pas' {fShowText}, + uAPIDA in 'Services\uAPIDA.pas', + uChatAPI in 'Services\uChatAPI.pas', + uCustomEmoties in 'Services\uCustomEmoties.pas', + uGigaChat in 'Services\uGigaChat.pas', + uKandinskyAPI in 'Services\uKandinskyAPI.pas', + uTTWAPI in 'Services\uTTWAPI.pas', + uTTWEventSub in 'Services\uTTWEventSub.pas', + uTTWIRC in 'Services\uTTWIRC.pas', + uTWAuth in 'Services\uTWAuth.pas', + uWebServerKandinsky in 'Services\uWebServerKandinsky.pas', + uWSDA in 'Services\uWSDA.pas', + uDataBase in 'utils\uDataBase.pas', + uMyTimer in 'utils\uMyTimer.pas', + uOBS_Doc_Player in 'utils\uOBS_Doc_Player.pas' {OBS_Doc_Player: TWebModule}, + uRecords in 'utils\uRecords.pas', + uRegExpr in 'utils\uRegExpr.pas', + uSoundManager in 'utils\uSoundManager.pas', + uTTS in 'utils\uTTS.pas', + fPlayerWeb in 'frames\fPlayerWeb.pas' {frPlayerWeb: TFrame}, + uPlayerThread in 'utils\uPlayerThread.pas', + uWebServerChat in 'utils\uWebServerChat.pas'; {$R *.res} @@ -36,9 +53,16 @@ begin {$IFDEF DEBUG} ReportMemoryLeaksOnShutdown := True; {$ENDIF} - Application.Initialize; + if WebRequestHandler <> nil then + WebRequestHandler.WebModuleClass := OBS_Doc_Player; + Application.Initialize; Application.CreateForm(TTTW_Bot, TTW_Bot); + Application.CreateForm(TfCreateChat, fCreateChat); + Application.CreateForm(TfCreateNotify, fCreateNotify); + Application.CreateForm(TfrmQ, frmQ); + Application.CreateForm(TfShowText, fShowText); + Application.CreateForm(TOBS_Doc_Player, OBS_Doc_Player); Application.OnException := TTW_Bot.GlobalExceptionHandler; Application.CreateForm(TfCreateChat, fCreateChat); Application.CreateForm(TfCreateNotify, fCreateNotify); diff --git a/TTW_Bot_app.dproj b/TTW_Bot_app.dproj index f7271e7..bae77af 100644 --- a/TTW_Bot_app.dproj +++ b/TTW_Bot_app.dproj @@ -317,77 +317,120 @@ MainSource - -
TTW_Bot
-
- -
frSettings
- TFrame -
- +
frAI
+ fmx TFrame
- -
frNotify
- TFrame -
- +
frAutoActions
+ fmx TFrame
- -
frOBS
- TFrame -
- -
frLog
- TFrame -
- - -
frCommands
- TFrame -
- - +
frColorSettings
+ fmx TFrame
- -
fCreateChat
-
- -
frFontSettings
+ +
frCommands
+ fmx TFrame
- -
fCreateNotify
-
- - - - -
fShowText
-
- - -
frmQ
-
- -
frSimpleGrid
- TFrame -
- +
frContruct
+ fmx TFrame
- + +
frFontSettings
+ fmx + TFrame +
+
frGroupsRequest
+ fmx TFrame
- - + +
frLog
+ fmx + TFrame +
+ +
frNotify
+ fmx + TFrame +
+ +
frOBS
+ fmx + TFrame +
+ +
frSettings
+ fmx + TFrame +
+ +
frSimpleGrid
+ fmx + TFrame +
+ +
frTTS
+ fmx + TFrame +
+ +
fCreateChat
+ fmx +
+ +
fCreateNotify
+ fmx +
+ +
TTW_Bot
+ fmx +
+ +
frmQ
+ fmx +
+ +
fShowText
+ fmx +
+ + + + + + + + + + + + + + +
OBS_Doc_Player
+ dfm + TWebModule +
+ + + + + +
frPlayerWeb
+ fmx + TFrame +
+ + Base diff --git a/fOBS.pas b/fOBS.pas deleted file mode 100644 index ffa60f3..0000000 --- a/fOBS.pas +++ /dev/null @@ -1,322 +0,0 @@ -unit fOBS; - -interface - -uses - System.SysUtils, System.Types, System.UITypes, System.Classes, - System.Variants, - FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, - System.Rtti, FMX.Grid.Style, FMX.Grid, FMX.ScrollBox, FMX.Edit, FMX.Colors, - FMX.ListBox, FMX.EditBox, FMX.SpinBox, FMX.Controls.Presentation, uRecords; - -type - TfrOBS = class(TFrame) - sgWebChats: TStringGrid; - btnCreateOBSChat: TButton; - btnDeleteeChat: TButton; - Label1: TLabel; - IntegerColumn1: TIntegerColumn; - StringColumn1: TStringColumn; - StringColumn2: TStringColumn; - btnCreateOBSNotify: TButton; - btnCreateOBSKandinsky: TButton; - procedure btnDeleteeChatClick(Sender: TObject); - procedure btnCreateOBSKandinskyClick(Sender: TObject); - procedure btnCreateOBSChatClick(Sender: TObject); - procedure btnCreateOBSNotifyClick(Sender: TObject); - procedure sgWebChatsCellDblClick(const Column: TColumn; const Row: Integer); - private - { Private declarations } - public - { Public declarations } - listChats: TArray; - listNotify: TArray; - listKandinsky: TArray; - - procedure UpdateGridFromArray; - procedure AddChat(newRecord: TOBSChat); - procedure EdtChat(newRecord: TOBSChat; oldPort: Integer); - procedure DelChat(aPort: Integer); - - procedure AddNotify(newRecord: TOBSNotify); - procedure EdtNotify(newRecord: TOBSNotify; oldPort: Integer); - procedure DelNotify(aPort: Integer); - - procedure AddKandinsky(newRecord: TOBSKandinsky); - procedure DelKandinsky(aPort: Integer); - - end; - -implementation - -{$R *.fmx} - -uses uGeneral, uCreateChat, uCreateNotify; - -{ TfrOBS } - -procedure TfrOBS.AddChat(newRecord: TOBSChat); -begin - SetLength(listChats, Length(listChats) + 1); - listChats[High(listChats)] := newRecord; - UpdateGridFromArray; - db.SaveRecordArray('listChats', listChats); -end; - -procedure TfrOBS.AddKandinsky(newRecord: TOBSKandinsky); -begin - SetLength(listKandinsky, Length(listKandinsky) + 1); - listKandinsky[High(listKandinsky)] := newRecord; - UpdateGridFromArray; - db.SaveRecordArray('listKandinsky', listKandinsky); -end; - -procedure TfrOBS.AddNotify(newRecord: TOBSNotify); -begin - SetLength(listNotify, Length(listNotify) + 1); - listNotify[High(listNotify)] := newRecord; - UpdateGridFromArray; - db.SaveRecordArray('listNotify', listNotify); -end; - -procedure TfrOBS.btnCreateOBSChatClick(Sender: TObject); -var - dport, i: Integer; -begin - dport := 8080; - for i := 0 to sgWebChats.RowCount - 1 do - begin - if strtoint(sgWebChats.Cells[0, i]) >= dport then - dport := strtoint(sgWebChats.Cells[0, i]) + 1; - end; - fCreateChat.sbWebServerPort.Value := dport; - fCreateChat.isEdit := false; - fCreateChat.Show; -end; - -procedure TfrOBS.btnCreateOBSKandinskyClick(Sender: TObject); -var - dport: Integer; - i: Integer; - rk: TOBSKandinsky; -begin - dport := 8080; - for i := 0 to sgWebChats.RowCount - 1 do - begin - if strtoint(sgWebChats.Cells[0, i]) >= dport then - dport := strtoint(sgWebChats.Cells[0, i]) + 1; - end; - rk.port := dport; - AddKandinsky(rk); - -end; - -procedure TfrOBS.btnCreateOBSNotifyClick(Sender: TObject); -var - dport, i: Integer; -begin - dport := 8080; - for i := 0 to sgWebChats.RowCount - 1 do - begin - if strtoint(sgWebChats.Cells[0, i]) >= dport then - dport := strtoint(sgWebChats.Cells[0, i]) + 1; - end; - fCreateNotify.sbWebServerPort.Value := dport; - fCreateNotify.isEdit := false; - fCreateNotify.Show; -end; - -procedure TfrOBS.btnDeleteeChatClick(Sender: TObject); -begin - if sgWebChats.Cells[1, sgWebChats.Row] = 'Чат' then - begin - DelChat(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); - end; - if sgWebChats.Cells[1, sgWebChats.Row] = 'Kandinsky' then - begin - DelKandinsky(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); - end; - if sgWebChats.Cells[1, sgWebChats.Row] = 'Оповещение' then - begin - DelNotify(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); - end; -end; - -procedure TfrOBS.DelChat(aPort: Integer); -var - i, j: Integer; -begin - // Ищем в обратном порядке для безопасного удаления - for i := High(listChats) downto 0 do - begin - if listChats[i].port = aPort then - begin - // Сдвигаем элементы массива - for j := i to High(listChats) - 1 do - listChats[j] := listChats[j + 1]; - // Уменьшаем размер массива - SetLength(listChats, Length(listChats) - 1); - // Выходим после первого найденного совпадения (предполагаем уникальность портов) - Break; - end; - end; - - db.SaveRecordArray('listChats', listChats); - UpdateGridFromArray; -end; - -procedure TfrOBS.DelKandinsky(aPort: Integer); -var - i, j: Integer; -begin - // Ищем в обратном порядке для безопасного удаления - for i := High(listKandinsky) downto 0 do - begin - if listKandinsky[i].port = aPort then - begin - // Сдвигаем элементы массива - for j := i to High(listKandinsky) - 1 do - listKandinsky[j] := listKandinsky[j + 1]; - // Уменьшаем размер массива - SetLength(listKandinsky, Length(listKandinsky) - 1); - // Выходим после первого найденного совпадения (предполагаем уникальность портов) - Break; - end; - end; - UpdateGridFromArray; - db.SaveRecordArray('listKandinsky', listKandinsky); -end; - -procedure TfrOBS.DelNotify(aPort: Integer); -var - i, j: Integer; -begin - // Ищем в обратном порядке для безопасного удаления - for i := High(listNotify) downto 0 do - begin - if listNotify[i].port = aPort then - begin - // Сдвигаем элементы массива - for j := i to High(listNotify) - 1 do - listNotify[j] := listNotify[j + 1]; - // Уменьшаем размер массива - SetLength(listNotify, Length(listNotify) - 1); - // Выходим после первого найденного совпадения (предполагаем уникальность портов) - Break; - end; - end; - - UpdateGridFromArray; - db.SaveRecordArray('listNotify', listNotify); -end; - -procedure TfrOBS.EdtChat(newRecord: TOBSChat; oldPort: Integer); -var - i: Integer; -begin - for i := 0 to High(listChats) do - if listChats[i].port = oldPort then - begin - listChats[i] := newRecord; - UpdateGridFromArray; - db.SaveRecordArray('listChats', listChats); - Break; - end; -end; - -procedure TfrOBS.EdtNotify(newRecord: TOBSNotify; oldPort: Integer); -var - i: Integer; -begin - for i := 0 to High(listNotify) do - if listNotify[i].port = oldPort then - begin - listNotify[i] := newRecord; - UpdateGridFromArray; - db.SaveRecordArray('listNotify', listNotify); - Break; - end; -end; - -procedure TfrOBS.sgWebChatsCellDblClick(const Column: TColumn; - const Row: Integer); -var - myChatRec: TOBSChat; - myNotifyRec: TOBSNotify; - i: Integer; -begin - if sgWebChats.Cells[1, Row] = 'Оповещение' then - begin - for i := 0 to High(listNotify) do - if listNotify[i].port = (strtoint(sgWebChats.Cells[0, Row])) then - begin - myNotifyRec := listNotify[i]; - Break; - end; - - fCreateNotify.isEdit := true; - fCreateNotify.setRecord(myNotifyRec); - fCreateNotify.Show; - end; - if sgWebChats.Cells[1, Row] = 'Чат' then - begin - for i := 0 to High(listChats) do - if listChats[i].port = (strtoint(sgWebChats.Cells[0, Row])) then - begin - myChatRec := listChats[i]; - Break; - end; - fCreateChat.isEdit := true; - fCreateChat.setRecord(myChatRec); - fCreateChat.Show; - end; -end; - -procedure TfrOBS.UpdateGridFromArray; -var - i, rowIndex: Integer; -begin - sgWebChats.BeginUpdate; - try - sgWebChats.RowCount := 0; // Сбрасываем строки - - rowIndex := 0; // Отдельный счетчик для строк сетки - - // listChats - for i := 0 to High(listChats) do - begin - sgWebChats.RowCount := rowIndex + 1; - sgWebChats.Cells[0, rowIndex] := inttostr(listChats[i].port); - sgWebChats.Cells[1, rowIndex] := 'Чат'; - sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + - inttostr(listChats[i].port); - Inc(rowIndex); // Увеличиваем счетчик строк - end; - - // listNotify - for i := 0 to High(listNotify) do - begin - sgWebChats.RowCount := rowIndex + 1; - sgWebChats.Cells[0, rowIndex] := inttostr(listNotify[i].port); - sgWebChats.Cells[1, rowIndex] := 'Оповещение'; - sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + - inttostr(listNotify[i].port); - Inc(rowIndex); // Увеличиваем счетчик строк - end; - - // listKandinsky - for i := 0 to High(listKandinsky) do - begin - sgWebChats.RowCount := rowIndex + 1; - sgWebChats.Cells[0, rowIndex] := inttostr(listKandinsky[i].port); - sgWebChats.Cells[1, rowIndex] := 'Kandinsky'; - sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + - inttostr(listKandinsky[i].port); - Inc(rowIndex); // Увеличиваем счетчик строк - end; - finally - sgWebChats.EndUpdate; - end; -end; - -end. diff --git a/forms/__recovery/__recovery.ini b/forms/__recovery/__recovery.ini new file mode 100644 index 0000000..e847a62 --- /dev/null +++ b/forms/__recovery/__recovery.ini @@ -0,0 +1,5 @@ +[uCreateChat.pas] +SaveTime=14.08.2025 10:44:31 +FileCount=2 +File0=C:\Users\PTyTb\Documents\Embarcadero\Studio\Projects\ttw_fmx_v10\forms\uCreateChat.pas +File1=C:\Users\PTyTb\Documents\Embarcadero\Studio\Projects\ttw_fmx_v10\forms\uCreateChat.fmx diff --git a/forms/__recovery/uCreateChat.fmx b/forms/__recovery/uCreateChat.fmx new file mode 100644 index 0000000..0cd5ea8 --- /dev/null +++ b/forms/__recovery/uCreateChat.fmx @@ -0,0 +1,189 @@ +object fCreateChat: TfCreateChat + Left = 0 + Top = 0 + Caption = #1056#1077#1076#1072#1082#1090#1086#1088' '#1095#1072#1090#1086#1074 + ClientHeight = 287 + ClientWidth = 810 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnShow = FormShow + DesignerMasterStyle = 0 + object GroupBox1: TGroupBox + Position.X = 8.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 305.000000000000000000 + Size.Height = 271.000000000000000000 + Size.PlatformDefault = False + Text = #1041#1083#1086#1082' '#1089#1086#1086#1073#1097#1077#1085#1080#1103 + TabOrder = 1 + inline frChatSettings1: TfrColorSettings + Align = Client + Margins.Top = 20.000000000000000000 + Size.Width = 305.000000000000000000 + Size.Height = 251.000000000000000000 + Size.PlatformDefault = False + inherited ccbStyleBorderColor: TColorComboBox + TabOrder = 30 + end + inherited Label40: TLabel + TabOrder = 8 + end + inherited Label42: TLabel + TabOrder = 35 + end + inherited Label44: TLabel + TabOrder = 38 + end + inherited Label48: TLabel + TabOrder = 40 + end + inherited sbStyleBlockBorderSize: TSpinBox + TabOrder = 37 + end + inherited sbStyleBlockPadding: TSpinBox + TabOrder = 41 + end + inherited Label1: TLabel + TabOrder = 34 + end + inherited ccbBColor: TColorComboBox + TabOrder = 36 + end + inherited btnChangeBGColor: TButton + TabOrder = 39 + end + end + end + object GroupBox2: TGroupBox + Position.X = 321.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 240.000000000000000000 + Size.Height = 145.000000000000000000 + Size.PlatformDefault = False + Text = #1064#1088#1080#1092#1090 + TabOrder = 2 + inline frFontSettings1: TfrFontSettings + Align = Client + Margins.Top = 20.000000000000000000 + Size.Width = 240.000000000000000000 + Size.Height = 125.000000000000000000 + Size.PlatformDefault = False + inherited ccbFontColor: TColorComboBox + TabOrder = 36 + end + inherited Label49: TLabel + TabOrder = 35 + end + inherited Label46: TLabel + TabOrder = 39 + end + end + end + object GroupBox10: TGroupBox + Position.X = 569.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 232.000000000000000000 + Size.Height = 203.000000000000000000 + Size.PlatformDefault = False + Text = #1053#1072#1089#1090#1088#1086#1081#1082#1080 + TabOrder = 0 + object Label27: TLabel + Position.X = 8.000000000000000000 + Position.Y = 22.000000000000000000 + Size.Width = 249.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + TextSettings.Trimming = None + Text = #1052#1072#1082#1089#1080#1084#1072#1083#1100#1085#1086#1077' '#1082#1086#1083#1080#1095#1077#1089#1090#1074#1086' '#1089#1086#1086#1073#1097#1077#1085#1080#1081 + TabOrder = 3 + end + object Label38: TLabel + Position.X = 8.000000000000000000 + Position.Y = 77.000000000000000000 + Size.Width = 249.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + TextSettings.Trimming = None + Text = #1042#1088#1077#1084#1103' '#1086#1090#1086#1073#1088#1072#1078#1077#1085#1080#1103' '#1089#1086#1086#1073#1097#1077#1085#1080#1103 + TabOrder = 0 + end + object sbMaxMsg: TSpinBox + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 5 + Cursor = crIBeam + Value = 5.000000000000000000 + Position.X = 8.000000000000000000 + Position.Y = 47.000000000000000000 + end + object sbTimeMsg: TSpinBox + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 2 + Cursor = crIBeam + Value = 10.000000000000000000 + Position.X = 8.000000000000000000 + Position.Y = 102.000000000000000000 + end + object Label39: TLabel + Position.X = 8.000000000000000000 + Position.Y = 132.000000000000000000 + Size.Width = 193.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + TextSettings.Trimming = None + Text = #1055#1086#1088#1090' '#1042#1077#1073' '#1057#1077#1088#1074#1077#1088#1072 + TabOrder = 6 + end + object cbFreez: TCheckBox + Position.X = 112.000000000000000000 + Position.Y = 105.000000000000000000 + Size.Width = 112.000000000000000000 + Size.Height = 19.000000000000000000 + Size.PlatformDefault = False + TabOrder = 38 + Text = #1042#1077#1095#1085#1086 + end + object sbWebServerPort: TSpinBox + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 4 + Cursor = crIBeam + Min = 8080.000000000000000000 + Max = 65000.000000000000000000 + Value = 8085.000000000000000000 + Position.X = 8.000000000000000000 + Position.Y = 157.000000000000000000 + end + end + object edtWebChatTest: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 5 + Position.X = 321.000000000000000000 + Position.Y = 161.000000000000000000 + Size.Width = 240.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end + object btnWebChatTest: TButton + Position.X = 321.000000000000000000 + Position.Y = 191.000000000000000000 + Size.Width = 152.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 3 + Text = #1058#1077#1089#1090#1086#1074#1086#1077' '#1089#1086#1086#1073#1097#1077#1085#1080#1077 + TextSettings.Trimming = None + OnClick = btnWebChatTestClick + end + object btnCreateWebChat: TButton + Position.X = 704.000000000000000000 + Position.Y = 257.000000000000000000 + Size.Width = 97.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 4 + Text = #1057#1086#1079#1076#1072#1090#1100' '#1095#1072#1090 + TextSettings.Trimming = None + OnClick = btnCreateWebChatClick + end +end diff --git a/forms/__recovery/uCreateChat.pas b/forms/__recovery/uCreateChat.pas new file mode 100644 index 0000000..ae29b88 --- /dev/null +++ b/forms/__recovery/uCreateChat.pas @@ -0,0 +1,207 @@ +unit uCreateChat; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, FMX.ListBox, FMX.Colors, FMX.SpinBox, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, + fColorSettings, fFontSettings, FMX.Controls.Presentation, FMX.StdCtrls, + FMX.Edit, FMX.EditBox, StrUtils, uRecords; + +type + TfCreateChat = class(TForm) + frChatSettings1: TfrColorSettings; + GroupBox1: TGroupBox; + GroupBox2: TGroupBox; + frFontSettings1: TfrFontSettings; + GroupBox10: TGroupBox; + Label27: TLabel; + Label38: TLabel; + sbMaxMsg: TSpinBox; + sbTimeMsg: TSpinBox; + Label39: TLabel; + cbFreez: TCheckBox; + sbWebServerPort: TSpinBox; + edtWebChatTest: TEdit; + btnWebChatTest: TButton; + btnCreateWebChat: TButton; + procedure FormCreate(Sender: TObject); + procedure btnCreateWebChatClick(Sender: TObject); + procedure FormShow(Sender: TObject); + procedure btnWebChatTestClick(Sender: TObject); + private + { Private declarations } + function GetColorFromColorPanel(aColor: TAlphaColor): string; + public + { Public declarations } + isEdit: boolean; + oldPort: integer; + procedure setRecord(aRec: TOBSChat); + end; + +var + fCreateChat: TfCreateChat; + +implementation + +uses uGeneral; + +{$R *.fmx} + +function TfCreateChat.GetColorFromColorPanel(aColor: TAlphaColor): string; +var + Color: TAlphaColor; + r, G, B: Byte; + A: Real; + FS: TFormatSettings; +begin + Color := aColor; + r := TAlphaColorRec(Color).r; + G := TAlphaColorRec(Color).G; + B := TAlphaColorRec(Color).B; + A := TAlphaColorRec(Color).A / 255; // Преобразуем альфа-канал РІ диапазон 0..1 + + // Устанавливаем точку РІ качестве десятичного разделителя + FS := TFormatSettings.Create; + FS.DecimalSeparator := '.'; + + result := Format('rgba(%d, %d, %d, %.2f)', [r, G, B, A], FS); +end; + +procedure TfCreateChat.setRecord(aRec: TOBSChat); +var + SavedColor: TAlphaColor; +begin + if TryStrToUInt('$' + aRec.ColorBlock, Cardinal(SavedColor)) then + fCreateChat.frChatSettings1.cpStyleBlockColor.Color := SavedColor + else + fCreateChat.frChatSettings1.cpStyleBlockColor.Color := TAlphaColorRec.Black; + fCreateChat.frChatSettings1.ccbStyleBorderColor.ItemIndex := aRec.ColorBorder; + fCreateChat.frChatSettings1.ccbBColor.ItemIndex := aRec.ColorBackground; + fCreateChat.frChatSettings1.sbStyleBlockBorderSize.Value := aRec.SolidBorder; + fCreateChat.frChatSettings1.sbStyleBlockPadding.Value := aRec.Paddings; + + fCreateChat.frFontSettings1.ccbFontColor.ItemIndex := aRec.ColorFont; + fCreateChat.frFontSettings1.sbFontSize.Value := aRec.SizeFont; + fCreateChat.frFontSettings1.cbFontStyleDefault.ItemIndex := aRec.StyleFont; + + fCreateChat.sbTimeMsg.Value := aRec.TimeMess; + fCreateChat.sbMaxMsg.Value := aRec.MaxCountMess; + fCreateChat.sbWebServerPort.Value := aRec.port; + oldPort := aRec.port; +end; + +procedure TfCreateChat.btnCreateWebChatClick(Sender: TObject); +var + OBSChat: TOBSChat; +begin + OBSChat.ColorBlock := GetColorFromColorPanel + (frChatSettings1.cpStyleBlockColor.Color); + OBSChat.ColorBorder := frChatSettings1.ccbStyleBorderColor.ItemIndex; + OBSChat.ColorBackground := frChatSettings1.ccbBColor.ItemIndex; + OBSChat.SolidBorder := round(frChatSettings1.sbStyleBlockBorderSize.Value); + OBSChat.Paddings := round(frChatSettings1.sbStyleBlockPadding.Value); + + OBSChat.ColorFont := frFontSettings1.ccbFontColor.ItemIndex; + OBSChat.SizeFont := round(frFontSettings1.sbFontSize.Value); + OBSChat.StyleFont := frFontSettings1.cbFontStyleDefault.ItemIndex; + + OBSChat.MaxCountMess := round(sbMaxMsg.Value); + OBSChat.TimeMess := round(sbTimeMsg.Value); + OBSChat.port := round(sbWebServerPort.Value); + + if isEdit then + TTW_Bot.frOBS1.EdtChat(OBSChat, oldPort) + else + TTW_Bot.frOBS1.AddChat(OBSChat); + close; +end; + +procedure TfCreateChat.btnWebChatTestClick(Sender: TObject); +var j:integer; aRecord: TTwitchChatMessage; +begin +aRecord.Username:='Test'; +aRecord.DisplayName:='Test'; +aRecord.Message:=edtWebChatTest.Text; + for j := 0 to TTW_Bot.frOBS1.ChatWebServers.Count - 1 do + begin + if TTW_Bot.frOBS1.ChatWebServers[j].port = round(sbWebServerPort.Value) then + begin + TTW_Bot.frOBS1.MsgToWebServer(aRecord); + end; + end; +end; + +procedure TfCreateChat.FormCreate(Sender: TObject); + procedure LoadFontList; + var + SearchRec: TSearchRec; + n: integer; + begin + if not DirectoryExists(myConst.fontsPath) then + CreateDir(myConst.fontsPath); + + n := 1; + if FindFirst(IncludeTrailingPathDelimiter(myConst.fontsPath) + '*.*', + faArchive, SearchRec) = 0 then + try + repeat + if (SearchRec.Attr and faAnyFile) = SearchRec.Attr then + begin + fCreateChat.frFontSettings1.cbFontStyleDefault.Items.Add + (SearchRec.Name); + Inc(n); + end; + until FindNext(SearchRec) <> 0; + finally + System.SysUtils.FindClose(SearchRec); + end; + end; + + procedure LoadChatOBSSettings; + var + I: integer; + c: TComponent; + ColorStr: string; + SavedColor: TAlphaColor; + begin + for I := 0 to frChatSettings1.ComponentCount - 1 do + begin + c := frChatSettings1.Components[I]; + if c is TComboBox then + TComboBox(c).ItemIndex := + strtoint(db.ReadSetting(TComboBox(c).Name, '0')) + else if c is TColorComboBox then + TColorComboBox(c).ItemIndex := + strtoint(db.ReadSetting(TComboBox(c).Name, '147')) + else if c is TSpinBox then + TSpinBox(c).text := db.ReadSetting(TSpinBox(c).Name, + IfThen(TSpinBox(c).Name = 'sbWebServerPort', '8080', '1')) + else if c is TCheckBox then + TCheckBox(c).IsChecked := db.ReadSetting(TCheckBox(c).Name, '0') = '1'; + end; + + ColorStr := db.ReadSetting('cpStyleBlockColor', 'FF000000'); + if TryStrToUInt('$' + ColorStr, Cardinal(SavedColor)) then + frChatSettings1.cpStyleBlockColor.Color := SavedColor + else + frChatSettings1.cpStyleBlockColor.Color := TAlphaColorRec.Black; + end; + +begin + isEdit := false; + LoadChatOBSSettings; + LoadFontList; + +end; + +procedure TfCreateChat.FormShow(Sender: TObject); +begin + if isEdit then + btnCreateWebChat.text := 'Рзменить чат' + else + btnCreateWebChat.text := 'Создать чат'; +end; + +end. diff --git a/uCreateChat.fmx b/forms/uCreateChat.fmx similarity index 100% rename from uCreateChat.fmx rename to forms/uCreateChat.fmx diff --git a/uCreateChat.pas b/forms/uCreateChat.pas similarity index 100% rename from uCreateChat.pas rename to forms/uCreateChat.pas diff --git a/uCreateNotify.fmx b/forms/uCreateNotify.fmx similarity index 100% rename from uCreateNotify.fmx rename to forms/uCreateNotify.fmx diff --git a/uCreateNotify.pas b/forms/uCreateNotify.pas similarity index 100% rename from uCreateNotify.pas rename to forms/uCreateNotify.pas diff --git a/uGeneral.fmx b/forms/uGeneral.fmx similarity index 99% rename from uGeneral.fmx rename to forms/uGeneral.fmx index c4990ab..36f44d5 100644 --- a/uGeneral.fmx +++ b/forms/uGeneral.fmx @@ -18,7 +18,7 @@ object TTW_Bot: TTTW_Bot Size.Width = 970.000000000000000000 Size.Height = 744.000000000000000000 Size.PlatformDefault = False - TabIndex = 7 + TabIndex = 0 TabOrder = 0 TabPosition = PlatformDefault Sizes = ( @@ -45,7 +45,7 @@ object TTW_Bot: TTTW_Bot item end> TextSettings.Trimming = None - IsSelected = False + IsSelected = True ImageIndex = 21 Size.Width = 96.000000000000000000 Size.Height = 26.000000000000000000 @@ -72,15 +72,13 @@ object TTW_Bot: TTTW_Bot inherited btnOpenStream: TButton Images = ImageList1 ImageIndex = 17 - TabOrder = 32 end inherited btnGetTokenStreamer: TButton Images = ImageList1 ImageIndex = 10 - TabOrder = 33 end inherited edtBotTokenStreamer: TEdit - TabOrder = 34 + TabOrder = 33 end inherited Label53: TLabel TabOrder = 36 @@ -91,39 +89,36 @@ object TTW_Bot: TTTW_Bot Images = ImageList1 ImageIndex = 10 end - inherited Label63: TLabel - TabOrder = 34 - end inherited edtDAClientID: TEdit - TabOrder = 37 + TabOrder = 33 end inherited Label64: TLabel - TabOrder = 35 + TabOrder = 31 end inherited edtDAClientSecret: TEdit - TabOrder = 36 + TabOrder = 34 end inherited Label65: TLabel - TabOrder = 38 + TabOrder = 35 end inherited edtDARedirectURL: TEdit - TabOrder = 39 + TabOrder = 42 end inherited edtDACode: TEdit - TabOrder = 40 + TabOrder = 36 end inherited Label66: TLabel - TabOrder = 41 + TabOrder = 39 end inherited btnDAStart: TButton Images = ImageList1 ImageIndex = 18 - TabOrder = 42 + TabOrder = 41 OnClick = frSettings1btnDAStartClick end inherited btnGetDADef: TButton Images = ImageList1 - TabOrder = 44 + TabOrder = 43 end end inherited btnOpenRomaning: TButton @@ -257,6 +252,7 @@ object TTW_Bot: TTTW_Bot inherited btnAIPic: TButton Images = ImageList1 ImageIndex = 5 + TabOrder = 46 end end inherited btnAddCommand: TButton @@ -283,8 +279,8 @@ object TTW_Bot: TTTW_Bot Viewport.Width = 207.000000000000000000 Viewport.Height = 116.000000000000000000 end - inherited btnRandomAdd: TButton - TabOrder = 32 + inherited btnRandomDel: TButton + TabOrder = 31 end inherited btnRmGroup: TButton TabOrder = 33 @@ -427,6 +423,28 @@ object TTW_Bot: TTTW_Bot Text = #1053#1072#1074#1099#1082#1080 ExplicitSize.cx = 79.000000000000000000 ExplicitSize.cy = 26.000000000000000000 + object GroupBox1: TGroupBox + Padding.Left = 10.000000000000000000 + Padding.Top = 20.000000000000000000 + Padding.Right = 10.000000000000000000 + Padding.Bottom = 10.000000000000000000 + Position.X = 1.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 328.000000000000000000 + Size.Height = 233.000000000000000000 + Size.PlatformDefault = False + Text = #1054#1079#1074#1091#1095#1082#1072' '#1090#1077#1082#1089#1090#1072 + TabOrder = 0 + inline frTTS1: TfrTTS + Align = Client + Size.Width = 308.000000000000000000 + Size.Height = 203.000000000000000000 + Size.PlatformDefault = False + inherited btnSend: TButton + OnClick = frTTS1btnSendClick + end + end + end end object TabItem4: TTabItem CustomIcon = < @@ -449,41 +467,84 @@ object TTW_Bot: TTTW_Bot Size.Height = 345.000000000000000000 Size.PlatformDefault = False inherited sgWebChats: TStringGrid + Align = Bottom + CanFocus = True + ClipChildren = True + Position.Y = 63.000000000000000000 Size.Width = 970.000000000000000000 Size.Height = 282.000000000000000000 + Size.PlatformDefault = False + TabOrder = 0 + RowCount = 0 + Options = [ColumnResize, ColumnMove, ColLines, RowLines, Tabs, Header, HeaderClick, AutoDisplacement] Viewport.Width = 970.000000000000000000 Viewport.Height = 282.000000000000000000 + inherited IntegerColumn1: TIntegerColumn + Header = #1055#1086#1088#1090 + HeaderSettings.TextSettings.WordWrap = False + end + inherited StringColumn1: TStringColumn + Header = #1058#1080#1087 + HeaderSettings.TextSettings.WordWrap = False + end inherited StringColumn2: TStringColumn + Header = #1057#1089#1099#1083#1082#1072' '#1076#1083#1103' OBS' + HeaderSettings.TextSettings.WordWrap = False Size.Width = 200.000000000000000000 end end inherited btnCreateOBSChat: TButton Images = ImageList1 ImageIndex = 13 + Position.X = 8.000000000000000000 + Position.Y = 8.000000000000000000 Size.Width = 94.000000000000000000 + Size.Height = 22.000000000000000000 Size.PlatformDefault = False TabOrder = 2 + Text = #1057#1086#1079#1076#1072#1090#1100' '#1095#1072#1090 + TextSettings.Trimming = None end inherited btnDeleteeChat: TButton Anchors = [akTop, akRight] Images = ImageList1 ImageIndex = 4 Position.X = 882.000000000000000000 + Position.Y = 8.000000000000000000 TabOrder = 3 + Text = #1059#1076#1072#1083#1080#1090#1100 + TextSettings.Trimming = None OnClick = frOBS1btnDeleteeChatClick end inherited Label1: TLabel - TabOrder = 10 + Position.X = 8.000000000000000000 + Position.Y = 38.000000000000000000 + Text = #1057#1086#1079#1076#1072#1085#1085#1099#1077' '#1095#1072#1090#1099':' + TabOrder = 13 end inherited btnCreateOBSNotify: TButton Images = ImageList1 ImageIndex = 24 Position.X = 110.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 146.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 4 + Text = #1057#1086#1079#1076#1072#1090#1100' '#1086#1087#1086#1074#1077#1097#1077#1085#1080#1077 + TextSettings.Trimming = None end inherited btnCreateOBSKandinsky: TButton Images = ImageList1 ImageIndex = 5 Position.X = 264.000000000000000000 + Position.Y = 8.000000000000000000 + Size.Width = 147.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 5 + Text = #1057#1086#1079#1076#1072#1090#1100' '#1050#1072#1085#1076#1080#1085#1089#1082#1080#1081 + TextSettings.Trimming = None end object btnCreateChat: TButton Images = ImageList1 @@ -498,6 +559,22 @@ object TTW_Bot: TTTW_Bot TextSettings.Trimming = None end end + inline frPlayerWeb1: TfrPlayerWeb + Position.X = 2.000000000000000000 + Position.Y = 353.000000000000000000 + Size.Width = 191.000000000000000000 + Size.Height = 96.000000000000000000 + Size.PlatformDefault = False + inherited Label1: TLabel + Size.Width = 171.000000000000000000 + Size.Height = 39.000000000000000000 + Text = #1057#1089#1099#1083#1082#1072' '#1076#1083#1103' OBS '#1044#1086#1082'-'#1087#1072#1085#1077#1083#1080' YouTube Player' + end + inherited Edit1: TEdit + Position.Y = 69.000000000000000000 + Size.Width = 171.000000000000000000 + end + end end object TabItem6: TTabItem CustomIcon = < @@ -586,7 +663,7 @@ object TTW_Bot: TTTW_Bot item end> TextSettings.Trimming = None - IsSelected = True + IsSelected = False ImageIndex = 23 Size.Width = 101.000000000000000000 Size.Height = 26.000000000000000000 @@ -602,23 +679,34 @@ object TTW_Bot: TTTW_Bot Size.Height = 718.000000000000000000 Size.PlatformDefault = False inherited GroupBox20: TGroupBox + inherited edtMessage: TEdit + TabOrder = 37 + end + inherited edtInterval: TEdit + TabOrder = 38 + end inherited btnAddMessage: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 39 end inherited btnRmMessage: TButton Images = ImageList1 ImageIndex = 4 + TabOrder = 40 end inherited btnEditMessage: TButton Images = ImageList1 ImageIndex = 3 + TabOrder = 41 end inherited btnNotifyTest: TButton Images = ImageList1 ImageIndex = 25 + TabOrder = 42 end inherited sgTimers: TStringGrid + TabOrder = 43 Viewport.Width = 463.000000000000000000 Viewport.Height = 225.000000000000000000 inherited scTimerMessage: TStringColumn @@ -630,19 +718,26 @@ object TTW_Bot: TTTW_Bot end end inherited GroupBox23: TGroupBox + inherited edtBanWords: TEdit + TabOrder = 37 + end inherited btnBanWordsAdd: TButton Images = ImageList1 ImageIndex = 0 + TabOrder = 38 end inherited btnBanWordsEdt: TButton Images = ImageList1 ImageIndex = 3 + TabOrder = 39 end inherited btnBanWordsDel: TButton Images = ImageList1 ImageIndex = 4 + TabOrder = 40 end inherited sgBanWords: TStringGrid + TabOrder = 41 Viewport.Width = 297.000000000000000000 Viewport.Height = 225.000000000000000000 inherited scRegEx: TStringColumn @@ -655,25 +750,26 @@ object TTW_Bot: TTTW_Bot Position.X = 217.000000000000000000 Size.Width = 88.000000000000000000 Size.PlatformDefault = False + TabOrder = 42 + end + inherited Label6: TLabel + TabOrder = 43 end inherited edtBanWordsCheck: TEdit + TabOrder = 44 Size.Width = 201.000000000000000000 end + inherited Label7: TLabel + TabOrder = 45 + end + inherited lBanWordsCheck: TLabel + TabOrder = 46 + end end inherited GroupBox17: TGroupBox - inherited edtCounterName: TEdit - TabOrder = 41 - end - inherited edtCounterTrigger: TEdit - TabOrder = 39 - end - inherited edtCounterCount: TEdit - TabOrder = 38 - end inherited btnCounterAdd: TButton Images = ImageList1 ImageIndex = 0 - TabOrder = 40 end inherited btnCounterDelete: TButton Images = ImageList1 @@ -685,7 +781,7 @@ object TTW_Bot: TTTW_Bot ImageIndex = 0 Position.X = 416.000000000000000000 Size.Width = 22.000000000000000000 - TabOrder = 43 + TabOrder = 42 Text = '' end inherited btnCounterM: TButton @@ -693,16 +789,16 @@ object TTW_Bot: TTTW_Bot ImageIndex = 12 Position.X = 449.000000000000000000 Size.Width = 22.000000000000000000 - TabOrder = 44 + TabOrder = 43 Text = '' end inherited btnCounterEdit: TButton Images = ImageList1 ImageIndex = 3 - TabOrder = 45 + TabOrder = 44 end inherited sgCounter: TStringGrid - TabOrder = 46 + TabOrder = 45 Viewport.Width = 463.000000000000000000 Viewport.Height = 121.000000000000000000 inherited scCounterTrigger: TStringColumn @@ -778,6 +874,7 @@ object TTW_Bot: TTTW_Bot TabOrder = 1 Text = #1055#1086#1076#1082#1083#1102#1095#1080#1090#1100#1089#1103 TextSettings.Trimming = None + OnClick = btnConnectingClick end object Label2: TLabel Position.X = 8.000000000000000000 diff --git a/forms/uGeneral.pas b/forms/uGeneral.pas new file mode 100644 index 0000000..49fc374 --- /dev/null +++ b/forms/uGeneral.pas @@ -0,0 +1,1724 @@ +п»їunit uGeneral; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, DateUtils, + System.Variants, uTTWIRC, uTTWEventSub, uTTWAPI, uSoundManager, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl, + FMX.Controls.Presentation, FMX.StdCtrls, System.ImageList, FMX.ImgList, + FMX.Styles, ShellAPI, StrUtils, IdComponent, uRegExpr, uCustomEmoties, + fSettings, fAI, fNotify, fAutoActions, FMX.ListBox, fLog, uMyTimer, uRecords, + System.Generics.Collections, utts, uGigaChat, uChatAPI, + System.IOUtils, fCommands, uDataBase, FMX.Edit, FMX.Colors, FMX.SpinBox, + windows, System.Skia, FMX.Skia, uCreateChat, uCreateNotify, fOBS, fTTS, + fPlayerWeb, uWebServerKandinsky; + +type + TTTW_Bot = class(TForm) + V: TTabControl; + TabItem1: TTabItem; + TabItem2: TTabItem; + TabItem3: TTabItem; + TabItem4: TTabItem; + frSettings1: TfrSettings; + ImageList1: TImageList; + TabItem5: TTabItem; + Panel1: TPanel; + btnConnecting: TButton; + Label2: TLabel; + Label3: TLabel; + Label5: TLabel; + Label6: TLabel; + Label7: TLabel; + Label8: TLabel; + aiConnecting: TAniIndicator; + Label9: TLabel; + Label10: TLabel; + Label11: TLabel; + Label12: TLabel; + frAI1: TfrAI; + TabItem6: TTabItem; + TabItem7: TTabItem; + TabItem8: TTabItem; + TabItem9: TTabItem; + frNotify1: TfrNotify; + Label1: TLabel; + frAutoActions1: TfrAutoActions; + frOBS1: TfrOBS; + frLog1: TfrLog; + cbTheme: TComboBox; + Label15: TLabel; + frCommands1: TfrCommands; + SpeedButton1: TSpeedButton; + SpeedButton2: TSpeedButton; + SpeedButton3: TSpeedButton; + btnCreateChat: TButton; + frTTS1: TfrTTS; + GroupBox1: TGroupBox; + frPlayerWeb1: TfrPlayerWeb; + procedure cbThemeChange(Sender: TObject); + procedure FormCreate(Sender: TObject); + procedure SpeedButton1Click(Sender: TObject); + procedure SpeedButton3Click(Sender: TObject); + procedure SpeedButton2Click(Sender: TObject); + procedure frSettings1btnDAStartClick(Sender: TObject); + procedure frCommands1btnRandAddClick(Sender: TObject); + procedure frOBS1btnDeleteeChatClick(Sender: TObject); + procedure FormDestroy(Sender: TObject); + + procedure frTTS1btnSendClick(Sender: TObject); + procedure btnConnectingClick(Sender: TObject); + private + { Private declarations } + procedure toSpeech(aText: string); + procedure OnTTWStatus(ASender: TObject; const AStatus: TIdStatus; + const AStatusText: string); + procedure ttwIRCOnMessageRecord(aRecord: TTwitchChatMessage); + procedure PlayNotifySound(aMod, aVip, aSub: Boolean); + procedure CheckBannedWords(const aText, dname, aID: string); + procedure ReadDB(); + function PreprocessMessageText(const aText: string): string; + procedure UpdateWordCounters(const aText: string); + procedure decodeResponseSendToTTW(aResponseText, aCommandText, + adName: string); + function Pars(T_, text, _T: string): string; + procedure gptError(Msg: string); + function ResponsParserGroups(inMess: string): string; + function ResponsParserRandoms(inMess: string): string; + function ResponsParserCounters(inMess: string): string; + function ResponsParserStatic(inMess, adName, aCommandText: string): string; + function ResponsParserAPI(inMess, adName: string): string; + function ResponsParserGPT(inMess, aCommandText: string): string; + function ResponsParserAIGen(inMess, aCommandText: string): string; + function ResponsParserAIPic(inMess, aCommandText, aNick: string): string; + function ResponsParserSounds(inMess: string): string; + function ResponsParserText(inMess: string): string; + function ValidateInput: Boolean; + procedure ConnectProcedure; + procedure DisconnectProcedure; + procedure HandleTimers(Start: Boolean); + procedure ESError(aMsg: string); + procedure ESStatus(Sender: TObject; const ConnectionEvent: String; + StatusCode: Integer; const Description: String); + public + { Public declarations } + procedure toLog(aModule, aMethod, aMessage: string; aCode: Integer); + function FindUserRow(const aNick: string): Integer; + procedure GlobalExceptionHandler(Sender: TObject; E: Exception); + end; + +var + TTW_Bot: TTTW_Bot; + myConst: TConst; + db: TSettingsDatabase; + appconst: TBotAppCfg; + ttw_IRS: TTTW; + ttw_ES: TTTW_ES; + ttw_API: TTTW_API; + userlist: TList; + Kandinsky: TKandinsky_Web; + +implementation + +{$R *.fmx} + +procedure TTTW_Bot.HandleTimers(Start: Boolean); +var + I: Integer; +begin + for I := 0 to high(frAutoActions1.listTimer) do + begin + if Assigned(frAutoActions1.FTimerList[I]) then + begin + if Start and (frAutoActions1.listTimer[I].Enable = 1) then + frAutoActions1.FTimerList[I].StartT + else + frAutoActions1.FTimerList[I].StopT; + end; + end; +end; + +procedure TTTW_Bot.toSpeech(aText: string); +var + tts: ttts; + s, s1: string; +begin + s := ExtractFilePath(ParamStr(0)) + 'piper\piper.exe'; + s1 := ExtractFilePath(ParamStr(0)) + 'piper\voices'; + if frTTS1.cbVoices.ItemIndex = -1 then + exit; + tts := ttts.Create(s, s1); + try + tts.SetModel(frTTS1.cbVoices.text); + tts.TextToSpeech(aText, true); + finally + tts.Free; + end; +end; + +procedure TTTW_Bot.GlobalExceptionHandler(Sender: TObject; E: Exception); +begin + try + TTW_Bot.toLog('GlobalException', E.ClassName, E.Message, 2); + except + // РЅР° случай, если логгер сам кинет исключение + end; +end; + +procedure TTTW_Bot.ConnectProcedure; +var + rid: string; + BotToken, StreamerToken: string; + dol: Integer; +begin + BotToken := frSettings1.edtBotToken.text; + StreamerToken := frSettings1.edtBotTokenStreamer.text; + + // Проверка Р±РѕС‚-токена + if not ttw_API.ValidateTwitchToken('Бот', BotToken, dol) then + raise Exception.Create('Недействительный Р±РѕС‚-токен'); + Label12.text := inttostr(dol); + // Проверка стример-токена (если отличается) + if (BotToken <> StreamerToken) and not ttw_API.ValidateTwitchToken('Стример', + StreamerToken, dol) then + raise Exception.Create('Недействительный стример-токен'); + Label11.text := inttostr(dol); + // Рнициализация Рё подключение РѕСЃРЅРѕРІРЅРѕРіРѕ сервиса + ttw_IRS.Init(frSettings1.edtBotToken.text, frSettings1.edtChannel.text, + frSettings1.edtBotName.text); + + ttw_IRS.Connect; + + // Рнициализация API + try + ttw_API.Init(frSettings1.edtBotClientID.text, frSettings1.edtBotToken.text, + IfThen(frSettings1.edtBotTokenStreamer.text = '', + frSettings1.edtBotToken.text, frSettings1.edtBotTokenStreamer.text), + frSettings1.edtChannel.text, frSettings1.edtBotName.text); + except + on E: Exception do + begin + toLog('uGeneral', 'ConnectProcedure.ttw_API.Init', E.Message, 2); + raise; + end; + end; + + // Обработка ролей + + rid := ttw_API.getRoomAndBot; + if rid = '' then + raise Exception.Create('РќРµ удалось получить Room ID'); + + // Загрузка СЌРјРѕРґР·Рё Рё бейджей + { try + fChatFrame.ChatBadges.Clear; + ttw_API.getCustomChatBadges(fChatFrame.ChatBadges); + ttw_API.getGlobalChatBadges(fChatFrame.ChatBadges); + + fChatFrame.ChatEmotes.Clear; + ttw_API.GetChannelEmotes(fChatFrame.ChatEmotes); + ttw_API.GetGlobalEmotes(fChatFrame.ChatEmotes); + + fChatFrame.BTTV.getGlobal; + fChatFrame.BTTV.getCustom(rid); + + fChatFrame.m7tv.getGlobal; + fChatFrame.m7tv.getCustom(rid); + except + on E: Exception do + begin + fLog.toLog(2, 'uGeneral', 'ConnectProcedure.Emotes', E.Message); + raise; + end; + end; } + + // Рнициализация EventSub + + try + if Assigned(ttw_ES) then + FreeAndNil(ttw_ES); + + ttw_ES := TTTW_ES.Create(Self, frSettings1.edtBotTokenStreamer.text, + frSettings1.edtBotClientID.text, rid); + + // Назначение обработчиков событий + // ttw_ES.OnMessage := fRewards.ESOnMessage; + ttw_ES.OnError := ESError; + // ttw_ES.OnGetCustomReward := fRewards.ESOnGetCustomReward; + ttw_ES.OnStatus := ESStatus; + // ttw_ES.OnRAW := fRewards.ESOnRAW; + // ttw_ES.OnSubOk := fRewards.ESOnSubOk; + // ttw_ES.OnFollow := fWebServerEvents.ESOnFollow; + // ttw_ES.OnSub := fWebServerEvents.ESOnSub; + // ttw_ES.OnGift := fWebServerEvents.ESOnGift; + // ttw_ES.OnRaid := fWebServerEvents.ESOnRaid; } + toLog('uGeneral', 'ConnectProcedure.ttw_ES', 'Создан', 0); + ttw_ES.Connect; + except + on E: Exception do + begin + toLog('uGeneral', 'ConnectProcedure.ttw_ES', E.Message, 2); + raise; + end; + end; + + HandleTimers(true); + + if frSettings1.cbDAAutoLogin.IsChecked then + begin + frSettings1.btnDAGetCodeClick(Self); + end; + +end; + +procedure TTTW_Bot.DisconnectProcedure; +var + I: Integer; +begin + try + toLog('DisconnectProcedure', 'Start', 'Начало процедуры отключения', 3); + // 1. Отключаем IRS + toLog('DisconnectProcedure', 'ttw_IRS.Disconnect', + 'Попытка отключения IRS', 3); + ttw_IRS.Disconnect; + // 2. Обрабатываем EventSub + if Assigned(ttw_ES) then + begin + toLog('DisconnectProcedure', 'ttw_ES.Disconnect', + 'Попытка отключения ES', 3); + ttw_ES.Disconnect; + + // Даем время РЅР° корректное завершение + Sleep(150); + toLog('DisconnectProcedure', 'ttw_ES.Free', 'Освобождение ES', 3); + FreeAndNil(ttw_ES); + end; + // 3. Очищаем интерфейс + { if Assigned(fChatFrame) then + begin + fChatFrame.ChatBadges.Clear; + fChatFrame.ChatEmotes.Clear; + { for I := 0 to fChatFrame.ChatWebServers.Count - 1 do + begin + fChatFrame.ChatWebServers[I].WebServerChat.ActiveServer(false); + end; + end; } + // 4. Останавливаем таймеры + HandleTimers(false); + + frSettings1.FWSClient.Disconnect; + + Label3.text := 'Disconnected'; + Label6.text := 'Disconnected'; + except + on E: Exception do + toLog('DisconnectProcedure', 'General', E.ClassName + ': ' + + E.Message, 2); + end; +end; + +procedure TTTW_Bot.ESError(aMsg: string); +begin + toLog('uGeneral', 'ESError', aMsg, 2); +end; + +procedure TTTW_Bot.ESStatus(Sender: TObject; const ConnectionEvent: String; + StatusCode: Integer; const Description: String); +begin + + Label6.text := ConnectionEvent; +end; + +function TTTW_Bot.ValidateInput: Boolean; +begin + Result := false; + + if Trim(frSettings1.edtBotToken.text) = '' then + begin + ShowMessage('Токен бота обязателен для заполнения!'); + exit; + end; + + if Trim(frSettings1.edtChannel.text) = '' then + begin + ShowMessage('Название канала обязательно для заполнения!'); + exit; + end; + + if (Trim(frSettings1.edtBotClientID.text) = '') then + begin + ShowMessage('Client ID обязателен!'); + exit; + end; + + Result := true; +end; + +procedure TTTW_Bot.btnConnectingClick(Sender: TObject); +var + rid, tb, ts: string; + I: Integer; +begin + if not ValidateInput then + exit; + + btnConnecting.Enabled := false; + aiConnecting.Enabled := true; + aiConnecting.Visible := true; + + try + if btnConnecting.text = 'Подключиться' then + ConnectProcedure + else + DisconnectProcedure; + + TThread.Synchronize(nil, + procedure + begin + btnConnecting.text := IfThen(btnConnecting.text = 'Подключиться', + 'Отключиться', 'Подключиться'); + end); + except + on E: Exception do + + ShowMessage('Ошибка подключения: ' + E.Message); + end; + + aiConnecting.Enabled := false; + aiConnecting.Visible := false; + btnConnecting.Enabled := true; + +end; + +procedure TTTW_Bot.cbThemeChange(Sender: TObject); +begin + cbTheme.ItemIndex := cbTheme.Items.IndexOf(cbTheme.text); + if cbTheme.ItemIndex <> -1 then + TStyleManager.SetStyleFromFile(myConst.stlPath + cbTheme.text); + db.WriteSetting('cbTheme', inttostr(cbTheme.ItemIndex)); +end; + +procedure TTTW_Bot.FormCreate(Sender: TObject); +var + Path: string; + + function GetPathToTestExe: string; // вернет папку romaming + begin + Result := GetEnvironmentVariable('APPDATA'); + if Result <> '' then + Result := IncludeTrailingPathDelimiter(Result); + end; + +begin + + myConst.GeneralPath := ExtractFilePath(ParamStr(0)); + myConst.AppDataPath := GetPathToTestExe + 'TTW_Bot\'; + + if not DirectoryExists(myConst.AppDataPath) then + CreateDir(myConst.AppDataPath); + + myConst.DBPath := myConst.AppDataPath + 'settings.db'; + + if not DirectoryExists(myConst.AppDataPath + 'fonts') then + CreateDir(myConst.AppDataPath + 'fonts'); + myConst.fontsPath := myConst.AppDataPath + 'fonts\'; + + if not DirectoryExists(myConst.AppDataPath + 'imgs') then + CreateDir(myConst.AppDataPath + 'imgs'); + myConst.imgsPath := myConst.AppDataPath + 'imgs\'; + + if not DirectoryExists(myConst.AppDataPath + 'sounds') then + CreateDir(myConst.AppDataPath + 'sounds'); + myConst.soundsPath := myConst.AppDataPath + 'sounds\'; + + if not DirectoryExists(myConst.AppDataPath + 'stl') then + CreateDir(myConst.AppDataPath + 'stl'); + myConst.stlPath := myConst.AppDataPath + 'stl\'; + + if not DirectoryExists(myConst.AppDataPath + 'ytSongs') then + CreateDir(myConst.AppDataPath + 'ytSongs'); + myConst.ytSongsPath := myConst.AppDataPath + 'ytSongs\'; + + myConst.SilentPlay := myConst.GeneralPath + 'SilentPlayer.exe'; + myConst.ytPlay := myConst.GeneralPath + 'Player.exe'; + myConst.cfg1 := myConst.GeneralPath + 'botapp.cfg'; + + db := TSettingsDatabase.Create(myConst.DBPath); + frAutoActions1.FTimerList := TObjectList.Create(false); + ReadDB; + frCommands1.frsgSounds.ObjectRecord := frCommands1.listSounds; + frCommands1.frsgSounds.TableName := 'listSounds'; + frCommands1.frsgSounds.UpdateGrid; + frCommands1.frsgFiles.ObjectRecord := frCommands1.listFiles; + frCommands1.frsgFiles.TableName := 'listFiles'; + frCommands1.frsgFiles.UpdateGrid; + frCommands1.frsgNeiro.ObjectRecord := frCommands1.listNeiro; + frCommands1.frsgNeiro.TableName := 'listNeiro'; + frCommands1.frsgNeiro.UpdateGrid; + + for Path in TDirectory.GetFiles(myConst.stlPath) do + cbTheme.Items.Add(ExtractFileName(Path)); + cbTheme.ItemIndex := strtoint(db.ReadSetting('cbTheme', '-1')); + frLog1.FLogList := TList.Create; + + frPlayerWeb1.Init; + + userlist := TList.Create; + ttw_IRS := TTTW.Create(Self); + ttw_IRS.OnMessageRecord := ttwIRCOnMessageRecord; + ttw_IRS.OnLog := toLog; + ttw_IRS.OnStatus := OnTTWStatus; + + ttw_API := TTTW_API.Create(Self); + + if (frAI1.edtKandiKey.text <> '') and (frAI1.edtKandiSecret.text <> '') then + begin + try + Kandinsky := TKandinsky_Web.Create(frAI1.edtKandiKey.text, + frAI1.edtKandiSecret.text); + Kandinsky.ActiveServer(true); + toLog('uAI', 'FormCreate', 'Kandinsky Создан', 0); + finally + + end; + end; +end; + +procedure TTTW_Bot.OnTTWStatus(ASender: TObject; const AStatus: TIdStatus; +const AStatusText: string); +begin + Label3.text := AStatusText; +end; + +procedure TTTW_Bot.FormDestroy(Sender: TObject); +begin + DisconnectProcedure; + if Assigned(ttw_IRS) then + ttw_IRS.Free; + if Assigned(ttw_ES) then + ttw_ES.free; + if Assigned(Kandinsky) then + Kandinsky.Free; + if Assigned(ttw_API ) then + ttw_API.Free; + frSettings1.Destroy; + FreeAndNil(db); + FreeAndNil(frAutoActions1.FTimerList); + FreeAndNil(frLog1.FLogList); + inherited; +end; + +function TTTW_Bot.FindUserRow(const aNick: string): Integer; +var + I: Integer; +begin + Result := -1; + for I := 0 to userlist.Count - 1 do + if userlist[I].login = LowerCase(aNick) then + begin + Result := I; + Break; + end; +end; + +function TTTW_Bot.PreprocessMessageText(const aText: string): string; +begin + Result := LowerCase(aText); + Result := StringReplace(Result, #$D, '', [rfReplaceAll]); + Result := StringReplace(Result, 'у ЂЂ', '', [rfReplaceAll]); + Result := Trim(Result) + ' '; +end; + +procedure TTTW_Bot.UpdateWordCounters(const aText: string); +var + I: Integer; + Words: TArray; + mode: Boolean; + + procedure IncrementCounter(ARow: Integer); + var + Count: Integer; + begin + Count := frAutoActions1.listCounters[ARow].Count + 1; + frAutoActions1.listCounters[ARow].Count := Count; + db.SaveRecordArray('listCounters', frAutoActions1.listCounters); + frAutoActions1.UpdateGridFromArray; + end; + + function StringToArray(const input: string): TArray; + var + Delimiter: char; + Words: TArray; + I: Integer; + begin + Delimiter := ','; + Words := input.Split([Delimiter]); + SetLength(Result, Length(Words)); + for I := 0 to High(Words) do + Result[I] := Words[I].Trim; + end; + + function ContainsAnyWord(const str: string; + const Words: array of string): Boolean; + var + I: Integer; + begin + for I := Low(Words) to High(Words) do + begin + if Pos(AnsiLowerCase(Words[I]), AnsiLowerCase(str)) > 0 then + begin + Result := true; + exit; + end; + end; + Result := false; + end; + +begin + for I := 0 to high(frAutoActions1.listCounters) do + begin + mode := frAutoActions1.listCounters[I].auto = 1; + if mode then + begin + Words := StringToArray(frAutoActions1.listCounters[I].trigger); + if ContainsAnyWord(aText, Words) then + IncrementCounter(I); + end; + end; +end; + +procedure TTTW_Bot.CheckBannedWords(const aText, dname, aID: string); +var + I: Integer; + rx: TRegExpr; +begin + rx := TRegExpr.Create; + try + for I := 0 to high(frAutoActions1.listBanWords) do + begin + rx.Expression := frAutoActions1.listBanWords[I].regexp; + if rx.Exec(aText) then + begin + if aID = '0' then + ttw_API.banUserTime(dname, 86400) + else + ttw_API.banUserTime(aID, 86400); + Break; + end; + end; + finally + rx.Free; + end; +end; + +procedure TTTW_Bot.ttwIRCOnMessageRecord(aRecord: TTwitchChatMessage); +// обработка РЅРѕРІРѕРіРѕ сообщения +var + processedText: string; + firstWord: string; + commandText: string; + responseText, s: string; + u: TUser; + I: Integer; + + function ExtractFirstWord(var aText: string): string; + var + posSpace: Integer; + begin + posSpace := Pos(' ', aText); + if posSpace > 0 then + begin + Result := Copy(aText, 1, posSpace - 1); + Result := StringReplace(Result, ',', '', [rfReplaceAll]); + Result := StringReplace(Result, '.', '', [rfReplaceAll]); + Result := StringReplace(Result, '?', '', [rfReplaceAll]); + end + else + Result := aText; + end; + function IsCommand(const aWord: string): Boolean; + begin + Result := (Length(aWord) > 0) and (aWord[1] = '!'); + end; + + function ExtractCommandText(const aText, aFirstWord: string): string; + begin + Result := Copy(aText, Length(aFirstWord) + 1, Length(aText)); + end; + + function ProcessCommand(const aCommand: string): string; + var + I: Integer; + begin + Result := ''; + for I := 0 to high(frCommands1.listCommands) do + begin + if ContainsText(aCommand, frCommands1.listCommands[I].R1) then + begin + Result := frCommands1.listCommands[I].R2; + Break; + end; + end; + end; + +begin + if aRecord.Username = '' then + exit; + I := FindUserRow(aRecord.Username); + if I <> -1 then + begin // user есть + u := userlist[I]; + end + else + begin + u.ID := aRecord.UserId; + u.login := aRecord.Username; + u.isVIP := false; + u.isModer := false; + u.isO := false; + u.created_at := StrToDate('01.01.1990'); + u.follow_at := StrToDate('01.01.1990'); + u.isO_today := false; + userlist.Add(u); + end; + I := FindUserRow(aRecord.Username); + if (u.isO) and (NOT u.isO_today) then + begin + ttw_API.shoutouts(u.ID); + u.isO_today := true; + end; + userlist[I] := u; + + processedText := PreprocessMessageText(aRecord.Message); + UpdateWordCounters(processedText); + CheckBannedWords(processedText, aRecord.DisplayName, aRecord.UserId); + + if (frCommands1.cbTextToSpeach.IsChecked) and (processedText[1] = '!') and + (processedText[2] = '!') and (processedText[3] = '!') then + begin + s := StringReplace(processedText, '!!!', '', [rfReplaceAll]); + s := Trim(s); + toSpeech(s); + exit; + end; + + firstWord := ExtractFirstWord(processedText); + if IsCommand(firstWord) then + begin + commandText := ExtractCommandText(processedText, firstWord); + responseText := ProcessCommand(firstWord); + decodeResponseSendToTTW(responseText, commandText, aRecord.DisplayName); + end; + TThread.Queue(nil, + procedure + begin + frOBS1.MsgToWebServer(aRecord); + end); + PlayNotifySound((aRecord.Moder = 1), (aRecord.Vip = 1), + (aRecord.Subscriber = 1)); +end; + +procedure TTTW_Bot.PlayNotifySound(aMod, aVip, aSub: Boolean); +var + s: string; + sm: TSongMachine; +begin + + sm := TSongMachine.Create; + try + if (aMod) and (frNotify1.chEnNotifyMod.IsChecked) and + (frNotify1.edtNotifyFileNameMod.text <> '') then + begin + if frNotify1.cbNotifyFileAgain1.IsChecked then + s := frNotify1.edtNotifyFileName.text + else + s := frNotify1.edtNotifyFileNameMod.text; + + sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeMod.Value))); + exit; + end; + if (aVip) and (frNotify1.chEnNotifyVip.IsChecked) and + (frNotify1.edtNotifyFileNameVip.text <> '') then + begin + if frNotify1.cbNotifyFileAgain2.IsChecked then + s := frNotify1.edtNotifyFileName.text + else + s := frNotify1.edtNotifyFileNameVip.text; + sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeVip.Value))); + exit; + end; + if (aSub) and (frNotify1.chEnNotifySub.IsChecked) and + (frNotify1.edtNotifyFileNameSub.text <> '') then + begin + if frNotify1.cbNotifyFileAgain3.IsChecked then + s := frNotify1.edtNotifyFileName.text + else + s := frNotify1.edtNotifyFileNameSub.text; + sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolumeSub.Value))); + exit; + end; + if (frNotify1.chEnNotify.IsChecked) and + (frNotify1.edtNotifyFileName.text <> '') then + begin + s := frNotify1.edtNotifyFileName.text; + sm.PlaySilent(s, inttostr(round(frNotify1.tbNotifyVolume.Value))); + exit; + end; + finally + sm.Free; + end; +end; + +procedure TTTW_Bot.frCommands1btnRandAddClick(Sender: TObject); +begin + frCommands1.btnRandAddClick(Sender); + +end; + +procedure TTTW_Bot.frOBS1btnDeleteeChatClick(Sender: TObject); +begin + frOBS1.btnDeleteeChatClick(Sender); + +end; + +procedure TTTW_Bot.frSettings1btnDAStartClick(Sender: TObject); +begin + frSettings1.btnDAStartClick(Sender); + +end; + +procedure TTTW_Bot.frTTS1btnSendClick(Sender: TObject); +begin + frTTS1.btnSendClick(Sender); + +end; + +procedure TTTW_Bot.ReadDB; + + function XorDecryptToStrings(const InputFile, Key: string): TStrings; + var + InStream: TFileStream; + MemStream: TMemoryStream; + KeyBytes: TBytes; + KeyLen, KeyIndex: Integer; + B: Byte; + begin + KeyBytes := TEncoding.ANSI.GetBytes(Key); + KeyLen := Length(KeyBytes); + if KeyLen = 0 then + raise Exception.Create('Ключ РЅРµ может быть пустым'); + + InStream := TFileStream.Create(InputFile, fmOpenRead); + try + MemStream := TMemoryStream.Create; + try + KeyIndex := 0; + while InStream.Position < InStream.Size do + begin + InStream.ReadBuffer(B, 1); + B := B xor KeyBytes[KeyIndex]; + MemStream.WriteBuffer(B, 1); + KeyIndex := (KeyIndex + 1) mod KeyLen; + end; + MemStream.Position := 0; + Result := TStringList.Create; + try + Result.LoadFromStream(MemStream, TEncoding.ANSI); + except + Result.Free; // Освобождаем РїСЂРё ошибке загрузки + raise; + end; + finally + MemStream.Free; + end; + finally + InStream.Free; + end; + end; +// Загрузка компонентов настроек (TEdit, TCheckBox) + procedure LoadSettingsComponents; + var + I: Integer; + c: TComponent; + begin + for I := 0 to frSettings1.ComponentCount - 1 do + begin + c := frSettings1.Components[I]; + if c is TEdit then + TEdit(c).text := db.ReadSetting(TEdit(c).Name) + else if c is TCheckBox then + TCheckBox(c).IsChecked := (db.ReadSetting(TCheckBox(c).Name) = 'True'); + end; + db.FChannel := frSettings1.edtChannel.text; + end; + +// Загрузка данных РІ РіСЂРёРґС‹ команд + procedure LoadGridsData; + begin + db.LoadRecordArray('RandomCounters', + frCommands1.RandomCounters); + db.LoadRecordArray('listSounds', frCommands1.listSounds); + db.LoadRecordArray('listFiles', frCommands1.listFiles); + db.LoadRecordArray('listNeiro', frCommands1.listNeiro); + db.LoadRecordArray('listCommands', frCommands1.listCommands); + frCommands1.UpdateGridFromArray; + + end; + +// Загрузка СЃРїРёСЃРєР° РіСЂСѓРїРї + procedure LoadGroupNames; + begin + db.getGroupName(frCommands1.frGroupsRequest1.lbRandomGroup.Items); + end; + +// Загрузка зашифрованного конфига + procedure LoadEncryptedConfig; + var + tempList: TStrings; // Временный СЃРїРёСЃРѕРє для результата + I: Integer; + begin + if not FileExists(myConst.cfg1) then + exit; + + tempList := nil; // Рнициализация + try + tempList := XorDecryptToStrings(myConst.cfg1, 'fgvasrgEFAXFAFAS'); + + for I := 0 to tempList.Count - 1 do + begin + var + eqPos := Pos('=', tempList[I]); + if eqPos > 0 then + begin + var + Key := Trim(Copy(tempList[I], 1, eqPos - 1)); + var + Value := Trim(Copy(tempList[I], eqPos + 1, MaxInt)); + + if Key = 'k1' then + appconst.TTV_ClientID := Value + else if Key = 'k2' then + appconst.AI_GigaChat_AC := Value + else if Key = 'k3' then + appconst.AI_GigaChat_ClientID := Value + else if Key = 'k4' then + appconst.AI_ChatGPT_Token := Value + else if Key = 'k5' then + appconst.AI_DeepSeec_Token := Value + else if Key = 'k6' then + appconst.DA_ClientID := Value + else if Key = 'k7' then + appconst.DA_Sicret := Value + else if Key = 'k8' then + appconst.DA_URL := Value; + end; + end; + + frSettings1.btnGetClientID.Visible := (appconst.TTV_ClientID <> ''); + frAI1.btnGetAIDef.Visible := ((appconst.AI_GigaChat_AC <> '') and + (appconst.AI_GigaChat_ClientID <> '')) or + (appconst.AI_ChatGPT_Token <> '') or (appconst.AI_DeepSeec_Token <> ''); + frSettings1.btnGetDADef.Visible := (appconst.DA_ClientID <> '') and + (appconst.DA_Sicret <> '') and (appconst.DA_URL <> ''); + finally + tempList.Free; // Важно: освобождаем временный СЃРїРёСЃРѕРє! + end; + end; +// Загрузка настроек уведомлений + procedure LoadNotifySettings; + var + I: Integer; + c: TComponent; + begin + for I := 0 to frNotify1.ComponentCount - 1 do + begin + c := frNotify1.Components[I]; + if c is TEdit then + TEdit(c).text := db.ReadSetting(TEdit(c).Name) + else if c is TCheckBox then + TCheckBox(c).IsChecked := (db.ReadSetting(TCheckBox(c).Name) = 'True') + else if c is TSwitch then + TSwitch(c).IsChecked := (db.ReadSetting(TSwitch(c).Name) = 'True') + else if c is TTrackBar then + TTrackBar(c).Value := + strtoint(db.ReadSetting(TTrackBar(c).Name, '100')); + end; + end; + +// Загрузка настроек РР + procedure LoadAISettings; + var + I: Integer; + c: TComponent; + ii: Integer; + + // Настройки GigaChat + procedure SetupGigaChatSettings; + begin + frAI1.rbGC.IsChecked := true; + frAI1.Label45.text := 'ClientID'; + frAI1.Label47.text := 'Autorization Code'; + frAI1.Label1.Visible := false; + frAI1.edtAIP2.Visible := true; + frAI1.edtAIP2.Password := true; + frAI1.edtAIP3.Visible := false; + frAI1.cbOllama.Visible := false; + end; + + // Настройки DeepSeek + procedure SetupDeepSeekSettings; + begin + frAI1.rbDS.IsChecked := true; + frAI1.Label45.text := 'API Token'; + frAI1.Label47.text := ''; + frAI1.Label1.Visible := false; + frAI1.edtAIP2.Visible := false; + frAI1.edtAIP3.Visible := false; + frAI1.cbOllama.Visible := false; + end; + + // Настройки ChatGPT + procedure SetupChatGPTSettings; + begin + frAI1.rbCG.IsChecked := true; + frAI1.Label45.text := 'API Token'; + frAI1.Label47.text := ''; + frAI1.Label1.Visible := false; + frAI1.edtAIP2.Visible := false; + frAI1.edtAIP3.Visible := false; + frAI1.cbOllama.Visible := false; + end; + + // Настройки кастомного РР + procedure SetupCustomAISettings; + begin + frAI1.RBCustom.IsChecked := true; + frAI1.Label45.text := 'API Token'; + frAI1.Label47.text := 'URL'; + frAI1.Label1.Visible := true; + frAI1.edtAIP2.Visible := true; + frAI1.edtAIP2.Password := false; + frAI1.edtAIP3.Visible := true; + frAI1.cbOllama.Visible := true; + frAI1.cbOllama.IsChecked := db.ReadSetting(frAI1.cbOllama.Name) = '1'; + end; + + begin + for I := 0 to frAI1.ComponentCount - 1 do + begin + c := frAI1.Components[I]; + if c is TEdit then + TEdit(c).text := db.ReadSetting(TEdit(c).Name) + else if c is TCheckBox then + TCheckBox(c).IsChecked := db.ReadSetting(TCheckBox(c).Name) = '1'; + end; + + ii := strtoint(db.ReadSetting('aiIndex', '0')); + case ii of + 0: + SetupGigaChatSettings; + 1: + SetupDeepSeekSettings; + 2: + SetupChatGPTSettings; + 3: + SetupCustomAISettings; + end; + + frSettings1.Init; + end; + +// Загрузка РіСЂРёРґРѕРІ автоматических действий + procedure LoadAutoActionsGrids; + begin + db.LoadRecordArray('listTimer', frAutoActions1.listTimer); + db.LoadRecordArray('listBanWords', frAutoActions1.listBanWords); + db.LoadRecordArray('listCounters', frAutoActions1.listCounters); + frAutoActions1.initTimers; + frAutoActions1.UpdateGridFromArray; + end; + +// Загрузка интеграций СЃ ОБС + procedure LoadOBSGrids; + var i:integer; + begin + db.LoadRecordArray('listChats', frOBS1.listChats); + + frOBS1.BTTV := TBTTV.Create; + frOBS1.m7tv := t7tv.Create; + frOBS1.ChatBadges := Tlist.Create; + frOBS1.ChatEmotes := Tlist.Create; + frOBS1.ChatWebServers := Tlist.Create; + + for I := 0 to High(frOBS1.listChats) do + begin + frOBS1.CreateWebChat(frOBS1.listChats[i]); + end; + + db.LoadRecordArray('listNotify', frOBS1.listNotify); + db.LoadRecordArray('listKandinsky', frOBS1.listKandinsky); + frOBS1.UpdateGridFromArray; + end; + +begin + LoadSettingsComponents; + LoadGridsData; + LoadGroupNames; + LoadEncryptedConfig; + + LoadNotifySettings; + LoadAISettings; + LoadOBSGrids; + LoadAutoActionsGrids; +end; + +procedure TTTW_Bot.SpeedButton1Click(Sender: TObject); +begin + ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/incadence'), + nil, nil, 1); +end; + +procedure TTTW_Bot.SpeedButton2Click(Sender: TObject); +begin + // https://www.twitch.tv/kuznecogr + ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/kuznecogr'), + nil, nil, 1); +end; + +procedure TTTW_Bot.SpeedButton3Click(Sender: TObject); +begin + // https://www.flaticon.com/ru/authors/karacis + ShellExecute(0, 'open', + pwidechar('https://www.flaticon.com/ru/authors/karacis'), nil, nil, 1); +end; + +procedure TTTW_Bot.toLog(aModule, aMethod, aMessage: string; aCode: Integer); +begin + TThread.Synchronize(nil, + procedure + var + ml: TRLog; + begin + // Рнициализация всех полей записи + ml.rTime := Now; + case aCode of + 0: + ml.rType := 'INFO'; + 1: + ml.rType := 'WARNING'; + 2: + ml.rType := 'ERROR'; + 3: + ml.rType := 'DEBUG'; + else + ml.rType := 'UNKNOWN'; + end; + ml.rModule := aModule; // string + ml.rMethod := aMethod; // string + ml.rMessage := aMessage; // string + // Добавляем запись РІ СЃРїРёСЃРѕРє + frLog1.FLogList.Add(ml); + // Обновляем РіСЂРёРґ + frLog1.UpdateGridFilters; + end); +end; + +procedure TTTW_Bot.decodeResponseSendToTTW(aResponseText, aCommandText, + adName: string); +var + res: string; + + ID: string; + RowIndex: Integer; +begin + + res := ''; + res := aResponseText; + RowIndex := FindUserRow(adName); + ID := userlist[RowIndex].ID; + if ID = '' then + ID := '0'; + // ----------------------------------------Группы ответов + res := ResponsParserGroups(res); + // ----------------------------------------рандомы + res := ResponsParserRandoms(res); + // ----------------------------------------счетчики + res := ResponsParserCounters(res); + // ----------------------------------------константы + res := ResponsParserStatic(res, adName, aCommandText); + // ----------------------------------------апи команды + res := ResponsParserAPI(res, adName); + // ----------------------------------------GPT + res := ResponsParserGPT(res, aCommandText); + // ----------------------------------------Р·РІСѓРєРё + res := ResponsParserSounds(res); + // ----------------------------------------text + res := ResponsParserText(res); + // ----------------------------------------AIGen + res := ResponsParserAIGen(res, aCommandText); + // ----------------------------------------AIPic + res := ResponsParserAIPic(res, aCommandText, adName); + if res <> '' then + ttw_IRS.sendMessage(res); +end; + +function TTTW_Bot.Pars(T_, text, _T: string): string; +var + A, B: Integer; +begin + Result := ''; + A := Pos(T_, text); + if A = 0 then + exit; + A := A + Length(T_); + B := Pos(_T, text, A); + if B > 0 then + Result := Copy(text, A, B - A); +end; + +function TTTW_Bot.ResponsParserGroups(inMess: string): string; +var + ss, ss2, res: string; + + sl: TStringList; +begin + res := inMess; + while Pos('{{', res) <> 0 do + begin + ss := Pars('{{', res, '}}'); + sl := TStringList.Create; + randomize; + db.getGroupResponse(ss, sl); + ss2 := sl[random(sl.Count)]; + res := StringReplace(res, '{{' + ss + '}}', ss2, [rfReplaceAll]); + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserSounds(inMess: string): string; +var + ss, res: string; + I, p: Integer; + sm: TSongMachine; +begin + res := inMess; + sm := TSongMachine.Create; + try + if ContainsText(res, '||') then + begin + // Находим позицию первого '||' + p := Pos('||', res); + // Копируем РІСЃРµ, что идет после первого '||' + ss := Copy(res, p + 2, Length(res) - p - 1); + // +2 чтобы пропустить первый '||' + // Находим позицию второго '||' + p := Pos('||', ss); + if p > 0 then + begin + // Копируем текст между '||' + ss := Copy(ss, 1, p - 1); + if ss <> '' then + begin + // Рщем значение РІ sgSAFiles + for I := 0 to high(frCommands1.listSounds) do + begin + if frCommands1.listSounds[I].R1 = ss then + begin + if FileExists(frCommands1.listSounds[I].R2) then + begin + { PlaySong(sgSAFiles.Cells[1, i], + inttostr(round(tbSoundVolume.Value)), True); } + sm.PlayPublic(frCommands1.listSounds[I].R2, + inttostr(round(100))); + end + else + begin + toLog('uGeneral', 'ResponsParserSounds', + 'Файл "' + frCommands1.listSounds[I].R1 + '" РЅРµ найден: ' + + frCommands1.listSounds[I].R2, 2); + end; + Break; + end; + end; + // Удаляем обработанный фрагмент РёР· РёСЃС…РѕРґРЅРѕР№ строки + res := StringReplace(res, '||' + ss + '||', '', [rfReplaceAll]); + end; + end; + end; + finally + sm.Free; + end; + + Result := res; +end; + +function TTTW_Bot.ResponsParserText(inMess: string): string; +var + ss, res, fn, rres: string; + sll: TStringList; + I, p: Integer; +begin + res := inMess; + if ContainsText(res, '|(') then + begin + p := Pos('|(', res); + ss := Copy(res, p, Length(res) - p); + p := Pos('|(', ss, 2); + ss := Copy(ss, 1, p + 1); + ss := StringReplace(ss, '|(', '', [rfReplaceAll]); + if ss <> '' then + begin + for I := 0 to high(frCommands1.listFiles) do + begin + if frCommands1.listFiles[I].R1 = ss then + begin + fn := frCommands1.listFiles[I].R2; + Break; + end; + end; + sll := TStringList.Create; + try + sll.LoadFromFile(fn, TEncoding.UTF8); + rres := sll.text; + finally + sll.Free; + end; + if rres <> '' then + begin + if Length(rres) > 450 then + begin + rres := Copy(rres, 1, 450); + rres := rres + '...'; + end; + rres := StringReplace(rres, #13#10, ' ', [rfReplaceAll]); + res := StringReplace(res, '|(' + ss + '|(', rres, [rfReplaceAll]); + end; + end; + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserRandoms(inMess: string): string; +var + r, res: string; + I: Integer; + + function RandomInRange(MinValue, MaxValue: Integer): Integer; + begin + randomize; + Result := random(MaxValue - MinValue + 1) + MinValue; + end; + +begin + res := inMess; + while ContainsText(res, '[[') do + begin + r := Pars('[[', res, ']]'); + for I := 0 to high(frCommands1.RandomCounters) do + begin + if frCommands1.RandomCounters[I].rndName = r then + begin + res := StringReplace(res, '[[' + r + ']]', + inttostr(RandomInRange(frCommands1.RandomCounters[I].Ot, + frCommands1.RandomCounters[I].ToValue)), [rfReplaceAll]); + Break; + end; + end; + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserCounters(inMess: string): string; +var + r, res: string; + I: Integer; +begin + res := inMess; + while ContainsText(res, '``') do + begin + r := Pars('``', res, '``'); + for I := 0 to high(frAutoActions1.listCounters) do + begin + if frAutoActions1.listCounters[I].counterName = r then + begin + res := StringReplace(res, '``' + r + '``', + inttostr(frAutoActions1.listCounters[I].Count), [rfReplaceAll]); + Break; + end; + end; + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserStatic(inMess, adName, + aCommandText: string): string; +var + res, RandomUserName: string; +begin + res := inMess; + res := StringReplace(res, '[USERNAME]', '@' + adName, [rfReplaceAll]); + res := StringReplace(res, '[TO]', aCommandText, [rfReplaceAll]); + if ContainsText(res, '[RANDOMUSER]') then + begin + if Pos('@', aCommandText) <> 0 then + begin + RandomUserName := aCommandText; + end + else + begin + randomize; + RandomUserName := '@' + userlist[random(userlist.Count - 1)].login; + end; + res := StringReplace(res, '[RANDOMUSER]', RandomUserName, [rfReplaceAll]); + end; + Result := res; +end; + +procedure TTTW_Bot.gptError(Msg: string); +begin + toLog('GPT', 'gptError', Msg, 2); +end; + +function TTTW_Bot.ResponsParserAIGen(inMess, aCommandText: string): string; +var + res, GPTRequest: string; + GigaChat: TGigaChat; + ChatAPI: TChatAPI; + mystr: string; + p, I: Integer; + ss: string; +begin + res := inMess; + if ContainsText(res, '<|') then + begin + // Находим позицию первого '||' + p := Pos('<|', res); + // Копируем РІСЃРµ, что идет после первого '<|' + ss := Copy(res, p + 2, Length(res) - p - 1); + // +2 чтобы пропустить первый '<|' + // Находим позицию второго '<|' + p := Pos('<|', ss); + if p > 0 then + begin + // Копируем текст между '<|' + ss := Copy(ss, 1, p - 1); + if ss <> '' then + begin + // Рщем значение РІ sgSAFiles + for I := 0 to high(frCommands1.listNeiro) do + begin + if frCommands1.listNeiro[I].R1 = ss then + begin + mystr := frCommands1.listNeiro[I].R2; + Break; + end; + end; + // Удаляем обработанный фрагмент РёР· РёСЃС…РѕРґРЅРѕР№ строки + // res := StringReplace(res, '<|' + ss + '<|', mystr, [rfReplaceAll]); + end; + end; + end; + mystr := StringReplace(mystr, '[UT]', aCommandText, [rfReplaceAll]); + + mystr := StringReplace(mystr, '[AI]', '', [rfReplaceAll]); + if (frAI1.edtAIP1.text = '') then + begin + GPTRequest := + 'тут должен быть ответ нейросети, РЅРѕ стример зажал логиниться'; + end + else + begin + if frAI1.rbGC.IsChecked then // GigaChat + begin + GigaChat := TGigaChat.Create(Self, frAI1.edtAIP1.text, frAI1.edtAIP2.text, + frAI1.edtGPTPrefix.text); + try + try + GPTRequest := GigaChat.GetGPTRequest + ('https://gigachat.devices.sberbank.ru/api/v1/chat/completions', + 'GigaChat', mystr); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserAIGen.gigachat', E.Message, 2); + end; + finally + GigaChat.Destroy; + end; + end; + if frAI1.rbDS.IsChecked then // DeepSeek + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + ChatAPI.OnError := gptError; + try + GPTRequest := ChatAPI.GetGPTRequest + ('https://api.deepseek.com/chat/completions', + 'deepseek-chat', mystr); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserAIGen.deepseek', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + if frAI1.rbCG.IsChecked then // ChatGPT + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + try + GPTRequest := ChatAPI.GetGPTRequest + ('https://api.openai.com/v1/chat/completions', + 'gpt-3.5-turbo', mystr); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserAIGen.openai', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + if frAI1.RBCustom.IsChecked then // Custom + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + try + GPTRequest := ChatAPI.GetGPTRequest(frAI1.edtAIP2.text, + frAI1.edtAIP3.text, mystr); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserAIGen.custom', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + + GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); + GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); + if GPTRequest = '' then + GPTRequest := 'РѕР№, кажется нейронка РїРѕРєР° РЅРµ доступна'; + + end; + res := StringReplace(res, '<|' + ss + '<|', GPTRequest, [rfReplaceAll]); + Result := res; +end; + +function TTTW_Bot.ResponsParserAIPic(inMess, aCommandText, + aNick: string): string; +var + res: string; +begin + res := inMess; + if ContainsText(res, '[Kandinsky]') then + begin + Kandinsky.generate(aCommandText, aNick); + res := StringReplace(res, '[Kandinsky]', '', [rfReplaceAll]); + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserAPI(inMess, adName: string): string; +var + res, follow, age, ID: string; + RowIndex: Integer; + u: TUser; + function GetPeriodEnding(n, r: Integer): string; + var + res: array [0 .. 3, 0 .. 2] of string; + begin + res[0, 0] := 'РіРѕРґ'; + res[0, 1] := 'РіРѕРґР°'; + res[0, 2] := 'лет'; + + res[1, 0] := 'месяц'; + res[1, 1] := 'месяца'; + res[1, 2] := 'месяцев'; + + res[2, 0] := 'день'; + res[2, 1] := 'РґРЅСЏ'; + res[2, 2] := 'дней'; + + res[3, 0] := 'раз'; + res[3, 1] := 'раза'; + res[3, 2] := 'раз'; + + if (n mod 10 = 1) and (n mod 100 <> 11) then + Result := res[r, 0] + else if (n mod 10 >= 2) and (n mod 10 <= 4) and + ((n mod 100 < 10) or (n mod 100 >= 20)) then + Result := res[r, 1] + else + Result := res[r, 2]; + end; + function GetDateDifference(const inputDate: string): string; + var + currentDate, targetDate: TDateTime; + years, months, days: Integer; + begin + try + targetDate := StrToDate(inputDate); + currentDate := Now; + + years := YearsBetween(currentDate, targetDate); + targetDate := IncYear(targetDate, years); + + months := MonthsBetween(currentDate, targetDate); + targetDate := IncMonth(targetDate, months); + + days := DaysBetween(currentDate, targetDate); + + Result := inttostr(years) + ' ' + GetPeriodEnding(years, 0) + ' ' + + inttostr(months) + ' ' + GetPeriodEnding(months, 1) + ' ' + + inttostr(days) + ' ' + GetPeriodEnding(days, 2); + except + on E: Exception do + begin + toLog('uGeneral', 'ResponsParserAPI.GetDateDifference', E.Message, 2); + Result := ''; + end; + end; + end; + +begin + res := inMess; + RowIndex := FindUserRow(adName); + u := userlist[RowIndex]; + ID := u.ID; + if ContainsText(res, '[FOLLOW]') then + begin + if YearOf(u.follow_at) < 2000 then + u.follow_at := ttw_API.getFollow(ID); + follow := GetDateDifference(DateToStr(u.follow_at)); + res := StringReplace(res, '[FOLLOW]', follow, [rfReplaceAll]); + end; + if ContainsText(res, '[AGE]') then + begin + if YearOf(userlist[RowIndex].created_at) < 2000 then + u := ttw_API.getUserbyLogin(u.login); + age := GetDateDifference(DateToStr(u.created_at)); + res := StringReplace(res, '[AGE]', age, [rfReplaceAll]); + end; + userlist[RowIndex] := u; + if ContainsText(res, '[STAT]') then + begin + var + avg_viewers: Integer; + var + max_viewers: Integer; + var + hours_watched: Integer; + var + followers: Integer; + var + followers_total: Integer; + ttw_API.getTTWStat(db.FChannel, avg_viewers, max_viewers, hours_watched, + followers, followers_total); + var + resultStat: string; + resultStat := 'Статистика канала Р·Р° месяц: Средний онлайн: ' + + inttostr(avg_viewers) + '; Максимальный онлайн: ' + inttostr(max_viewers) + + '; Часов просмотра: ' + inttostr(hours_watched) + + '; Подписчиков Р·Р° месяц: ' + inttostr(followers) + '; Всего подписчиков: ' + + inttostr(followers_total); + res := StringReplace(res, '[STAT]', resultStat, [rfReplaceAll]); + end; + Result := res; +end; + +function TTTW_Bot.ResponsParserGPT(inMess, aCommandText: string): string; +var + res, GPTRequest: string; + GigaChat: TGigaChat; + ChatAPI: TChatAPI; +begin + res := inMess; + + if ContainsText(res, '[AI]') then + begin + + begin + if frAI1.rbGC.IsChecked then // GigaChat + begin + GigaChat := TGigaChat.Create(Self, frAI1.edtAIP1.text, + frAI1.edtAIP2.text, frAI1.edtGPTPrefix.text); + try + try + GPTRequest := GigaChat.GetGPTRequest + ('https://gigachat.devices.sberbank.ru/api/v1/chat/completions', + 'GigaChat', aCommandText); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserGPT.gigachat', E.Message, 2); + end; + finally + GigaChat.Destroy; + end; + end; + if frAI1.rbDS.IsChecked then // DeepSeek + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + ChatAPI.OnError := gptError; + try + GPTRequest := ChatAPI.GetGPTRequest + ('https://api.deepseek.com/chat/completions', 'deepseek-chat', + aCommandText); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserGPT.deepseek', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + if frAI1.rbCG.IsChecked then // ChatGPT + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + try + GPTRequest := ChatAPI.GetGPTRequest + ('https://api.openai.com/v1/chat/completions', 'gpt-3.5-turbo', + aCommandText); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserGPT.openai', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + if frAI1.RBCustom.IsChecked then // Custom + begin + ChatAPI := TChatAPI.Create(Self, frAI1.edtAIP1.text, + frAI1.edtGPTPrefix.text); + try + try + GPTRequest := ChatAPI.GetGPTRequest(frAI1.edtAIP2.text, + frAI1.edtAIP3.text, aCommandText, frAI1.cbOllama.IsChecked); + except + on E: Exception do + toLog('uGeneral', 'ResponsParserGPT.custom', E.Message, 2); + end; + finally + ChatAPI.Destroy; + end; + end; + + GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); + GPTRequest := StringReplace(GPTRequest, 'nn', ' ', [rfReplaceAll]); + if GPTRequest = '' then + GPTRequest := 'РѕР№, кажется нейронка РїРѕРєР° РЅРµ доступна'; + end; + res := StringReplace(res, '[AI]', GPTRequest, [rfReplaceAll]); + end; + Result := res; +end; + +end. diff --git a/uQ.fmx b/forms/uQ.fmx similarity index 100% rename from uQ.fmx rename to forms/uQ.fmx diff --git a/uQ.pas b/forms/uQ.pas similarity index 100% rename from uQ.pas rename to forms/uQ.pas diff --git a/uShowText.fmx b/forms/uShowText.fmx similarity index 100% rename from uShowText.fmx rename to forms/uShowText.fmx diff --git a/uShowText.pas b/forms/uShowText.pas similarity index 100% rename from uShowText.pas rename to forms/uShowText.pas diff --git a/fAI.fmx b/frames/fAI.fmx similarity index 100% rename from fAI.fmx rename to frames/fAI.fmx diff --git a/fAI.pas b/frames/fAI.pas similarity index 100% rename from fAI.pas rename to frames/fAI.pas diff --git a/fAutoActions.fmx b/frames/fAutoActions.fmx similarity index 100% rename from fAutoActions.fmx rename to frames/fAutoActions.fmx diff --git a/fAutoActions.pas b/frames/fAutoActions.pas similarity index 100% rename from fAutoActions.pas rename to frames/fAutoActions.pas diff --git a/fColorSettings.fmx b/frames/fColorSettings.fmx similarity index 100% rename from fColorSettings.fmx rename to frames/fColorSettings.fmx diff --git a/fColorSettings.pas b/frames/fColorSettings.pas similarity index 100% rename from fColorSettings.pas rename to frames/fColorSettings.pas diff --git a/fCommands.fmx b/frames/fCommands.fmx similarity index 97% rename from fCommands.fmx rename to frames/fCommands.fmx index 0e11afd..8f73c80 100644 --- a/fCommands.fmx +++ b/frames/fCommands.fmx @@ -86,6 +86,12 @@ object frCommands: TfrCommands inherited btnRmCommand: TButton OnClick = frContruct1btnRmCommandClick end + object cbTextToSpeach: TCheckBox + Position.X = 272.000000000000000000 + Position.Y = 8.000000000000000000 + TabOrder = 47 + Text = #1054#1079#1074#1091#1095#1082#1072' '#1087#1086#1089#1083#1077' !!!' + end end end object GroupBox9: TGroupBox @@ -115,16 +121,16 @@ object frCommands: TfrCommands Viewport.Height = 116.000000000000000000 end inherited btnRandomAdd: TButton - TabOrder = 34 + TabOrder = 33 end inherited btnRandomDel: TButton - TabOrder = 35 + TabOrder = 34 end inherited btnRmGroup: TButton - TabOrder = 37 + TabOrder = 36 end inherited Label4: TLabel - TabOrder = 39 + TabOrder = 38 end end end diff --git a/fCommands.pas b/frames/fCommands.pas similarity index 99% rename from fCommands.pas rename to frames/fCommands.pas index dfa35f2..005dcee 100644 --- a/fCommands.pas +++ b/frames/fCommands.pas @@ -36,6 +36,7 @@ type frsgNeiro: TfrSimpleGrid; frContruct1: TfrContruct; frGroupsRequest1: TfrGroupsRequest; + cbTextToSpeach: TCheckBox; procedure btnRandAddClick(Sender: TObject); procedure btnRandDelClick(Sender: TObject); procedure frsgSoundsbtnSoundDelClick(Sender: TObject); diff --git a/fContruct.fmx b/frames/fContruct.fmx similarity index 100% rename from fContruct.fmx rename to frames/fContruct.fmx diff --git a/fContruct.pas b/frames/fContruct.pas similarity index 100% rename from fContruct.pas rename to frames/fContruct.pas diff --git a/fFontSettings.fmx b/frames/fFontSettings.fmx similarity index 98% rename from fFontSettings.fmx rename to frames/fFontSettings.fmx index 9b387ad..62f1c81 100644 --- a/fFontSettings.fmx +++ b/frames/fFontSettings.fmx @@ -10,7 +10,7 @@ object frFontSettings: TfrFontSettings Size.PlatformDefault = False TextSettings.Trimming = None Text = #1056#1072#1079#1084#1077#1088' '#1096#1088#1080#1092#1090#1072 - TabOrder = 8 + TabOrder = 7 end object sbFontSize: TSpinBox Touch.InteractiveGestures = [LongTap, DoubleTap] @@ -30,14 +30,14 @@ object frFontSettings: TfrFontSettings Size.Width = 120.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 38 + TabOrder = 37 end object Label49: TLabel Position.X = 116.000000000000000000 Position.Y = 63.000000000000000000 TextSettings.Trimming = None Text = #1062#1074#1077#1090' '#1096#1088#1080#1092#1090#1072 - TabOrder = 37 + TabOrder = 36 end object cbFontStyleDefault: TComboBox Items.Strings = ( diff --git a/fFontSettings.pas b/frames/fFontSettings.pas similarity index 100% rename from fFontSettings.pas rename to frames/fFontSettings.pas diff --git a/fGroupsRequest.fmx b/frames/fGroupsRequest.fmx similarity index 100% rename from fGroupsRequest.fmx rename to frames/fGroupsRequest.fmx diff --git a/fGroupsRequest.pas b/frames/fGroupsRequest.pas similarity index 100% rename from fGroupsRequest.pas rename to frames/fGroupsRequest.pas diff --git a/fLog.fmx b/frames/fLog.fmx similarity index 100% rename from fLog.fmx rename to frames/fLog.fmx diff --git a/fLog.pas b/frames/fLog.pas similarity index 100% rename from fLog.pas rename to frames/fLog.pas diff --git a/fNotify.fmx b/frames/fNotify.fmx similarity index 100% rename from fNotify.fmx rename to frames/fNotify.fmx diff --git a/fNotify.pas b/frames/fNotify.pas similarity index 100% rename from fNotify.pas rename to frames/fNotify.pas diff --git a/fOBS.fmx b/frames/fOBS.fmx similarity index 100% rename from fOBS.fmx rename to frames/fOBS.fmx diff --git a/frames/fOBS.pas b/frames/fOBS.pas new file mode 100644 index 0000000..982fb65 --- /dev/null +++ b/frames/fOBS.pas @@ -0,0 +1,697 @@ +unit fOBS; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, uCustomEmoties, + System.Variants, uWebServerChat, fColorSettings, System.Generics.Collections, + FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, + System.Rtti, FMX.Grid.Style, FMX.Grid, FMX.ScrollBox, FMX.Edit, FMX.Colors, + FMX.ListBox, FMX.EditBox, FMX.SpinBox, FMX.Controls.Presentation, uRecords, + System.Generics.Defaults, fFontSettings; + +type + TChatWebServers = record + port: integer; + MaxMsg: integer; + TimeMsg: integer; + Freez: boolean; + StyleBorderColor: string; + StyleBlockColor: string; + StyleBlockBorderSize: integer; + StyleBlockPadding: integer; + FontStyleDefault: string; + FontColor: string; + BColor: string; + FontSize: integer; + WebServerChat: TTTW_Chat; + end; + +type + TfrOBS = class(TFrame) + sgWebChats: TStringGrid; + btnCreateOBSChat: TButton; + btnDeleteeChat: TButton; + Label1: TLabel; + IntegerColumn1: TIntegerColumn; + StringColumn1: TStringColumn; + StringColumn2: TStringColumn; + btnCreateOBSNotify: TButton; + btnCreateOBSKandinsky: TButton; + procedure btnDeleteeChatClick(Sender: TObject); + procedure btnCreateOBSKandinskyClick(Sender: TObject); + procedure btnCreateOBSChatClick(Sender: TObject); + procedure btnCreateOBSNotifyClick(Sender: TObject); + procedure sgWebChatsCellDblClick(const Column: TColumn; const Row: integer); + private + { Private declarations } + + function checkBttv(aMsg: string): string; + function ReplaceEmotesInMessage(const MessageText, + EmotesString: string): string; + function FindEmoteByID(const ID: string): TEmotes; + function GetBadgesHTML(Badges: string): string; + public + { Public declarations } + listChats: TArray; + listNotify: TArray; + listKandinsky: TArray; + + BTTV: TBTTV; + m7tv: t7tv; + ChatBadges: Tlist; + ChatEmotes: Tlist; + ChatWebServers: Tlist; + procedure MsgToWebServer(const aRecord: TTwitchChatMessage); + procedure CreateWebChat(chatSettings: TOBSChat); + procedure UpdateGridFromArray; + procedure AddChat(newRecord: TOBSChat); + procedure EdtChat(newRecord: TOBSChat; oldPort: integer); + procedure DelChat(aPort: integer); + + procedure AddNotify(newRecord: TOBSNotify); + procedure EdtNotify(newRecord: TOBSNotify; oldPort: integer); + procedure DelNotify(aPort: integer); + + procedure AddKandinsky(newRecord: TOBSKandinsky); + procedure DelKandinsky(aPort: integer); + + end; + +implementation + +{$R *.fmx} + +uses uGeneral, uCreateChat, uCreateNotify; + +{ TfrOBS } + +function TfrOBS.checkBttv(aMsg: string): string; +var + Words: tstringlist; + i: integer; + CurrentWord, Url: string; +begin + Words := tstringlist.Create; + try + // Разбиваем строку на слова по пробелам + Words.Delimiter := ' '; + Words.StrictDelimiter := True; // Игнорировать повторяющиеся пробелы + Words.DelimitedText := aMsg; + + // Обработка слов + for i := 0 to Words.Count - 1 do + begin + CurrentWord := Words[i]; + Url := BTTV.generateURL(CurrentWord); + if Url = '' then + Url := m7tv.generateURL(CurrentWord); + if Url <> '' then + Words[i] := Format('', [Url]); + end; + + // Собираем результат + result := Words.text; + finally + Words.Free; + end; +end; + +function TfrOBS.FindEmoteByID(const ID: string): TEmotes; +var + i: integer; +begin + result.ID := ''; + if not Assigned(ChatEmotes) then + exit; + + for i := 0 to ChatEmotes.Count - 1 do + if ChatEmotes[i].ID = ID then + begin + result := ChatEmotes[i]; + Break; + end; +end; + +function TfrOBS.ReplaceEmotesInMessage(const MessageText, + EmotesString: string): string; +type + TEmotePosition = record + StartPos: integer; + EndPos: integer; + ImageURL: string; + end; +var + Positions: Tlist; + i, ColonPos: integer; + Parts, EmoteData, Ranges: tstringlist; + EmoteID, RangeStr: string; + StartPos, EndPos: integer; + Emote: TEmotes; +begin + result := MessageText; + if EmotesString.IsEmpty then + exit; + + Parts := tstringlist.Create; + EmoteData := tstringlist.Create; + Ranges := tstringlist.Create; + Positions := Tlist.Create; + try + Parts.Delimiter := '/'; + Parts.StrictDelimiter := True; + Parts.DelimitedText := EmotesString; + + for i := 0 to Parts.Count - 1 do + begin + ColonPos := Pos(':', Parts[i]); + if ColonPos = 0 then + Continue; + + EmoteID := Copy(Parts[i], 1, ColonPos - 1); + RangeStr := Copy(Parts[i], ColonPos + 1, MaxInt); + + Ranges.Clear; + Ranges.Delimiter := ','; + Ranges.StrictDelimiter := True; + Ranges.DelimitedText := RangeStr; + + Emote := FindEmoteByID(EmoteID); + if Emote.ID = '' then + Continue; + + for var j := 0 to Ranges.Count - 1 do + begin + EmoteData.Clear; + EmoteData.Delimiter := '-'; + EmoteData.StrictDelimiter := True; + EmoteData.DelimitedText := Ranges[j]; + if EmoteData.Count <> 2 then + Continue; + + if TryStrToInt(EmoteData[0], StartPos) and + TryStrToInt(EmoteData[1], EndPos) then + begin + var + EmotePosition: TEmotePosition; + EmotePosition.StartPos := StartPos; + EmotePosition.EndPos := EndPos; + EmotePosition.ImageURL := Emote.images.Url1x; + + Positions.Add(EmotePosition); + end; + end; + end; + + Positions.Sort(TComparer.Construct( + function(const Left, Right: TEmotePosition): integer + begin + result := Right.StartPos - Left.StartPos; + end)); + + var + SB := TStringBuilder.Create(MessageText); + try + for var Pos in Positions do + begin + if (Pos.StartPos < 0) or (Pos.EndPos >= SB.Length) or + (Pos.StartPos > Pos.EndPos) then + Continue; + + var + Replacement := Format('', + [Pos.ImageURL]); + + SB.Remove(Pos.StartPos, Pos.EndPos - Pos.StartPos + 1); + SB.Insert(Pos.StartPos, Replacement); + end; + result := SB.ToString; + finally + SB.Free; + end; + finally + Parts.Free; + EmoteData.Free; + Ranges.Free; + Positions.Free; + end; +end; + +function TfrOBS.GetBadgesHTML(Badges: string): string; +var + BadgeList: TArray; + CodeParts: TArray; + CurrentCode, SetId, VersionId: string; + Badge: TChatBadge; + Version: TBadgeVersion; + Found: boolean; +begin + // Разбиваем строку на отдельные бейдж-коды + BadgeList := Badges.Split([',']); + + for CurrentCode in BadgeList do + begin + // Разделяем SetId и VersionId + CodeParts := CurrentCode.Split(['/']); + if Length(CodeParts) <> 2 then + Continue; + + SetId := CodeParts[0]; + VersionId := CodeParts[1]; + Found := false; + + // Ищем соответствующий бейдж + for Badge in ChatBadges do + begin + if Badge.SetId = SetId then + begin + // Ищем нужную версию + for Version in Badge.Versions do + begin + if Version.ID = VersionId then + begin + // Формируем HTML-тег + result := result + + Format(' %s', + [Version.ImageUrl1x, Version.Title, Version.Description]); + Found := True; + Break; + end; + end; + if Found then + Break; + end; + end; + + // Если не нашли - добавляем заглушку + if not Found then + result := result + ' '; + end; +end; + +procedure TfrOBS.MsgToWebServer(const aRecord: TTwitchChatMessage); +var + s: string; + ms: TStyleChat; + i: integer; +begin + s := checkBttv(aRecord.Message); + if aRecord.Emotes <> '' then + s := ReplaceEmotesInMessage(s, aRecord.Emotes); + + ms.Nick := GetBadgesHTML(aRecord.Badges) + '' + aRecord.DisplayName + ''; + ms.Context := '' + s + ''; + + for i := 0 to ChatWebServers.Count - 1 do + begin + ms.FontColor := ChatWebServers[i].FontColor; + ms.FontSize := ChatWebServers[i].FontSize; + ms.FontFamily := '''' + ChatWebServers[i].FontStyleDefault + ''';'; + ms.FontFamily := StringReplace(ms.FontFamily, '.ttf', '', [rfReplaceAll]); + ms.BlockColor := ChatWebServers[i].StyleBlockColor; + ms.BlockPadding := ChatWebServers[i].StyleBlockPadding; + ms.MaxMsgCount := ChatWebServers[i].MaxMsg; + ms.TimeMsg := ChatWebServers[i].TimeMsg; + ms.BorderSize := ChatWebServers[i].StyleBlockBorderSize; + ms.BorderColor := ChatWebServers[i].StyleBorderColor; + ms.BColor := ChatWebServers[i].BColor; + ChatWebServers[i].WebServerChat.AddMessage(ms); + end; +end; + +procedure TfrOBS.AddChat(newRecord: TOBSChat); +begin + SetLength(listChats, Length(listChats) + 1); + listChats[High(listChats)] := newRecord; + UpdateGridFromArray; + db.SaveRecordArray('listChats', listChats); + CreateWebChat(newRecord); +end; + +procedure TfrOBS.AddKandinsky(newRecord: TOBSKandinsky); +begin + SetLength(listKandinsky, Length(listKandinsky) + 1); + listKandinsky[High(listKandinsky)] := newRecord; + UpdateGridFromArray; + db.SaveRecordArray('listKandinsky', listKandinsky); +end; + +procedure TfrOBS.AddNotify(newRecord: TOBSNotify); +begin + SetLength(listNotify, Length(listNotify) + 1); + listNotify[High(listNotify)] := newRecord; + UpdateGridFromArray; + db.SaveRecordArray('listNotify', listNotify); +end; + +procedure TfrOBS.btnCreateOBSChatClick(Sender: TObject); +var + dport, i: integer; +begin + dport := 8080; + for i := 0 to sgWebChats.RowCount - 1 do + begin + if strtoint(sgWebChats.Cells[0, i]) >= dport then + dport := strtoint(sgWebChats.Cells[0, i]) + 1; + end; + fCreateChat.sbWebServerPort.Value := dport; + fCreateChat.isEdit := false; + fCreateChat.Show; +end; + +procedure TfrOBS.btnCreateOBSKandinskyClick(Sender: TObject); +var + dport: integer; + i: integer; + rk: TOBSKandinsky; +begin + dport := 8080; + for i := 0 to sgWebChats.RowCount - 1 do + begin + if strtoint(sgWebChats.Cells[0, i]) >= dport then + dport := strtoint(sgWebChats.Cells[0, i]) + 1; + end; + rk.port := dport; + AddKandinsky(rk); + +end; + +procedure TfrOBS.btnCreateOBSNotifyClick(Sender: TObject); +var + dport, i: integer; +begin + dport := 8080; + for i := 0 to sgWebChats.RowCount - 1 do + begin + if strtoint(sgWebChats.Cells[0, i]) >= dport then + dport := strtoint(sgWebChats.Cells[0, i]) + 1; + end; + fCreateNotify.sbWebServerPort.Value := dport; + fCreateNotify.isEdit := false; + fCreateNotify.Show; +end; + +procedure TfrOBS.btnDeleteeChatClick(Sender: TObject); +begin + if sgWebChats.Cells[1, sgWebChats.Row] = 'Чат' then + begin + DelChat(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); + end; + if sgWebChats.Cells[1, sgWebChats.Row] = 'Kandinsky' then + begin + DelKandinsky(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); + end; + if sgWebChats.Cells[1, sgWebChats.Row] = 'Оповещение' then + begin + DelNotify(strtoint(sgWebChats.Cells[0, sgWebChats.Row])); + end; +end; + +procedure TfrOBS.CreateWebChat(chatSettings: TOBSChat); +var + ChatWebServer: TChatWebServers; + fonts: tstringlist; + f: TfrColorSettings; + t: TfrFontSettings; + procedure LoadFontList(const mySL: tstringlist); + var + SearchRec: TSearchRec; + n: integer; + begin + if not DirectoryExists(myConst.fontsPath) then + CreateDir(myConst.fontsPath); + + n := 1; + if FindFirst(IncludeTrailingPathDelimiter(myConst.fontsPath) + '*.*', + faArchive, SearchRec) = 0 then + try + repeat + if (SearchRec.Attr and faAnyFile) = SearchRec.Attr then + begin + mySL.Add(SearchRec.Name); + Inc(n); + end; + until FindNext(SearchRec) <> 0; + finally + System.SysUtils.FindClose(SearchRec); + end; + end; + +begin + fonts := tstringlist.Create; + f := TfrColorSettings.Create(self); + t := TfrFontSettings.Create(self); + try + LoadFontList(fonts); + ChatWebServer.WebServerChat := TTTW_Chat.Create(fonts, chatSettings.port, + f.ccbBColor.Items[chatSettings.ColorBackground]); + ChatWebServer.port := chatSettings.port; + ChatWebServer.MaxMsg := chatSettings.MaxCountMess; + ChatWebServer.TimeMsg := chatSettings.TimeMess; + ChatWebServer.Freez := chatSettings.Freez = 1; + ChatWebServer.StyleBorderColor := f.ccbStyleBorderColor.Items + [chatSettings.ColorBorder]; + ChatWebServer.StyleBlockColor := chatSettings.ColorBlock; + ChatWebServer.StyleBlockBorderSize := chatSettings.SolidBorder; + ChatWebServer.StyleBlockPadding := chatSettings.Paddings; + ChatWebServer.FontStyleDefault := t.cbFontStyleDefault.Items + [chatSettings.StyleFont]; + ChatWebServer.FontColor := t.ccbFontColor.Items[chatSettings.ColorFont]; + ChatWebServer.BColor := f.ccbBColor.Items[chatSettings.ColorBorder];; + ChatWebServer.FontSize := chatSettings.SizeFont; + ChatWebServers.Add(ChatWebServer); + + ChatWebServers[ChatWebServers.Count - 1].WebServerChat.ActiveServer(True); + ChatWebServers[ChatWebServers.Count - 1].WebServerChat.SetDeleteMode + (not ChatWebServer.Freez, ChatWebServer.MaxMsg); + + finally + fonts.Free; + f.Free; + t.Free; + end; +end; + +procedure TfrOBS.DelChat(aPort: integer); +var + i, j: integer; +begin + // Ищем в обратном порядке для безопасного удаления + for i := High(listChats) downto 0 do + begin + if listChats[i].port = aPort then + begin + // Сдвигаем элементы массива + for j := i to High(listChats) - 1 do + listChats[j] := listChats[j + 1]; + // Уменьшаем размер массива + SetLength(listChats, Length(listChats) - 1); + // Выходим после первого найденного совпадения (предполагаем уникальность портов) + Break; + end; + end; + ChatWebServers[i].WebServerChat.ActiveServer(false); + ChatWebServers[i].WebServerChat.Destroy; + ChatWebServers.Delete(i); + db.SaveRecordArray('listChats', listChats); + UpdateGridFromArray; +end; + +procedure TfrOBS.DelKandinsky(aPort: integer); +var + i, j: integer; +begin + // Ищем в обратном порядке для безопасного удаления + for i := High(listKandinsky) downto 0 do + begin + if listKandinsky[i].port = aPort then + begin + // Сдвигаем элементы массива + for j := i to High(listKandinsky) - 1 do + listKandinsky[j] := listKandinsky[j + 1]; + // Уменьшаем размер массива + SetLength(listKandinsky, Length(listKandinsky) - 1); + // Выходим после первого найденного совпадения (предполагаем уникальность портов) + Break; + end; + end; + UpdateGridFromArray; + db.SaveRecordArray('listKandinsky', listKandinsky); +end; + +procedure TfrOBS.DelNotify(aPort: integer); +var + i, j: integer; +begin + // Ищем в обратном порядке для безопасного удаления + for i := High(listNotify) downto 0 do + begin + if listNotify[i].port = aPort then + begin + // Сдвигаем элементы массива + for j := i to High(listNotify) - 1 do + listNotify[j] := listNotify[j + 1]; + // Уменьшаем размер массива + SetLength(listNotify, Length(listNotify) - 1); + // Выходим после первого найденного совпадения (предполагаем уникальность портов) + Break; + end; + end; + + UpdateGridFromArray; + db.SaveRecordArray('listNotify', listNotify); +end; + +procedure TfrOBS.EdtChat(newRecord: TOBSChat; oldPort: integer); +var + i, j: integer; + chatWeb: TChatWebServers; // Временная переменная для записи + f: TfrColorSettings; + t: TfrFontSettings; +begin + f := TfrColorSettings.Create(self); + t := TfrFontSettings.Create(self); + try + // Обновляем запись в listChats + for i := 0 to High(listChats) do + if listChats[i].port = oldPort then + begin + listChats[i] := newRecord; + Break; + end; + + // Обновляем соответствующий сервер в ChatWebServers + for j := 0 to ChatWebServers.Count - 1 do + begin + // 1. Извлекаем запись во временную переменную + chatWeb := ChatWebServers[j]; + + if chatWeb.port = oldPort then + begin + // 2. Модифицируем поля записи + chatWeb.MaxMsg := newRecord.MaxCountMess; + chatWeb.TimeMsg := newRecord.TimeMess; + chatWeb.Freez := newRecord.Freez = 1; + chatWeb.StyleBorderColor := f.ccbStyleBorderColor.Items[newRecord.ColorBorder]; + chatWeb.StyleBlockColor := newRecord.ColorBlock; + chatWeb.StyleBlockBorderSize := newRecord.SolidBorder; + chatWeb.StyleBlockPadding := newRecord.Paddings; + chatWeb.FontStyleDefault := t.cbFontStyleDefault.Items[newRecord.StyleFont]; + chatWeb.FontColor := t.ccbFontColor.Items[newRecord.ColorFont]; + chatWeb.FontSize := newRecord.SizeFont; + chatWeb.BColor := f.ccbBColor.Items[newRecord.ColorBackground]; + chatWeb.WebServerChat.changeBackground(f.ccbBColor.Items[newRecord.ColorBackground]); + chatWeb.WebServerChat.SetDeleteMode(not chatWeb.Freez, chatWeb.MaxMsg); + + // 4. Возвращаем модифицированную запись в список + ChatWebServers[j] := chatWeb; + + Break; + end; + end; + + UpdateGridFromArray; + db.SaveRecordArray('listChats', listChats); + finally + f.Free; + t.Free; + end; +end; + +procedure TfrOBS.EdtNotify(newRecord: TOBSNotify; oldPort: integer); +var + i: integer; +begin + for i := 0 to High(listNotify) do + if listNotify[i].port = oldPort then + begin + listNotify[i] := newRecord; + UpdateGridFromArray; + db.SaveRecordArray('listNotify', listNotify); + Break; + end; +end; + +procedure TfrOBS.sgWebChatsCellDblClick(const Column: TColumn; +const Row: integer); +var + myChatRec: TOBSChat; + myNotifyRec: TOBSNotify; + i: integer; +begin + if sgWebChats.Cells[1, Row] = 'Оповещение' then + begin + for i := 0 to High(listNotify) do + if listNotify[i].port = (strtoint(sgWebChats.Cells[0, Row])) then + begin + myNotifyRec := listNotify[i]; + Break; + end; + + fCreateNotify.isEdit := True; + fCreateNotify.setRecord(myNotifyRec); + fCreateNotify.Show; + end; + if sgWebChats.Cells[1, Row] = 'Чат' then + begin + for i := 0 to High(listChats) do + if listChats[i].port = (strtoint(sgWebChats.Cells[0, Row])) then + begin + myChatRec := listChats[i]; + Break; + end; + fCreateChat.isEdit := True; + fCreateChat.setRecord(myChatRec); + fCreateChat.Show; + end; +end; + +procedure TfrOBS.UpdateGridFromArray; +var + i, rowIndex: integer; +begin + sgWebChats.BeginUpdate; + try + sgWebChats.RowCount := 0; // Сбрасываем строки + + rowIndex := 0; // Отдельный счетчик для строк сетки + + // listChats + for i := 0 to High(listChats) do + begin + sgWebChats.RowCount := rowIndex + 1; + sgWebChats.Cells[0, rowIndex] := inttostr(listChats[i].port); + sgWebChats.Cells[1, rowIndex] := 'Чат'; + sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + + inttostr(listChats[i].port); + Inc(rowIndex); // Увеличиваем счетчик строк + end; + + // listNotify + for i := 0 to High(listNotify) do + begin + sgWebChats.RowCount := rowIndex + 1; + sgWebChats.Cells[0, rowIndex] := inttostr(listNotify[i].port); + sgWebChats.Cells[1, rowIndex] := 'Оповещение'; + sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + + inttostr(listNotify[i].port); + Inc(rowIndex); // Увеличиваем счетчик строк + end; + + // listKandinsky + for i := 0 to High(listKandinsky) do + begin + sgWebChats.RowCount := rowIndex + 1; + sgWebChats.Cells[0, rowIndex] := inttostr(listKandinsky[i].port); + sgWebChats.Cells[1, rowIndex] := 'Kandinsky'; + sgWebChats.Cells[2, rowIndex] := 'http://127.0.0.1:' + + inttostr(listKandinsky[i].port); + Inc(rowIndex); // Увеличиваем счетчик строк + end; + finally + sgWebChats.EndUpdate; + end; +end; + +end. diff --git a/frames/fPlayerWeb.fmx b/frames/fPlayerWeb.fmx new file mode 100644 index 0000000..fc90db4 --- /dev/null +++ b/frames/fPlayerWeb.fmx @@ -0,0 +1,35 @@ +object frPlayerWeb: TfrPlayerWeb + Size.Width = 207.000000000000000000 + Size.Height = 76.000000000000000000 + Size.PlatformDefault = False + object Label1: TLabel + Align = Top + Margins.Left = 10.000000000000000000 + Margins.Top = 10.000000000000000000 + Margins.Right = 10.000000000000000000 + Margins.Bottom = 10.000000000000000000 + Position.X = 10.000000000000000000 + Position.Y = 10.000000000000000000 + Size.Width = 187.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = #1057#1089#1099#1083#1082#1072' '#1076#1083#1103' OBS '#1044#1086#1082'-'#1087#1072#1085#1077#1083#1080 + TabOrder = 3 + end + object Edit1: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + Align = Top + TabOrder = 2 + ReadOnly = True + Text = 'http://127.0.0.1:8088' + Position.X = 10.000000000000000000 + Position.Y = 47.000000000000000000 + Margins.Left = 10.000000000000000000 + Margins.Top = 10.000000000000000000 + Margins.Right = 10.000000000000000000 + Margins.Bottom = 10.000000000000000000 + Size.Width = 187.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end +end diff --git a/frames/fPlayerWeb.pas b/frames/fPlayerWeb.pas new file mode 100644 index 0000000..feaa66e --- /dev/null +++ b/frames/fPlayerWeb.pas @@ -0,0 +1,58 @@ +unit fPlayerWeb; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, FMX.Styles, IdHTTPWebBrokerBridge, IdGlobal, Web.HTTPApp, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Controls.Presentation, FMX.Layouts, FMX.ListBox, uPlayerThread, + bass_simple, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, FMX.Edit; + +type + TfrPlayerWeb = class(TFrame) + Label1: TLabel; + Edit1: TEdit; + + private + mp: TBassSimple; + Player: TPlayerThread; + FServer: TIdHTTPWebBrokerBridge; + procedure StartServer; + public + procedure init; + destructor Destoy(Sender: TObject; var Action: TCloseAction); + end; + +implementation + +{$R *.fmx} + +destructor TfrPlayerWeb.Destoy(Sender: TObject; var Action: TCloseAction); +begin + FServer.Active := False; + FServer.Bindings.Clear; + Player.Free; +mp.Free; +end; + +procedure TfrPlayerWeb.init; +begin + + FServer := TIdHTTPWebBrokerBridge.Create(Self); + StartServer; +end; + +procedure TfrPlayerWeb.StartServer; +begin + if not FServer.Active then + begin + FServer.Bindings.Clear; + FServer.DefaultPort := 8088; + FServer.Active := True; + end; + + +end; + +end. diff --git a/fSettings.fmx b/frames/fSettings.fmx similarity index 97% rename from fSettings.fmx rename to frames/fSettings.fmx index d8bd93b..b448895 100644 --- a/fSettings.fmx +++ b/frames/fSettings.fmx @@ -63,6 +63,7 @@ object frSettings: TfrSettings Size.Width = 177.000000000000000000 Size.Height = 21.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit Left = 11 Top = 43 end @@ -75,6 +76,7 @@ object frSettings: TfrSettings Size.Width = 177.000000000000000000 Size.Height = 21.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit Left = 11 Top = 89 end @@ -86,6 +88,7 @@ object frSettings: TfrSettings Size.Width = 177.000000000000000000 Size.Height = 21.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit Left = 11 Top = 181 end @@ -112,6 +115,7 @@ object frSettings: TfrSettings Size.Width = 177.000000000000000000 Size.Height = 21.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit Left = 11 Top = 135 end @@ -134,7 +138,7 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 33 + TabOrder = 31 Text = #1054#1090#1082#1088#1099#1090#1100' '#1089#1090#1088#1080#1084 TextSettings.Trimming = None OnClick = btnOpenStreamClick @@ -145,20 +149,21 @@ object frSettings: TfrSettings Size.Width = 128.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 34 + TabOrder = 32 Text = #1055#1086#1083#1091#1095#1080#1090#1100' Token' TextSettings.Trimming = None OnClick = btnGetTokenStreamerClick end object edtBotTokenStreamer: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 35 + TabOrder = 34 Password = True Position.X = 8.000000000000000000 Position.Y = 146.000000000000000000 Size.Width = 177.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit end object Label53: TLabel Position.X = 8.000000000000000000 @@ -178,6 +183,7 @@ object frSettings: TfrSettings Size.PlatformDefault = False TabOrder = 4 Text = #1040#1074#1090#1086#1087#1086#1076#1082#1083#1102#1095#1077#1085#1080#1077 + OnExit = edtChannelExit end end object GroupBox22: TGroupBox @@ -191,7 +197,7 @@ object frSettings: TfrSettings object btnDAGetCode: TButton Position.X = 200.000000000000000000 Position.Y = 216.000000000000000000 - TabOrder = 43 + TabOrder = 40 Text = #1055#1086#1083#1091#1095#1080#1090#1100 TextSettings.Trimming = None OnClick = btnDAGetCodeClick @@ -201,55 +207,58 @@ object frSettings: TfrSettings Position.Y = 24.000000000000000000 TextSettings.Trimming = None Text = 'Client ID' - TabOrder = 35 + TabOrder = 33 end object edtDAClientID: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 39 + TabOrder = 35 Password = True Position.X = 8.000000000000000000 Position.Y = 49.000000000000000000 Size.Width = 272.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit end object Label64: TLabel Position.X = 8.000000000000000000 Position.Y = 79.000000000000000000 TextSettings.Trimming = None Text = 'Client Secret' - TabOrder = 36 + TabOrder = 34 end object edtDAClientSecret: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 38 + TabOrder = 36 Password = True Position.X = 8.000000000000000000 Position.Y = 104.000000000000000000 Size.Width = 272.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit end object Label65: TLabel Position.X = 8.000000000000000000 Position.Y = 134.000000000000000000 TextSettings.Trimming = None Text = 'Redirect URL' - TabOrder = 40 + TabOrder = 38 end object edtDARedirectURL: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 41 + TabOrder = 39 Password = True Position.X = 8.000000000000000000 Position.Y = 159.000000000000000000 Size.Width = 272.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False + OnExit = edtChannelExit end object edtDACode: TEdit Touch.InteractiveGestures = [LongTap, DoubleTap] - TabOrder = 42 + TabOrder = 41 Password = True Position.X = 8.000000000000000000 Position.Y = 214.000000000000000000 @@ -262,7 +271,7 @@ object frSettings: TfrSettings Position.Y = 189.000000000000000000 TextSettings.Trimming = None Text = 'Code' - TabOrder = 44 + TabOrder = 42 end object btnDAStart: TButton Position.X = 8.000000000000000000 @@ -270,7 +279,7 @@ object frSettings: TfrSettings Size.Width = 121.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 45 + TabOrder = 43 Text = #1055#1086#1076#1082#1083#1102#1095#1080#1090#1100#1089#1103 TextSettings.Trimming = None OnClick = btnDAStartClick @@ -282,7 +291,7 @@ object frSettings: TfrSettings Size.Width = 209.000000000000000000 Size.Height = 22.000000000000000000 Size.PlatformDefault = False - TabOrder = 47 + TabOrder = 46 Text = #1055#1086#1083#1091#1095#1080#1090#1100' '#1076#1072#1085#1085#1099#1077' Donation Alerts' TextSettings.Trimming = None Visible = False diff --git a/fSettings.pas b/frames/fSettings.pas similarity index 95% rename from fSettings.pas rename to frames/fSettings.pas index f2bfcec..084fead 100644 --- a/fSettings.pas +++ b/frames/fSettings.pas @@ -56,6 +56,7 @@ type procedure btnImportSettingsClick(Sender: TObject); procedure btnExportSettingsClick(Sender: TObject); procedure btnMasterClick(Sender: TObject); + procedure edtChannelExit(Sender: TObject); private { Private declarations } FAPIClient: TAPIClient; @@ -325,6 +326,17 @@ if Assigned(FWSClient) then inherited; end; +procedure TfrSettings.edtChannelExit(Sender: TObject); +begin + if Sender is TEdit then + DB.WriteSetting(TEdit(Sender).Name, TEdit(Sender).text); + if Sender is TCheckBox then + if TCheckBox(Sender).IsChecked then + DB.WriteSetting(TCheckBox(Sender).Name, 'True') + else + DB.WriteSetting(TCheckBox(Sender).Name, 'False'); +end; + procedure TfrSettings.init; begin if not Assigned(FAPIClient) then @@ -361,7 +373,7 @@ end; procedure TfrSettings.HandleWSStatus(AStatusText: string; AStatusCode: integer); begin - // fLog.tolog(3,'uLogin','HandleWSStatus',AStatusText); + TTW_Bot.tolog('fSettings','HandleWSStatus',AStatusText,3); TTW_Bot.Label8.text := AStatusText; case AStatusCode of 0: diff --git a/fSimpleGrid.fmx b/frames/fSimpleGrid.fmx similarity index 100% rename from fSimpleGrid.fmx rename to frames/fSimpleGrid.fmx diff --git a/fSimpleGrid.pas b/frames/fSimpleGrid.pas similarity index 100% rename from fSimpleGrid.pas rename to frames/fSimpleGrid.pas diff --git a/frames/fTTS.fmx b/frames/fTTS.fmx new file mode 100644 index 0000000..05d9aab --- /dev/null +++ b/frames/fTTS.fmx @@ -0,0 +1,71 @@ +object frTTS: TfrTTS + Size.Width = 314.000000000000000000 + Size.Height = 214.000000000000000000 + Size.PlatformDefault = False + object Label1: TLabel + Position.X = 8.000000000000000000 + Position.Y = 8.000000000000000000 + Text = #1043#1086#1083#1086#1089 + TabOrder = 0 + end + object cbVoices: TComboBox + Position.X = 8.000000000000000000 + Position.Y = 33.000000000000000000 + Size.Width = 161.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 1 + end + object btnUpdateVoices: TButton + Position.X = 177.000000000000000000 + Position.Y = 33.000000000000000000 + Size.Width = 128.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 2 + Text = #1054#1073#1085#1086#1074#1080#1090#1100' '#1075#1086#1083#1086#1089#1072 + TextSettings.Trimming = None + OnClick = btnUpdateVoicesClick + end + object Label2: TLabel + Position.X = 8.000000000000000000 + Position.Y = 63.000000000000000000 + Text = #1058#1077#1082#1089#1090' '#1076#1083#1103' '#1086#1079#1074#1091#1095#1082#1080 + TabOrder = 3 + end + object edtText: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + TabOrder = 4 + Position.X = 8.000000000000000000 + Position.Y = 88.000000000000000000 + Size.Width = 161.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end + object Label3: TLabel + Position.X = 8.000000000000000000 + Position.Y = 118.000000000000000000 + Text = #1048#1089#1090#1086#1095#1085#1080#1082 + TabOrder = 5 + end + object cbOutput: TComboBox + Items.Strings = ( + #1069#1090#1086' '#1087#1088#1080#1083#1086#1078#1077#1085#1080#1077 + 'SilentPlayer') + ItemIndex = 0 + Position.X = 8.000000000000000000 + Position.Y = 143.000000000000000000 + Size.Width = 161.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + TabOrder = 6 + end + object btnSend: TButton + Position.X = 8.000000000000000000 + Position.Y = 173.000000000000000000 + TabOrder = 7 + Text = #1054#1079#1074#1091#1095#1080#1090#1100 + TextSettings.Trimming = None + OnClick = btnSendClick + end +end diff --git a/frames/fTTS.pas b/frames/fTTS.pas new file mode 100644 index 0000000..7b4ce0f --- /dev/null +++ b/frames/fTTS.pas @@ -0,0 +1,86 @@ +unit fTTS; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, shellapi, + FMX.Types, FMX.Graphics, FMX.Controls, FMX.Forms, FMX.Dialogs, FMX.StdCtrls, + FMX.Edit, FMX.ListBox, FMX.Controls.Presentation, uTTS, bass_simple; + +type + TfrTTS = class(TFrame) + Label1: TLabel; + cbVoices: TComboBox; + btnUpdateVoices: TButton; + Label2: TLabel; + edtText: TEdit; + Label3: TLabel; + btnSend: TButton; + cbOutput: TComboBox; + procedure btnUpdateVoicesClick(Sender: TObject); + procedure btnSendClick(Sender: TObject); + private + { Private declarations } + tts: TTTS; + public + { Public declarations } + end; + +implementation + +{$R *.fmx} +uses ugeneral; + +procedure TfrTTS.btnSendClick(Sender: TObject); +var + s, s1: string; + +begin + case cbOutput.ItemIndex of + 0: + begin // this + s := ExtractFilePath(ParamStr(0)) + 'piper\piper.exe'; + s1 := ExtractFilePath(ParamStr(0)) + 'piper\voices'; + if cbVoices.ItemIndex = -1 then + exit; + tts := TTTS.Create(s, s1); + try + tts.SetModel(cbVoices.Text); + tts.TextToSpeech(edtText.Text, true); + finally + tts.Free; + end; + end; + 1: + begin // SilentPlay + ShellExecute(0, 'open', PChar(myConst.SilentPlay), + PChar(Format('%s %s "%s"', ['2', cbVoices.Text, edtText.Text])), nil, 0); + end; + + + end; + +end; + +procedure TfrTTS.btnUpdateVoicesClick(Sender: TObject); +var + s, s1: string; + List: TStringList; +begin + s := ExtractFilePath(ParamStr(0)) + 'piper\piper.exe'; + s1 := ExtractFilePath(ParamStr(0)) + 'piper\voices'; + + tts := TTTS.Create(s, s1); + List := TStringList.Create; + try + cbVoices.Items.Clear; + List := tts.GetModelsList; + cbVoices.Items.Assign(List); + finally + tts.Free; + List.Free; + end; +end; + +end. diff --git a/uGeneral.pas b/uGeneral.pas deleted file mode 100644 index f5eff86..0000000 --- a/uGeneral.pas +++ /dev/null @@ -1,506 +0,0 @@ -unit uGeneral; - -interface - -uses - System.SysUtils, System.Types, System.UITypes, System.Classes, - System.Variants, - FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.TabControl, - FMX.Controls.Presentation, FMX.StdCtrls, System.ImageList, FMX.ImgList, - FMX.Styles, ShellAPI, StrUtils, - fSettings, fAI, fNotify, fAutoActions, FMX.ListBox, fLog, uMyTimer, uRecords, - System.Generics.Collections, - System.IOUtils, fCommands, uDataBase, FMX.Edit, FMX.Colors, FMX.SpinBox, - windows, System.Skia, FMX.Skia, uCreateChat, uCreateNotify, fOBS; - -type - TTTW_Bot = class(TForm) - V: TTabControl; - TabItem1: TTabItem; - TabItem2: TTabItem; - TabItem3: TTabItem; - TabItem4: TTabItem; - frSettings1: TfrSettings; - ImageList1: TImageList; - TabItem5: TTabItem; - Panel1: TPanel; - btnConnecting: TButton; - Label2: TLabel; - Label3: TLabel; - Label5: TLabel; - Label6: TLabel; - Label7: TLabel; - Label8: TLabel; - aiConnecting: TAniIndicator; - Label9: TLabel; - Label10: TLabel; - Label11: TLabel; - Label12: TLabel; - frAI1: TfrAI; - TabItem6: TTabItem; - TabItem7: TTabItem; - TabItem8: TTabItem; - TabItem9: TTabItem; - frNotify1: TfrNotify; - Label1: TLabel; - frAutoActions1: TfrAutoActions; - frOBS1: TfrOBS; - frLog1: TfrLog; - cbTheme: TComboBox; - Label15: TLabel; - frCommands1: TfrCommands; - SpeedButton1: TSpeedButton; - SpeedButton2: TSpeedButton; - SpeedButton3: TSpeedButton; - btnCreateChat: TButton; - procedure cbThemeChange(Sender: TObject); - procedure FormCreate(Sender: TObject); - procedure SpeedButton1Click(Sender: TObject); - procedure SpeedButton3Click(Sender: TObject); - procedure SpeedButton2Click(Sender: TObject); - procedure frSettings1btnDAStartClick(Sender: TObject); - procedure frCommands1btnRandAddClick(Sender: TObject); - procedure frOBS1btnDeleteeChatClick(Sender: TObject); - procedure FormDestroy(Sender: TObject); - private - { Private declarations } - procedure ReadDB(); - public - { Public declarations } - procedure toLog(aModule, aMethod, aMessage: string; aCode: integer); - procedure GlobalExceptionHandler(Sender: TObject; E: Exception); - end; - -var - TTW_Bot: TTTW_Bot; - myConst: TConst; - db: TSettingsDatabase; - appconst: TBotAppCfg; - -implementation - -{$R *.fmx} - -procedure TTTW_Bot.GlobalExceptionHandler(Sender: TObject; E: Exception); -begin - try - TTW_Bot.toLog('GlobalException', E.ClassName, E.Message, 2); - except - // на случай, если логгер сам кинет исключение - end; -end; - -procedure TTTW_Bot.cbThemeChange(Sender: TObject); -begin - cbTheme.ItemIndex := cbTheme.Items.IndexOf(cbTheme.text); - if cbTheme.ItemIndex <> -1 then - TStyleManager.SetStyleFromFile(myConst.stlPath + cbTheme.text); - // db.WriteSetting('cbTheme', inttostr(cbTheme.ItemIndex)); -end; - -procedure TTTW_Bot.FormCreate(Sender: TObject); -var - Path: string; - - function GetPathToTestExe: string; // вернет папку romaming - begin - Result := GetEnvironmentVariable('APPDATA'); - if Result <> '' then - Result := IncludeTrailingPathDelimiter(Result); - end; - -begin - - myConst.GeneralPath := ExtractFilePath(ParamStr(0)); - myConst.AppDataPath := GetPathToTestExe + 'TTW_Bot\'; - - if not DirectoryExists(myConst.AppDataPath) then - CreateDir(myConst.AppDataPath); - - myConst.DBPath := myConst.AppDataPath + 'settings.db'; - - if not DirectoryExists(myConst.AppDataPath + 'fonts') then - CreateDir(myConst.AppDataPath + 'fonts'); - myConst.fontsPath := myConst.AppDataPath + 'fonts\'; - - if not DirectoryExists(myConst.AppDataPath + 'imgs') then - CreateDir(myConst.AppDataPath + 'imgs'); - myConst.imgsPath := myConst.AppDataPath + 'imgs\'; - - if not DirectoryExists(myConst.AppDataPath + 'sounds') then - CreateDir(myConst.AppDataPath + 'sounds'); - myConst.soundsPath := myConst.AppDataPath + 'sounds\'; - - if not DirectoryExists(myConst.AppDataPath + 'stl') then - CreateDir(myConst.AppDataPath + 'stl'); - myConst.stlPath := myConst.AppDataPath + 'stl\'; - - if not DirectoryExists(myConst.AppDataPath + 'ytSongs') then - CreateDir(myConst.AppDataPath + 'ytSongs'); - myConst.ytSongsPath := myConst.AppDataPath + 'ytSongs\'; - - myConst.PublicPlay := myConst.GeneralPath + 'PublicPlay.exe'; - myConst.SilentPlay := myConst.GeneralPath + 'SilentPlayer.exe'; - myConst.ytPlay := myConst.GeneralPath + 'Player.exe'; - myConst.cfg1 := myConst.GeneralPath + 'botapp.cfg'; - - db := TSettingsDatabase.Create(myConst.DBPath); - frAutoActions1.FTimerList := TObjectList.Create(false); - ReadDB; - frCommands1.frsgSounds.ObjectRecord := frCommands1.listSounds; - frCommands1.frsgSounds.TableName := 'listSounds'; - frCommands1.frsgSounds.UpdateGrid; - frCommands1.frsgFiles.ObjectRecord := frCommands1.listFiles; - frCommands1.frsgFiles.TableName := 'listFiles'; - frCommands1.frsgFiles.UpdateGrid; - frCommands1.frsgNeiro.ObjectRecord := frCommands1.listNeiro; - frCommands1.frsgNeiro.TableName := 'listNeiro'; - frCommands1.frsgNeiro.UpdateGrid; - - for Path in TDirectory.GetFiles(myConst.stlPath) do - cbTheme.Items.Add(ExtractFileName(Path)); - cbTheme.ItemIndex := strtoint(db.ReadSetting('cbTheme', '-1')); - frLog1.FLogList := TList.Create; -end; - -procedure TTTW_Bot.FormDestroy(Sender: TObject); -begin - FreeAndNil(db); - FreeAndNil(frAutoActions1.FTimerList); - FreeAndNil(frLog1.FLogList); - inherited; -end; - -procedure TTTW_Bot.frCommands1btnRandAddClick(Sender: TObject); -begin - frCommands1.btnRandAddClick(Sender); - -end; - -procedure TTTW_Bot.frOBS1btnDeleteeChatClick(Sender: TObject); -begin - frOBS1.btnDeleteeChatClick(Sender); - -end; - -procedure TTTW_Bot.frSettings1btnDAStartClick(Sender: TObject); -begin - frSettings1.btnDAStartClick(Sender); - -end; - -procedure TTTW_Bot.ReadDB; - - function XorDecryptToStrings(const InputFile, Key: string): TStrings; -var - InStream: TFileStream; - MemStream: TMemoryStream; - KeyBytes: TBytes; - KeyLen, KeyIndex: integer; - B: Byte; -begin - KeyBytes := TEncoding.ANSI.GetBytes(Key); - KeyLen := Length(KeyBytes); - if KeyLen = 0 then - raise Exception.Create('Ключ не может быть пустым'); - - InStream := TFileStream.Create(InputFile, fmOpenRead); - try - MemStream := TMemoryStream.Create; - try - KeyIndex := 0; - while InStream.Position < InStream.Size do - begin - InStream.ReadBuffer(B, 1); - B := B xor KeyBytes[KeyIndex]; - MemStream.WriteBuffer(B, 1); - KeyIndex := (KeyIndex + 1) mod KeyLen; - end; - MemStream.Position := 0; - Result := TStringList.Create; - try - Result.LoadFromStream(MemStream, TEncoding.ANSI); - except - Result.Free; // Освобождаем при ошибке загрузки - raise; - end; - finally - MemStream.Free; - end; - finally - InStream.Free; - end; -end; -// Загрузка компонентов настроек (TEdit, TCheckBox) - procedure LoadSettingsComponents; - var - I: integer; - c: TComponent; - begin - for I := 0 to frSettings1.ComponentCount - 1 do - begin - c := frSettings1.Components[I]; - if c is TEdit then - TEdit(c).text := db.ReadSetting(TEdit(c).Name) - else if c is TCheckBox then - TCheckBox(c).IsChecked := (db.ReadSetting(TCheckBox(c).Name) = 'True'); - end; - db.FChannel := frSettings1.edtChannel.text; - end; - -// Загрузка данных в гриды команд - procedure LoadGridsData; - begin - db.LoadRecordArray('RandomCounters', - frCommands1.RandomCounters); - db.LoadRecordArray('listSounds', frCommands1.listSounds); - db.LoadRecordArray('listFiles', frCommands1.listFiles); - db.LoadRecordArray('listNeiro', frCommands1.listNeiro); - db.LoadRecordArray('listCommands', frCommands1.listCommands); - frCommands1.UpdateGridFromArray; - - end; - -// Загрузка списка групп - procedure LoadGroupNames; - begin - db.getGroupName(frCommands1.frGroupsRequest1.lbRandomGroup.Items); - end; - -// Загрузка зашифрованного конфига -procedure LoadEncryptedConfig; -var - tempList: TStrings; // Временный список для результата - I: integer; -begin - if not FileExists(myConst.cfg1) then - Exit; - - tempList := nil; // Инициализация - try - tempList := XorDecryptToStrings(myConst.cfg1, 'fgvasrgEFAXFAFAS'); - - for I := 0 to tempList.Count - 1 do - begin - var eqPos := Pos('=', tempList[I]); - if eqPos > 0 then - begin - var Key := Trim(Copy(tempList[I], 1, eqPos - 1)); - var Value := Trim(Copy(tempList[I], eqPos + 1, MaxInt)); - - if Key = 'k1' then - appconst.TTV_ClientID := Value - else if Key = 'k2' then - appconst.AI_GigaChat_AC := Value - else if Key = 'k3' then - appconst.AI_GigaChat_ClientID := Value - else if Key = 'k4' then - appconst.AI_ChatGPT_Token := Value - else if Key = 'k5' then - appconst.AI_DeepSeec_Token := Value - else if Key = 'k6' then - appconst.DA_ClientID := Value - else if Key = 'k7' then - appconst.DA_Sicret := Value - else if Key = 'k8' then - appconst.DA_URL := Value; - end; - end; - - frSettings1.btnGetClientID.Visible := (appconst.TTV_ClientID <> ''); - frAI1.btnGetAIDef.Visible := ((appconst.AI_GigaChat_AC <> '') and - (appconst.AI_GigaChat_ClientID <> '')) or - (appconst.AI_ChatGPT_Token <> '') or (appconst.AI_DeepSeec_Token <> ''); - frSettings1.btnGetDADef.Visible := (appconst.DA_ClientID <> '') and - (appconst.DA_Sicret <> '') and (appconst.DA_URL <> ''); - finally - tempList.Free; // Важно: освобождаем временный список! - end; -end; -// Загрузка настроек уведомлений - procedure LoadNotifySettings; - var - I: integer; - c: TComponent; - begin - for I := 0 to frNotify1.ComponentCount - 1 do - begin - c := frNotify1.Components[I]; - if c is TEdit then - TEdit(c).text := db.ReadSetting(TEdit(c).Name) - else if c is TCheckBox then - TCheckBox(c).IsChecked := (db.ReadSetting(TCheckBox(c).Name) = 'True') - else if c is TSwitch then - TSwitch(c).IsChecked := (db.ReadSetting(TSwitch(c).Name) = 'True') - else if c is TTrackBar then - TTrackBar(c).Value := - strtoint(db.ReadSetting(TTrackBar(c).Name, '100')); - end; - end; - -// Загрузка настроек ИИ - procedure LoadAISettings; - var - I: integer; - c: TComponent; - ii: integer; - - // Настройки GigaChat - procedure SetupGigaChatSettings; - begin - frAI1.rbGC.IsChecked := True; - frAI1.Label45.text := 'ClientID'; - frAI1.Label47.text := 'Autorization Code'; - frAI1.Label1.Visible := false; - frAI1.edtAIP2.Visible := True; - frAI1.edtAIP2.Password := True; - frAI1.edtAIP3.Visible := false; - frAI1.cbOllama.Visible := false; - end; - - // Настройки DeepSeek - procedure SetupDeepSeekSettings; - begin - frAI1.rbDS.IsChecked := True; - frAI1.Label45.text := 'API Token'; - frAI1.Label47.text := ''; - frAI1.Label1.Visible := false; - frAI1.edtAIP2.Visible := false; - frAI1.edtAIP3.Visible := false; - frAI1.cbOllama.Visible := false; - end; - - // Настройки ChatGPT - procedure SetupChatGPTSettings; - begin - frAI1.rbCG.IsChecked := True; - frAI1.Label45.text := 'API Token'; - frAI1.Label47.text := ''; - frAI1.Label1.Visible := false; - frAI1.edtAIP2.Visible := false; - frAI1.edtAIP3.Visible := false; - frAI1.cbOllama.Visible := false; - end; - - // Настройки кастомного ИИ - procedure SetupCustomAISettings; - begin - frAI1.RBCustom.IsChecked := True; - frAI1.Label45.text := 'API Token'; - frAI1.Label47.text := 'URL'; - frAI1.Label1.Visible := True; - frAI1.edtAIP2.Visible := True; - frAI1.edtAIP2.Password := false; - frAI1.edtAIP3.Visible := True; - frAI1.cbOllama.Visible := True; - frAI1.cbOllama.IsChecked := db.ReadSetting(frAI1.cbOllama.Name) = '1'; - end; - - begin - for I := 0 to frAI1.ComponentCount - 1 do - begin - c := frAI1.Components[I]; - if c is TEdit then - TEdit(c).text := db.ReadSetting(TEdit(c).Name) - else if c is TCheckBox then - TCheckBox(c).IsChecked := db.ReadSetting(TCheckBox(c).Name) = '1'; - end; - - ii := strtoint(db.ReadSetting('aiIndex', '0')); - case ii of - 0: - SetupGigaChatSettings; - 1: - SetupDeepSeekSettings; - 2: - SetupChatGPTSettings; - 3: - SetupCustomAISettings; - end; - - frSettings1.init; - end; - -// Загрузка гридов автоматических действий - procedure LoadAutoActionsGrids; - begin - db.LoadRecordArray('listTimer', frAutoActions1.listTimer); - db.LoadRecordArray('listBanWords', frAutoActions1.listBanWords); - db.LoadRecordArray('listCounters', frAutoActions1.listCounters); - frAutoActions1.initTimers; - frAutoActions1.UpdateGridFromArray; - end; - -// Загрузка интеграций с ОБС - procedure LoadOBSGrids; - begin - db.LoadRecordArray('listChats', frOBS1.listChats); - db.LoadRecordArray('listNotify', frOBS1.listNotify); - db.LoadRecordArray('listKandinsky', frOBS1.listKandinsky); - frOBS1.UpdateGridFromArray; - end; - -begin - LoadSettingsComponents; - LoadGridsData; - LoadGroupNames; - LoadEncryptedConfig; - - LoadNotifySettings; - LoadAISettings; - LoadOBSGrids; - LoadAutoActionsGrids; -end; - -procedure TTTW_Bot.SpeedButton1Click(Sender: TObject); -begin - ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/incadence'), - nil, nil, 1); -end; - -procedure TTTW_Bot.SpeedButton2Click(Sender: TObject); -begin - // https://www.twitch.tv/kuznecogr - ShellExecute(0, 'open', pwidechar('https://www.twitch.tv/kuznecogr'), - nil, nil, 1); -end; - -procedure TTTW_Bot.SpeedButton3Click(Sender: TObject); -begin - // https://www.flaticon.com/ru/authors/karacis - ShellExecute(0, 'open', - pwidechar('https://www.flaticon.com/ru/authors/karacis'), nil, nil, 1); -end; - -procedure TTTW_Bot.toLog(aModule, aMethod, aMessage: string; aCode: integer); -begin - TThread.Synchronize(nil, - procedure - var - ml: TRLog; - begin - // Инициализация всех полей записи - ml.rTime := Now; - case aCode of - 0: - ml.rType := 'INFO'; - 1: - ml.rType := 'WARNING'; - 2: - ml.rType := 'ERROR'; - 3: - ml.rType := 'DEBUG'; - else - ml.rType := 'UNKNOWN'; - end; - ml.rModule := aModule; // string - ml.rMethod := aMethod; // string - ml.rMessage := aMessage; // string - // Добавляем запись в список - frLog1.FLogList.Add(ml); - // Обновляем грид - frLog1.UpdateGridFilters; - end); -end; - -end. diff --git a/uPlayer.fmx b/uPlayer.fmx new file mode 100644 index 0000000..91d67d3 --- /dev/null +++ b/uPlayer.fmx @@ -0,0 +1,43 @@ +object fPlayer: TfPlayer + Left = 0 + Top = 0 + Caption = #1055#1083#1077#1077#1088 + ClientHeight = 81 + ClientWidth = 227 + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnClose = FormClose + DesignerMasterStyle = 0 + object Edit1: TEdit + Touch.InteractiveGestures = [LongTap, DoubleTap] + Align = Top + TabOrder = 2 + ReadOnly = True + Text = 'http://127.0.0.1:8088' + Position.X = 10.000000000000000000 + Position.Y = 47.000000000000000000 + Margins.Left = 10.000000000000000000 + Margins.Top = 10.000000000000000000 + Margins.Right = 10.000000000000000000 + Margins.Bottom = 10.000000000000000000 + Size.Width = 207.000000000000000000 + Size.Height = 22.000000000000000000 + Size.PlatformDefault = False + end + object Label1: TLabel + Align = Top + Margins.Left = 10.000000000000000000 + Margins.Top = 10.000000000000000000 + Margins.Right = 10.000000000000000000 + Margins.Bottom = 10.000000000000000000 + Position.X = 10.000000000000000000 + Position.Y = 10.000000000000000000 + Size.Width = 207.000000000000000000 + Size.Height = 17.000000000000000000 + Size.PlatformDefault = False + Text = #1057#1089#1099#1083#1082#1072' '#1076#1083#1103' OBS '#1044#1086#1082'-'#1087#1072#1085#1077#1083#1080 + TabOrder = 3 + end +end diff --git a/uPlayer.pas b/uPlayer.pas new file mode 100644 index 0000000..396505d --- /dev/null +++ b/uPlayer.pas @@ -0,0 +1,72 @@ +unit uPlayer; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, FMX.Styles, IdHTTPWebBrokerBridge, IdGlobal, Web.HTTPApp, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.StdCtrls, + FMX.Controls.Presentation, FMX.Layouts, FMX.ListBox, uPlayerThread, + bass_simple, FMX.Memo.Types, FMX.ScrollBox, FMX.Memo, FMX.Edit; + +type + TfPlayer = class(TForm) + Edit1: TEdit; + Label1: TLabel; + procedure FormCreate(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + private + { Private declarations } + mp: TBassSimple; + + Player: TPlayerThread; + FServer: TIdHTTPWebBrokerBridge; + procedure StartServer; + public + { Public declarations } + end; + +var + fPlayer: TfPlayer; + +implementation + +{$R *.fmx} + +function GetPathToTestExe: string; // вернет папку romaming +begin + Result := GetEnvironmentVariable('APPDATA'); + if Result <> '' then + Result := IncludeTrailingPathDelimiter(Result); +end; + +procedure TfPlayer.FormClose(Sender: TObject; var Action: TCloseAction); +begin + FServer.Active := False; + FServer.Bindings.Clear; +end; + +procedure TfPlayer.FormCreate(Sender: TObject); +var + theme: string; +begin + theme := ParamStr(1); + if theme <> '' then + begin + TStyleManager.SetStyleFromFile(GetPathToTestExe + 'TTW_Bot\stl\' + theme); + end; + FServer := TIdHTTPWebBrokerBridge.Create(Self); + StartServer; +end; + +procedure TfPlayer.StartServer; +begin + if not FServer.Active then + begin + FServer.Bindings.Clear; + FServer.DefaultPort := 8088; + FServer.Active := True; + end; +end; + +end. diff --git a/uSilentPlayer.fmx b/uSilentPlayer.fmx new file mode 100644 index 0000000..12b0df7 --- /dev/null +++ b/uSilentPlayer.fmx @@ -0,0 +1,14 @@ +object fPublicPlayer: TfPublicPlayer + Left = 0 + Top = 0 + Caption = 'Form1' + ClientHeight = 52 + ClientWidth = 168 + Transparency = True + FormFactor.Width = 320 + FormFactor.Height = 480 + FormFactor.Devices = [Desktop] + OnCreate = FormCreate + OnClose = FormClose + DesignerMasterStyle = 0 +end diff --git a/uSilentPlayer.pas b/uSilentPlayer.pas new file mode 100644 index 0000000..712a01b --- /dev/null +++ b/uSilentPlayer.pas @@ -0,0 +1,101 @@ +unit uSilentPlayer; + +interface + +uses + System.SysUtils, System.Types, System.UITypes, System.Classes, + System.Variants, utts, mmsystem, + FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, bass_simple, + FMX.Controls.Presentation, FMX.StdCtrls; + +type + TfPublicPlayer = class(TForm) + procedure FormCreate(Sender: TObject); + procedure FormClose(Sender: TObject; var Action: TCloseAction); + private + procedure endPlay(Sender: TObject); + public + { Public declarations } + end; + +var + fPublicPlayer: TfPublicPlayer; + b1: tbassSimple; + i, sec: Integer; + s: string; + +implementation + +{$R *.fmx} + +procedure TfPublicPlayer.endPlay(Sender: TObject); +begin + try + TThread.Queue(nil, + procedure + begin + Close; + end); + except + + end; +end; + +procedure TfPublicPlayer.FormClose(Sender: TObject; var Action: TCloseAction); +begin + try + if Assigned(b1) then + b1.Free; + except + + end; +end; + +procedure TfPublicPlayer.FormCreate(Sender: TObject); // mode(1) sec vol othet +var + mode: Integer; // mode(2) model texts + tts: ttts; + p, v, m: string; +begin + hide; + try + mode := StrToIntDef(ParamStr(1), 1); + case mode of + 1: + begin + b1 := tbassSimple.Create(0); + b1.Volume := StrToIntDef(ParamStr(2), 255); + sec := StrToIntDef(ParamStr(1), 1); + s := ''; + for i := 3 to ParamCount do + begin + s := s + ParamStr(i) + ' '; + end; + Delete(s, length(s), 1); + b1.Play(s); + b1.OnEndPlay := endPlay; + end; + 2: + begin + s := ''; + m := ParamStr(2); + for i := 3 to ParamCount do + begin + s := s + ParamStr(i) + ' '; + end; + Delete(s, length(s), 1); + p := ExtractFilePath(ParamStr(0)) + 'piper\piper.exe'; + v := ExtractFilePath(ParamStr(0)) + 'piper\voices'; + tts := ttts.Create(p, v); + tts.OnPlayFinished := endPlay; + tts.SetModel(m); + tts.TextToSpeech(s, true); + end; + end; + except + + end; + +end; + +end. diff --git a/uTTWIRC.txt b/uTTWIRC.txt new file mode 100644 index 0000000..ac4f3bd --- /dev/null +++ b/uTTWIRC.txt @@ -0,0 +1,342 @@ +unit uTTWIRC; + +interface + +uses + System.Classes, System.SysUtils, IdIRC, IdSSLOpenSSL, IdContext, + FMX.Forms, IdGlobal, IdComponent, System.StrUtils, uRecords; + + + +type + TNotifyEvent = procedure(s: string) of object; + TJoinEvent = procedure(aNick: string) of object; + TMyStatusEvent = procedure(ASender: TObject; const AStatus: TIdStatus; const AStatusText: string) of object; + tOnMessageRecord = procedure(aRecord: TTwitchChatMessage) of object; + TOnLog = procedure(aModul: string; aMethod: string; aMessage: string; aLevel: integer) of object; + +type + TTTW = class(TObject) + private + ws: TIdIRC; + ssl: TIdSSLIOHandlerSocketOpenSSL; + channel_name: string; + Token: string; + + FOnConnect: TNotifyEvent; + FOnDisConnect: TNotifyEvent; + + FOnJoin: TJoinEvent; + FOnStatus: TMyStatusEvent; + FOnMessageRecord:tOnMessageRecord; + FOnLog: TOnLog; + room_id: string; + + procedure wsConnected(Sender: TObject); + procedure wsDisconnected(Sender: TObject); + procedure wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string); + procedure toParse(t: string); + procedure Join(ASender: TIdContext; const ANickname, AHost, AChannel: string); + procedure se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String); + function Pars(T_, text, _T: string): string; + function ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage; + procedure toLog(aLevel: integer; aMethod: string; aMessage: string); + public + constructor Create(Sender: TObject); + destructor Destroy; override; + procedure Init(myToken, Channel, Bot_Name: string); + procedure sendMessage(text: string); + procedure RAW(text: string); + procedure Connect; + procedure Disconnect; + + function GetRoom_ID:string; + property FRoom_ID:string read GetRoom_ID; + property OnConnect: TNotifyEvent read FOnConnect write FOnConnect; + property OnDisConnect: TNotifyEvent read FOnDisConnect write FOnDisConnect; + + property OnMessageRecord: tOnMessageRecord read FOnMessageRecord write FOnMessageRecord; + property OnLog: TOnLog read FOnLog write FOnLog; + property OnJoin: TJoinEvent read FOnJoin write FOnJoin; + property OnStatus: TMyStatusEvent read FOnStatus write FOnStatus; + end; + + + + +implementation + +uses uGeneral; // Для доступа к процедуре Log + +procedure TTTW.toLog(aLevel: integer; aMethod: string; aMessage: string); +begin + if Assigned(FOnLog) then + FOnLog('uTTWIRC', aMethod, aMessage, aLevel); +end; + +constructor TTTW.Create(Sender: TObject); +begin + try + ws := TIdIRC.Create; + ssl := TIdSSLIOHandlerSocketOpenSSL.Create; + ws.IOHandler := ssl; + ws.OnConnected := wsConnected; + ws.OnDisconnected := wsDisconnected; + ws.OnStatus:= FOnStatus; + ws.OnRaw := wsDataIn; + ws.OnJoin := Join; + ws.OnServerError := se; + except + on E: Exception do + toLog(2, 'Create', E.Message); + end; +end; + +destructor TTTW.Destroy; +begin + ws.Free; + inherited; +end; + +function TTTW.ParseTwitchChatMessage(const AMessage: string): TTwitchChatMessage; +var + s: string; + LSpacePos: Integer; + LParamStr, LRestStr: string; + LParams: TArray; + LKeyValue: TArray; + I: Integer; + LUsernamePart: string; + LMessagePos: Integer; +begin + s := AMessage; + + // Разделяем строку на параметры и остальную часть + LSpacePos := Pos(' ', s); + if LSpacePos > 0 then + begin + LParamStr := Copy(s, 1, LSpacePos - 1); + LRestStr := Copy(s, LSpacePos + 1, Length(s) - LSpacePos); + end + else + begin + LParamStr := s; + LRestStr := ''; + end; + + // Обрабатываем параметры + LParams := LParamStr.Split([';']); + for I := 0 to High(LParams) do + begin + LKeyValue := LParams[I].Split(['=']); + if Length(LKeyValue) = 2 then + begin + case AnsiIndexStr(LKeyValue[0], [ + '@badge-info', 'badges', 'client-nonce', 'color', 'display-name', 'emotes', + 'first-msg', 'id', 'mod', 'returning-chatter', 'room-id', 'subscriber', + 'tmi-sent-ts', 'turbo', 'user-id', 'user-type', 'vip' + ]) of + 0: Result.BadgeInfo := LKeyValue[1]; + 1: Result.Badges := LKeyValue[1]; + 2: Result.ClientNonce := LKeyValue[1]; + 3: Result.Color := LKeyValue[1]; + 4: Result.DisplayName := LKeyValue[1]; + 5: Result.Emotes := LKeyValue[1]; + 6: Result.FirstMsg := StrToIntDef(LKeyValue[1], 0); + 7: Result.Id := LKeyValue[1]; + 8: Result.Moder := StrToIntDef(LKeyValue[1], 0); + 9: Result.ReturningChatter := StrToIntDef(LKeyValue[1], 0); + 10: Result.RoomId := LKeyValue[1]; + 11: Result.Subscriber := StrToIntDef(LKeyValue[1], 0); + 12: Result.TmiSentTs := StrToInt64Def(LKeyValue[1], 0); + 13: Result.Turbo := StrToIntDef(LKeyValue[1], 0); + 14: Result.UserId := LKeyValue[1]; + 15: Result.UserType := LKeyValue[1]; + 16: Result.Vip := StrToIntDef(LKeyValue[1], 0); + end; + end; + end; + + // Извлекаем имя пользователя + if LRestStr.StartsWith(':') then + begin + LUsernamePart := Copy(LRestStr, 1, Pos('!', LRestStr) - 1); + Result.Username := LUsernamePart.Substring(1); + end + else + Result.Username := ''; + + // Извлекаем канал + LMessagePos := Pos('PRIVMSG #', LRestStr); + if LMessagePos > 0 then + begin + Inc(LMessagePos, Length('PRIVMSG #')); + Result.Channel := Copy(LRestStr, LMessagePos, PosEx(' ', LRestStr, LMessagePos) - LMessagePos); + end + else + Result.Channel := ''; + + // Извлекаем сообщение + LMessagePos := Pos(' :', LRestStr); + if LMessagePos > 0 then + Result.Message := Copy(LRestStr, LMessagePos + 2, MaxInt).Trim + else + Result.Message := ''; +end; + +procedure TTTW.Init(myToken, Channel, Bot_Name: string); +begin + try + ws.Host := 'irc.chat.twitch.tv'; + ws.Port := 6697; + ssl.SSLOptions.SSLVersions := [sslvSSLv23]; + ws.Password := 'oauth:' + myToken; + ws.Nickname := Bot_Name; + channel_name := Channel; + Token := myToken; + except + on E: Exception do + toLog(2, 'Init', E.Message); + end; +end; + +procedure TTTW.Connect; +begin + try + ws.Connect; + ws.Raw('CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands'); + ws.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8; + except + on E: Exception do + toLog(2, 'Connect', E.Message); + end; +end; + +procedure TTTW.Disconnect; +begin + try + if ws.Connected then // Добавляем проверку состояния + begin + ws.Disconnect; + end; + except + on E: Exception do + toLog(2, 'Disconnect', E.ClassName + ': ' + E.Message); + end; +end; + +function TTTW.GetRoom_ID: string; +begin +result:=room_id; +end; + +procedure TTTW.sendMessage(text: string); +begin + try + ws.Say('#' + channel_name, text); + except + on E: Exception do + toLog(2, 'sendMessage', E.Message); + end; +end; + +procedure TTTW.RAW(text: string); +begin + try + ws.Raw(text); + except + on E: Exception do + toLog(2, 'RAW', E.Message); + end; +end; + +procedure TTTW.wsConnected(Sender: TObject); +begin + if Assigned(FOnStatus) then + FOnStatus(ws, TIdStatus.hsDisconnected, 'Connected.'); + if Assigned(FOnConnect) then + FOnConnect('Connected'); + toLog(0, 'wsConnected', 'Connected to Twitch IRC'); +end; + +procedure TTTW.wsDisconnected(Sender: TObject); +begin + if Assigned(FOnStatus) then + FOnStatus(ws, TIdStatus.hsDisconnected, 'Disconnected.'); + if Assigned(FOnDisConnect) then + FOnDisConnect('Disconnected'); + toLog(1, 'wsDisconnected', 'Disconnected from Twitch IRC'); +end; + +procedure TTTW.wsDataIn(ASender: TIdContext; AIn: boolean; const AMessage: string); +begin + + toLog(3, 'wsDataIn', AMessage); + + if Pos('CAP * ACK', AMessage) <> 0 then + begin + Sleep(200); + ws.Raw('JOIN #' + channel_name); + end; + toParse(AMessage); +end; + +procedure TTTW.toParse(t: string); +var + LTwitchChatMessage:tTwitchChatMessage; +begin + try + if (Pos('room-id=', t) <> 0) and (Pos('ROOMSTATE', t) <> 0) then + room_id := Pars('room-id=', t, ';'); + + if Pos('NOTICE * :Login authentication failed', t) <> 0 then + begin + toLog(2, 'toParse', 'Токен бота просрочен'); + Disconnect; + Exit; + end; + + if Pos('PRIVMSG', t) <> 0 then + begin + LTwitchChatMessage := ParseTwitchChatMessage(t); + if Assigned(FOnMessageRecord) then + FOnMessageRecord(LTwitchChatMessage); + end; + except + on E: Exception do + toLog(2, 'toParse', E.Message); + end; +end; + + +procedure TTTW.Join(ASender: TIdContext; const ANickname, AHost, AChannel: string); +begin + if Assigned(FOnJoin) then + FOnJoin(ANickname); + toLog(0, 'Join', ANickname + ' joined ' + AChannel); +end; + +procedure TTTW.se(ASender: TIdContext; AErrorCode: Integer; const AErrorMessage: String); +begin + toLog(2, 'se', AErrorMessage); +end; + +function TTTW.Pars(T_, text, _T: string): string; +var + a, b: Integer; +begin + Result := ''; + if (T_ = '') or (text = '') or (_T = '') then + Exit; + a := Pos(T_, text); + if a = 0 then + Exit + else + a := a + Length(T_); + text := Copy(text, a, Length(text) - a + 1); + b := Pos(_T, text); + if b > 0 then + Result := Copy(text, 1, b - 1); +end; + +end. diff --git a/uDataBase.pas b/utils/uDataBase.pas similarity index 100% rename from uDataBase.pas rename to utils/uDataBase.pas diff --git a/uMyTimer.pas b/utils/uMyTimer.pas similarity index 100% rename from uMyTimer.pas rename to utils/uMyTimer.pas diff --git a/utils/uOBS_Doc_Player.dfm b/utils/uOBS_Doc_Player.dfm new file mode 100644 index 0000000..c10015e --- /dev/null +++ b/utils/uOBS_Doc_Player.dfm @@ -0,0 +1,13 @@ +object OBS_Doc_Player: TOBS_Doc_Player + OnCreate = WebModuleCreate + OnDestroy = WebModuleDestroy + Actions = < + item + Default = True + Name = 'DefaultHandler' + PathInfo = '/' + OnAction = WebModule1DefaultHandlerAction + end> + Height = 230 + Width = 415 +end diff --git a/utils/uOBS_Doc_Player.pas b/utils/uOBS_Doc_Player.pas new file mode 100644 index 0000000..d034412 --- /dev/null +++ b/utils/uOBS_Doc_Player.pas @@ -0,0 +1,273 @@ +unit uOBS_Doc_Player; + +interface + +uses + System.SysUtils, System.Classes, Web.HTTPApp, FMX.Types, + FMX.Controls3D, FMX.Objects3D, FMX.Controls, FMX.Forms, FMX.StdCtrls, + FMX.Edit, FMX.ListBox, uPlayerThread, bass_simple; + +type + TOBS_Doc_Player = class(TWebModule) + procedure WebModule1DefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); + procedure Button1Click(Sender: TObject); + procedure Button2Click(Sender: TObject); + procedure TrackBar1Change(Sender: TObject); + procedure WebModuleCreate(Sender: TObject); + procedure WebModuleDestroy(Sender: TObject); + private + { Private declarations } + function gethtml(): string; + procedure AddToListBox(Text: String); + procedure DelFromListBox(Text: String); + procedure add(const aTitle: string); + procedure del(const aTitle: string); + { Private declarations } + public + { Public declarations } + end; + +var + OBS_Doc_Player: TComponentClass = TOBS_Doc_Player; + Button1: TButton; + Button2: TButton; + ProgressBar1: TProgressBar; + TrackBar1: TTrackBar; + ListBox1: TListBox; + b: TBassSimple; + player: TPlayerThread; + mVolume: Integer; + isplay: string; + mySoundPath: string; + +implementation + +{%CLASSGROUP 'FMX.Controls.TControl'} +{$R *.dfm} +{ TOBS_Doc_Player } + +function GetPathToTestExe: string; // вернет папку romaming +begin + Result := GetEnvironmentVariable('APPDATA'); + if Result <> '' then + Result := IncludeTrailingPathDelimiter(Result); +end; + +procedure TOBS_Doc_Player.add(const aTitle: string); +begin + AddToListBox(aTitle); +end; + +procedure TOBS_Doc_Player.AddToListBox(Text: String); +begin + if ListBox1.Items.IndexOf(Text) = -1 then + ListBox1.Items.add(Text); +end; + +procedure TOBS_Doc_Player.Button1Click(Sender: TObject); +begin + b.Pause; + if isplay = '0' then + isplay := '1' + else + isplay := '0' +end; + +procedure TOBS_Doc_Player.Button2Click(Sender: TObject); +begin + player.Skip; +end; + +procedure TOBS_Doc_Player.del(const aTitle: string); +begin + DelFromListBox(aTitle); +end; + +procedure TOBS_Doc_Player.DelFromListBox(Text: String); +var + i: Integer; +begin + Application.ProcessMessages; + i := ListBox1.Items.IndexOf(Text); + if i <> -1 then + if i <= ListBox1.Items.Count - 1 then + ListBox1.Items.Delete(i); + Application.ProcessMessages; +end; + +function TOBS_Doc_Player.gethtml: string; +var + s: string; + i: Integer; + playIcon: string; +begin + // Определяем иконку в зависимости от состояния + if isplay = '1' then + playIcon := 'fa-pause' + else + playIcon := 'fa-play'; + + // Собираем элементы ListBox в строку + s := ''; + for i := 0 to ListBox1.Items.Count - 1 do + s := s + '
  • ' + ListBox1.Items[i] + '
  • '; + + // Генерация HTML-страницы с обновленными значениями + Result := '' + 'Web Radio' + + '' + + '' + + + '' + '' + + '' + '
    ' + + + '' + + '' + '
    ' + + + '
    ' + '' + + '' + '
    ' + + + '
      ' + s + '
    ' + + + '' + '' + + ''; +end; + +procedure TOBS_Doc_Player.TrackBar1Change(Sender: TObject); +begin + mVolume := Round(TrackBar1.Value); + b.Volume := mVolume; +end; + +procedure TOBS_Doc_Player.WebModule1DefaultHandlerAction(Sender: TObject; + Request: TWebRequest; Response: TWebResponse; var Handled: Boolean); +var + VolumeStr: string; + VolumeValue, i: Integer; +begin + if Request.PathInfo = '/getSongs' then + begin + // Возвращаем список песен в формате JSON + var + songList: string := '['; + for i := 0 to ListBox1.Items.Count - 1 do + begin + songList := songList + '"' + ListBox1.Items[i] + '"'; + if i < ListBox1.Items.Count - 1 then + songList := songList + ','; + end; + songList := songList + ']'; + + Response.Content := songList; // Отправляем JSON + Response.ContentType := 'application/json;charset=utf8'; + Handled := True; + Exit; + end; + + if Request.PathInfo = '/setVolume' then + begin + // Получаем значение из параметра 'value' + VolumeStr := Request.QueryFields.Values['value']; + // Пробуем преобразовать в целое число + if TryStrToInt(VolumeStr, VolumeValue) then + begin + // Если удалось преобразовать, обновляем переменную mVolume + mVolume := VolumeValue; + // Обновляем звук на сервере (если нужно, например, через библиотеку bass) + b.Volume := mVolume; + // Возвращаем обновленное состояние на страницу + Response.Content := gethtml; + end + else + begin + // Если не удалось преобразовать, возвращаем ошибку + Response.Content := 'Invalid volume value'; + end; + Handled := True; // Запрос обработан + Exit; + end; + + if Request.PathInfo = '/button1' then + begin + Button1Click(Sender); // Вызов процедуры для кнопки 1 + Response.Content := gethtml; + Handled := True; // Указываем, что запрос был обработан + Exit; + end; + + if Request.PathInfo = '/button2' then + begin + Button2Click(Sender); // Вызов процедуры для кнопки 2 + Response.Content := gethtml; + Handled := True; // Указываем, что запрос был обработан + Exit; + end; + + Response.Content := gethtml; + Handled := True; +end; + +procedure TOBS_Doc_Player.WebModuleCreate(Sender: TObject); +begin + mySoundPath := GetPathToTestExe + 'TTW_Bot\ytSongs'; + b := TBassSimple.Create(0); + player := TPlayerThread.Create(b, mySoundPath); + player.OnAddAd := add; + player.OnSkip := del; + mVolume := 0; + ListBox1 := TListBox.Create(self); + player.Start; + isplay := '1'; +end; + +procedure TOBS_Doc_Player.WebModuleDestroy(Sender: TObject); +begin + player.Free; + b.Free; + ListBox1.Free; +end; + +end. diff --git a/utils/uPlayerThread.pas b/utils/uPlayerThread.pas new file mode 100644 index 0000000..59fd555 --- /dev/null +++ b/utils/uPlayerThread.pas @@ -0,0 +1,183 @@ +unit uPlayerThread; + +interface + +uses + Classes, SysUtils, SyncObjs, Generics.Collections, bass_simple, + System.IOUtils, Types; + +type + TOnError = procedure(const Msg, FileName: string) of object; + TOnSkip = procedure(const FileName: string) of object; + + TPlayerThread = class(TThread) + private + b: tbasssimple; + FFilesQueue: TList; + FQueueCS: TCriticalSection; + FCurrentFile: string; + FOnError: TOnError; + FOnSkip: TOnSkip; + FOnAddAd: TOnSkip; + FMusicFolder: string; + FIsPlaying: Boolean; + procedure PlayCurrentFile; + procedure ScanFolder; + procedure OnPlayHandler(Sender: TObject); + procedure OnStopHandler(Sender: TObject); + procedure OnEndPlayHandler(Sender: TObject); + protected + procedure Execute; override; + public + constructor Create(var ab: tbasssimple; const aFolder: string); + destructor Destroy; override; + procedure Skip; + property OnError: TOnError read FOnError write FOnError; + property OnSkip: TOnSkip read FOnSkip write FOnSkip; + property OnAddAd: TOnSkip read FOnAddAd write FOnAddAd; + end; + +implementation + +constructor TPlayerThread.Create(var ab: tbasssimple; const aFolder: string); +begin + inherited Create(True); + b := ab; + FMusicFolder := aFolder; + FIsPlaying := False; + b.OnPlay := OnPlayHandler; + b.OnStop := OnStopHandler; + b.OnEndPlay := OnEndPlayHandler; + FFilesQueue := TList.Create; + FQueueCS := TCriticalSection.Create; +end; + +destructor TPlayerThread.Destroy; +begin + FQueueCS.Enter; + try + FFilesQueue.Free; + finally + FQueueCS.Leave; + end; + FQueueCS.Free; + + inherited; +end; + +procedure TPlayerThread.Execute; +begin + while not Terminated do + begin + + ScanFolder; + if (not FIsPlaying) then + begin + FQueueCS.Enter; + try + if (FFilesQueue.Count > 0) then + begin + FCurrentFile := FFilesQueue[0]; + FFilesQueue.Delete(0); + end; + finally + FQueueCS.Leave; + end; + PlayCurrentFile; + end; + + Sleep(1000); + end; +end; + +procedure TPlayerThread.PlayCurrentFile; +begin + if (FCurrentFile = '') or (not FileExists(FCurrentFile)) then + begin + if Assigned(FOnError) then + FOnError('Файл не найден', FCurrentFile); + Exit; + end; + + // Создаем новый поток и начинаем воспроизведение + TThread.Synchronize(nil, + procedure + begin + b.Play(FCurrentFile); + end); + + FIsPlaying := True; + +end; + +procedure TPlayerThread.ScanFolder; +var + Files: TStringDynArray; + FileName: string; +begin + Files := TDirectory.GetFiles(FMusicFolder, '*.mp3'); + FQueueCS.Enter; + try + for FileName in Files do + if FFilesQueue.IndexOf(FileName) = -1 then + begin + Sleep(5000); + FFilesQueue.Add(FileName); + if Assigned(FOnAddAd) then + TThread.Synchronize(nil, + procedure + begin + FOnAddAd(ExtractFileName(FileName)); + end); + end; + finally + FQueueCS.Leave; + end; +end; + +procedure TPlayerThread.OnPlayHandler(Sender: TObject); +begin + FIsPlaying := True; +end; + +procedure TPlayerThread.OnStopHandler(Sender: TObject); +begin + FIsPlaying := False; +end; + +procedure TPlayerThread.OnEndPlayHandler(Sender: TObject); +begin + try + b.Stop; + b.FreeStream; + if FileExists(FCurrentFile) then + begin + DeleteFile(FCurrentFile); + if Assigned(FOnSkip) then + FOnSkip(ExtractFileName(FCurrentFile)); + end; + FCurrentFile := ''; + FIsPlaying := False; + except + + end; +end; + +procedure TPlayerThread.Skip; +begin + if FIsPlaying then + begin + b.Stop; + b.FreeStream; + if FileExists(FCurrentFile) then + begin + DeleteFile(FCurrentFile); + if Assigned(FOnSkip) then + FOnSkip(ExtractFileName(FCurrentFile)); + end; + FCurrentFile := ''; + FIsPlaying := False; + end; +end; + +end. diff --git a/uRecords.pas b/utils/uRecords.pas similarity index 99% rename from uRecords.pas rename to utils/uRecords.pas index d91562e..48ab346 100644 --- a/uRecords.pas +++ b/utils/uRecords.pas @@ -2,6 +2,9 @@ unit uRecords; interface + + + type TRLog = record rTime: ttime; @@ -77,6 +80,7 @@ type MaxCountMess: integer; TimeMess: integer; port: integer; + freez:integer; StyleFont: integer; end; @@ -103,7 +107,6 @@ type soundsPath: string; stlPath: string; ytSongsPath: string; - PublicPlay: string; SilentPlay: string; ytPlay: string; cfg1: string; diff --git a/uRegExpr.pas b/utils/uRegExpr.pas similarity index 100% rename from uRegExpr.pas rename to utils/uRegExpr.pas diff --git a/utils/uSoundManager.pas b/utils/uSoundManager.pas new file mode 100644 index 0000000..5708c30 --- /dev/null +++ b/utils/uSoundManager.pas @@ -0,0 +1,98 @@ +unit uSoundManager; + +interface + +uses classes, ShellAPI, bass_simple, windows, System.SysUtils; + +type + TSongMachine = class(TObject) + + private + + public + constructor Create; + destructor Destroy; + procedure PlayPublic(AFileName: string; aVolume: string); + procedure PlaySilent(AFileName: string; aVolume: string); + + end; + +implementation + +uses uGeneral; + +var + mp: TBassSimple; + + { SongMachine } + +constructor TSongMachine.Create; +begin + mp := TBassSimple.Create(0); +end; + +destructor TSongMachine.Destroy; +begin + mp.FreeStream; + mp.Free; +end; + +function TimeToSeconds(const timeStr: string): Integer; +var + minutes, seconds: Integer; +begin + if TryStrToInt(Copy(timeStr, 1, 2), minutes) and + TryStrToInt(Copy(timeStr, 4, 5), seconds) then + begin + result := minutes * 60 + seconds; + end + else + begin + result := -1; + end; +end; + +procedure TSongMachine.PlaySilent(AFileName: string; aVolume: string); +var + sec: string; + mm: TBassSimple; +begin + try + if not FileExists(AFileName) then + begin + TTW_Bot.toLog( 'TSongMachine', 'PlayPublic', 'Нет файла ' + AFileName,2); + exit; + end; + mm := TBassSimple.Create(0); + try + mm.OpenFile(AFileName); + sec := inttostr(TimeToSeconds(mm.TimeLength) + 1); + finally + mm.FreeStream; + mm.Free; + end; + ShellExecute(0, 'open', PChar(myConst.SilentPlay), + PChar(Format('%s %s "%s"', [sec, aVolume, AFileName])), nil, SW_HIDE); + except + on e: Exception do + TTW_Bot.toLog( 'TSongMachine', 'PlaySilent', e.message,2) + end; +end; + +procedure TSongMachine.PlayPublic(AFileName: string; aVolume: string); +begin + try + if not FileExists(AFileName) then + begin + TTW_Bot.toLog( 'TSongMachine', 'PlayPublic', 'Нет файла ' + AFileName,2); + exit; + end; + mp.Volume := strtoint(aVolume); + mp.Play(AFileName) + except + on e: Exception do + TTW_Bot.toLog( 'TSongMachine', 'PlaySilent', e.message,2) + end; +end; + +end. diff --git a/utils/uTTS.pas b/utils/uTTS.pas new file mode 100644 index 0000000..4860dd4 --- /dev/null +++ b/utils/uTTS.pas @@ -0,0 +1,212 @@ +unit uTTS; + +interface + +uses + Windows, SysUtils, Classes, MMSystem; + +type + TPlayFinishedEvent = procedure(Sender: TObject) of object; + +type + TTTS = class + private + FPiperExePath: string; + FVoicesFolder: string; + FCurrentModel: string; + FOnPlayFinished: TPlayFinishedEvent; + procedure DoPlayFinished; + function RunProcessCaptureOutput(const Exe, Args, InputText: string; out OutputText: string): Boolean; + public + constructor Create(const APiperExePath, AVoicesFolder: string); + /// Список моделей (файлы .onnx) в папке voices + function GetModelsList: TStringList; + + /// Выбрать модель озвучивания (имя файла .onnx) + procedure SetModel(const ModelFileName: string); + + /// Озвучить текст выбранной моделью + procedure TextToSpeech(const aText: string; isDeleteFile: Boolean = False); + + property OnPlayFinished: TPlayFinishedEvent read FOnPlayFinished write FOnPlayFinished; + end; + +implementation + +procedure TTTS.DoPlayFinished; +begin + if Assigned(FOnPlayFinished) then + FOnPlayFinished(Self); +end; + +constructor TTTS.Create(const APiperExePath, AVoicesFolder: string); +begin + inherited Create; + FPiperExePath := APiperExePath; + FVoicesFolder := AVoicesFolder; + FCurrentModel := ''; // Пока не выбрана модель +end; + +function TTTS.GetModelsList: TStringList; +var + SR: TSearchRec; +begin + Result := TStringList.Create; + if not DirectoryExists(FVoicesFolder) then Exit; + if FindFirst(FVoicesFolder + PathDelim + '*.onnx', faAnyFile, SR) = 0 then + begin + repeat + Result.Add(SR.Name); + until FindNext(SR) <> 0; + FindClose(SR); + end; +end; + +procedure TTTS.SetModel(const ModelFileName: string); +var + FullPath: string; +begin + FullPath := IncludeTrailingPathDelimiter(FVoicesFolder) + ModelFileName; + if not FileExists(FullPath) then + raise Exception.CreateFmt('Модель не найдена: %s', [FullPath]); + FCurrentModel := FullPath; +end; + +function TTTS.RunProcessCaptureOutput(const Exe, Args, InputText: string; out OutputText: string): Boolean; +var + SecAttr: TSecurityAttributes; + StdOutRead, StdOutWrite: THandle; + StdInRead, StdInWrite: THandle; + StartupInfo: TStartupInfo; + ProcInfo: TProcessInformation; + Buffer: array [0..4095] of AnsiChar; + BytesRead: Cardinal; + ReadOK: BOOL; + Stream: TStringStream; + InheritHandles: Boolean; + BytesWritten: Cardinal; + Utf8Bytes: TBytes; +begin + Result := False; + OutputText := ''; + Stream := TStringStream.Create('', TEncoding.UTF8); + try + SecAttr.nLength := SizeOf(SecAttr); + SecAttr.bInheritHandle := True; + SecAttr.lpSecurityDescriptor := nil; + + if not CreatePipe(StdOutRead, StdOutWrite, @SecAttr, 0) then Exit; + try + if not SetHandleInformation(StdOutRead, HANDLE_FLAG_INHERIT, 0) then Exit; + + if not CreatePipe(StdInRead, StdInWrite, @SecAttr, 0) then Exit; + try + if not SetHandleInformation(StdInWrite, HANDLE_FLAG_INHERIT, 0) then Exit; + + ZeroMemory(@StartupInfo, SizeOf(StartupInfo)); + StartupInfo.cb := SizeOf(StartupInfo); + StartupInfo.hStdOutput := StdOutWrite; + StartupInfo.hStdError := StdOutWrite; + StartupInfo.hStdInput := StdInRead; + StartupInfo.dwFlags := STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW; + StartupInfo.wShowWindow := SW_HIDE; + + InheritHandles := True; + + if not CreateProcess(PChar(Exe), PChar('"' + Exe + '" ' + Args), nil, nil, + InheritHandles, CREATE_NO_WINDOW, nil, nil, StartupInfo, ProcInfo) then Exit; + + CloseHandle(StdOutWrite); + CloseHandle(StdInRead); + + if InputText <> '' then + begin + Utf8Bytes := TEncoding.UTF8.GetBytes(InputText + #10); + WriteFile(StdInWrite, Utf8Bytes[0], Length(Utf8Bytes), BytesWritten, nil); + end; + CloseHandle(StdInWrite); + + repeat + ReadOK := ReadFile(StdOutRead, Buffer, SizeOf(Buffer), BytesRead, nil); + if ReadOK and (BytesRead > 0) then + Stream.Write(Buffer, BytesRead); + until not ReadOK or (BytesRead = 0); + + WaitForSingleObject(ProcInfo.hProcess, INFINITE); + + CloseHandle(ProcInfo.hThread); + CloseHandle(ProcInfo.hProcess); + + OutputText := Stream.DataString; + Result := True; + finally + CloseHandle(StdInWrite); + CloseHandle(StdInRead); + end; + finally + CloseHandle(StdOutRead); + CloseHandle(StdOutWrite); + end; + finally + Stream.Free; + end; +end; + +procedure TTTS.TextToSpeech(const aText: string; isDeleteFile: Boolean); +var + Args, Text, Output, WavFile: string; + Lines: TStringList; + i: Integer; +begin + if (FPiperExePath = '') or (FCurrentModel = '') then + raise Exception.Create('Piper.exe или модель не указаны.'); + + Text := Trim(aText); + if Text = '' then Exit; + + // Формируем аргументы - пусть Piper сохраняет wav в текущую папку с уникальным именем + Args := '--model "' + FCurrentModel + '" -f o.wav'; + + if not RunProcessCaptureOutput(FPiperExePath, Args, Text, Output) then + begin + + Exit; + end; + + Lines := TStringList.Create; + try + Lines.Text := Output; + WavFile := ''; + for i := Lines.Count - 1 downto 0 do + if (Pos('.wav', LowerCase(Lines[i])) > 0) and FileExists(Trim(Lines[i])) then + begin + WavFile := Trim(Lines[i]); + Break; + end; + + if WavFile <> '' then + begin + // Запускаем в отдельном потоке, чтобы отследить окончание + TThread.CreateAnonymousThread( + procedure + begin + PlaySound(PChar(WavFile), 0, SND_FILENAME); // без SND_ASYNC — ждём окончания + if isDeleteFile then + DeleteFile(WavFile); + TThread.Synchronize(nil, + procedure + begin + DoPlayFinished; + end + ); + end + ).Start; + end; + + finally + Lines.Free; + end; +end; + +end. + diff --git a/utils/uWebServerChat.pas b/utils/uWebServerChat.pas new file mode 100644 index 0000000..20f2fbd --- /dev/null +++ b/utils/uWebServerChat.pas @@ -0,0 +1,293 @@ +unit uWebServerChat; + +interface + +uses classes, StrUtils, DateUtils, System.JSON, System.Generics.Collections, + IdBaseComponent, IdComponent, IdCustomTCPServer, IdContext, + IdCustomHTTPServer, uRecords, System.IOUtils, IdGlobalProtocols, + IdHTTPServer, System.SysUtils; + +type + TTwitchMessage = record + Nickname: string; + Content: string; + Timestamp: TDateTime; + TimeMsg: Integer; + end; + +type + TTTW_Chat = class(TObject) + msgStyle: TStyleChat; + fFontsList: tstringlist; + IdHTTPServer1: TIdHTTPServer; + procedure IdHTTPServer1CommandGet(AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); + private + FBColor: string; + Messages: TThreadList; + FDeleteByTime: Boolean; // Режим удаления: по времени (true) или количеству (false) + FMaxMsgCount: Integer; // Максимальное количество сообщений + function GenerateHTML: string; + function GenerateJSON: string; + procedure CleanupOldMessages; + public + constructor Create(FontList: tstrings; aPort:integer; aColor:string); + destructor Destroy; + procedure addMessage(newMsg: TStyleChat); + procedure ActiveServer(aEn: boolean); + procedure SetDeleteMode(DeleteByTime: Boolean; MaxMsgCount: Integer); // Установка режима удаления + procedure changeBackground(aColor:string); + end; + +var + Timestamp2: string; + +implementation + +uses uGeneral; + +{ TTTW_Chat } + +procedure TTTW_Chat.SetDeleteMode(DeleteByTime: Boolean; MaxMsgCount: Integer); +begin + FDeleteByTime := DeleteByTime; + FMaxMsgCount := MaxMsgCount; +end; + +procedure TTTW_Chat.ActiveServer(aEn: boolean); +begin + IdHTTPServer1.Active := aEn; +end; + +procedure TTTW_Chat.addMessage(newMsg: TStyleChat); +var + Msg: TTwitchMessage; +begin + Msg.Nickname := newMsg.Nick; + Msg.Content := newMsg.Context; + Msg.Timestamp := now; + Msg.TimeMsg := newMsg.TimeMsg; + msgStyle := newMsg; + with Messages.LockList do +try + if not FDeleteByTime then + begin + // Удаление старых сообщений при превышении лимита + while Count >= FMaxMsgCount do + Delete(0); + end; + Add(Msg); + finally + Messages.UnlockList; + end; +end; + +procedure TTTW_Chat.changeBackground(aColor: string); +begin +FBColor:=aColor; +end; + +procedure TTTW_Chat.CleanupOldMessages; +var + MsgList: TList; + I: integer; + ExpiryTime: TDateTime; +begin + if not FDeleteByTime then + Exit; // Выходим, если удаление по времени отключено + MsgList := Messages.LockList; + try + for I := MsgList.Count - 1 downto 0 do + begin + ExpiryTime := Now - (MsgList[I].TimeMsg / 86400); // Используем значение из сообщения + if MsgList[I].Timestamp < ExpiryTime then + MsgList.Delete(I); + end; + finally + Messages.UnlockList; + end; +end; + +constructor TTTW_Chat.Create(FontList: tstrings; aPort:integer;AColor:string); +var + I: integer; +begin + FBColor := AColor; + Messages := TThreadList.Create; + IdHTTPServer1 := TIdHTTPServer.Create; + IdHTTPServer1.DefaultPort := aPort; + IdHTTPServer1.OnCommandGet := IdHTTPServer1CommandGet; + fFontsList := tstringlist.Create; + for I := 0 to FontList.Count - 1 do + fFontsList.Add(FontList[I]); + FDeleteByTime := True; // По умолчанию удаление по времени + FMaxMsgCount := 100; // Значение по умолчанию + +end; + +destructor TTTW_Chat.Destroy; +begin + Messages.Free; + IdHTTPServer1.Active := false; + IdHTTPServer1.Free; + fFontsList.Free; +end; + + +function TTTW_Chat.GenerateHTML: string; +var + I: integer; + s, s1: string; +DeleteByTimeJS: string; +begin + DeleteByTimeJS := LowerCase(BoolToStr(FDeleteByTime)); // Преобразуем булево значение в строку 'true' или 'false' +s := 'body { background: ' + FBColor + '; }' + #13#10; + for I := 41 to fFontsList.Count - 1 do + begin + s1 := StringReplace(fFontsList[I], '.ttf', '', [rfReplaceAll]); + s := s + '@font-face { font-family: ''' + s1 + '''; src: url(fonts/' + fFontsList[I] + '); }' + #13#10; + end; + + Result := '' + + '' + + '' + + 'Messages
    '; +end; + +function TTTW_Chat.GenerateJSON: string; +var + MsgList: TList; + Msg: TTwitchMessage; + JSONArray: TJSONArray; + JSONObject: TJSONObject; +begin + JSONArray := TJSONArray.Create; + try + MsgList := Messages.LockList; + try + for Msg in MsgList do + begin + JSONObject := TJSONObject.Create; + JSONObject.AddPair('nickname', Msg.Nickname); + JSONObject.AddPair('content', Msg.Content); + JSONObject.AddPair('timestamp', + TJSONNumber.Create(DateTimeToUnix(Msg.Timestamp))); + JSONObject.AddPair('color', msgStyle.BlockColor); // Оставляем HEX-цвет + JSONObject.AddPair('bcolor', msgStyle.BColor); // Оставляем HEX-цвет + JSONObject.AddPair('fontSize', TJSONNumber.Create(msgStyle.FontSize)); + JSONObject.AddPair('colorText', msgStyle.FontColor); + + JSONObject.AddPair('colorBorder', msgStyle.BorderColor); + JSONObject.AddPair('sizeBorder', + TJSONNumber.Create(msgStyle.BorderSize)); + JSONObject.AddPair('padding', TJSONNumber.Create(msgStyle.BorderSize)); + JSONObject.AddPair('family', msgStyle.FontFamily); + JSONObject.AddPair('timeMsg', TJSONNumber.Create(Msg.TimeMsg)); // Добавляем время + // Управляет только блоком, не текстом + JSONArray.Add(JSONObject); + end; + finally + Messages.UnlockList; + end; + Result := JSONArray.ToString; + finally + JSONArray.Free; + end; +end; + +procedure TTTW_Chat.IdHTTPServer1CommandGet(AContext: TIdContext; + ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo); +var + HtmlContent: string; + FontFileName: string; + FontFilePath: string; + MIMEType: string; + FS: TFileStream; +begin + CleanupOldMessages; + + if ARequestInfo.Document = '/messages' then + begin + AResponseInfo.ContentType := 'application/json; charset=utf-8'; + AResponseInfo.ContentText := GenerateJSON; + end + else if Pos('/fonts/', ARequestInfo.Document) = 1 then + // Проверяем запрос к шрифтам + begin + // Извлекаем имя файла из URL + FontFileName := TPath.GetFileName(ARequestInfo.Document); + + // Формируем полный путь к файлу (папка fonts должна быть рядом с исполняемым файлом) + FontFilePath := myConst.fontsPath + FontFileName; + + // Проверяем существование файла + if FileExists(FontFilePath) then + begin + + MIMEType := 'font/ttf'; + + // Настраиваем ответ + AResponseInfo.ContentType := MIMEType; + try + FS := TFileStream.Create(FontFilePath, fmOpenRead + fmShareDenyWrite); + AResponseInfo.ContentStream := FS; + AResponseInfo.ResponseNo := 200; + except + FS.Free; + AResponseInfo.ResponseNo := 500; + end; + end; + end + else + begin + AResponseInfo.CacheControl := 'no-cache, no-store, must-revalidate'; + AResponseInfo.Pragma := 'no-cache'; + AResponseInfo.Expires := 0; + Timestamp2 := IntToStr(DateTimeToUnix(now)); + AResponseInfo.ContentType := 'text/html; charset=utf-8'; + AResponseInfo.ContentText := GenerateHTML; + end; +end; + + +end.