mirror of
https://github.com/btclock/btclock_v3.git
synced 2024-11-19 04:10:01 +01:00
Nostr data source implementation
This commit is contained in:
parent
8e71f29d10
commit
87b22e5851
2
data
2
data
@ -1 +1 @@
|
|||||||
Subproject commit 2363d98965bb1fdbfdf5d130b41732f5b864e2d0
|
Subproject commit ee4d6d88c76fa279e643faabf4216c88145e0b2c
|
@ -41,6 +41,7 @@ lib_deps =
|
|||||||
https://github.com/dsbaars/universal_pin
|
https://github.com/dsbaars/universal_pin
|
||||||
https://github.com/dsbaars/GxEPD2#universal_pin
|
https://github.com/dsbaars/GxEPD2#universal_pin
|
||||||
https://github.com/tzapu/WiFiManager.git#v2.0.17
|
https://github.com/tzapu/WiFiManager.git#v2.0.17
|
||||||
|
rblb/Nostrduino@^1.2.5
|
||||||
|
|
||||||
[env:lolin_s3_mini]
|
[env:lolin_s3_mini]
|
||||||
extends = btclock_base
|
extends = btclock_base
|
||||||
|
@ -172,7 +172,25 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBlockHeight = block["height"].as<uint>();
|
processNewBlock(block["height"].as<uint>());
|
||||||
|
}
|
||||||
|
else if (doc.containsKey("mempool-blocks"))
|
||||||
|
{
|
||||||
|
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
|
||||||
|
|
||||||
|
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
|
||||||
|
|
||||||
|
processNewBlockFee(medianFee);
|
||||||
|
}
|
||||||
|
|
||||||
|
doc.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void processNewBlock(uint newBlockHeight) {
|
||||||
|
if (newBlockHeight < currentBlockHeight)
|
||||||
|
return;
|
||||||
|
|
||||||
|
currentBlockHeight = newBlockHeight;
|
||||||
|
|
||||||
// Serial.printf("New block found: %d\r\n", block["height"].as<uint>());
|
// Serial.printf("New block found: %d\r\n", block["height"].as<uint>());
|
||||||
preferences.putUInt("blockHeight", currentBlockHeight);
|
preferences.putUInt("blockHeight", currentBlockHeight);
|
||||||
@ -210,20 +228,15 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (doc.containsKey("mempool-blocks"))
|
|
||||||
{
|
|
||||||
JsonArray blockInfo = doc["mempool-blocks"].as<JsonArray>();
|
|
||||||
|
|
||||||
uint medianFee = (uint)round(blockInfo[0]["medianFee"].as<double>());
|
void processNewBlockFee(uint newBlockFee) {
|
||||||
|
if (blockMedianFee == newBlockFee)
|
||||||
if (blockMedianFee == medianFee)
|
|
||||||
{
|
{
|
||||||
doc.clear();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serial.printf("New median fee: %d\r\n", medianFee);
|
// Serial.printf("New median fee: %d\r\n", medianFee);
|
||||||
blockMedianFee = medianFee;
|
blockMedianFee = newBlockFee;
|
||||||
|
|
||||||
if (workQueue != nullptr)
|
if (workQueue != nullptr)
|
||||||
{
|
{
|
||||||
@ -232,9 +245,6 @@ void onWebsocketBlockMessage(esp_websocket_event_data_t *event_data)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
doc.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint getBlockHeight() { return currentBlockHeight; }
|
uint getBlockHeight() { return currentBlockHeight; }
|
||||||
|
|
||||||
void setBlockHeight(uint newBlockHeight)
|
void setBlockHeight(uint newBlockHeight)
|
||||||
|
@ -31,6 +31,9 @@ bool isBlockNotifyConnected();
|
|||||||
void stopBlockNotify();
|
void stopBlockNotify();
|
||||||
void restartBlockNotify();
|
void restartBlockNotify();
|
||||||
|
|
||||||
|
void processNewBlock(uint newBlockHeight);
|
||||||
|
void processNewBlockFee(uint newBlockFee);
|
||||||
|
|
||||||
bool getBlockNotifyInit();
|
bool getBlockNotifyInit();
|
||||||
uint getLastBlockUpdate();
|
uint getLastBlockUpdate();
|
||||||
int getBlockFetch();
|
int getBlockFetch();
|
||||||
|
@ -48,7 +48,9 @@ void setup()
|
|||||||
{
|
{
|
||||||
delay(1000);
|
delay(1000);
|
||||||
}
|
}
|
||||||
} else if (mcp1.digitalRead(1) == LOW) {
|
}
|
||||||
|
else if (mcp1.digitalRead(1) == LOW)
|
||||||
|
{
|
||||||
preferences.clear();
|
preferences.clear();
|
||||||
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
|
queueLedEffect(LED_EFFECT_WIFI_ERASE_SETTINGS);
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
@ -66,8 +68,16 @@ void setup()
|
|||||||
setupTasks();
|
setupTasks();
|
||||||
setupTimers();
|
setupTimers();
|
||||||
|
|
||||||
|
if (preferences.getBool("useNostr", DEFAULT_USE_NOSTR))
|
||||||
|
{
|
||||||
|
setupNostrNotify();
|
||||||
|
setupNostrTask();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL,
|
xTaskCreate(setupWebsocketClients, "setupWebsocketClients", 8192, NULL,
|
||||||
tskIDLE_PRIORITY, NULL);
|
tskIDLE_PRIORITY, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
setupButtonTask();
|
setupButtonTask();
|
||||||
setupOTA();
|
setupOTA();
|
||||||
@ -404,7 +414,8 @@ void setupHardware()
|
|||||||
Wire.beginTransmission(0x5C);
|
Wire.beginTransmission(0x5C);
|
||||||
byte error = Wire.endTransmission();
|
byte error = Wire.endTransmission();
|
||||||
|
|
||||||
if (error == 0) {
|
if (error == 0)
|
||||||
|
{
|
||||||
Serial.println(F("Found BH1750"));
|
Serial.println(F("Found BH1750"));
|
||||||
hasLuxSensor = true;
|
hasLuxSensor = true;
|
||||||
bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C);
|
bh1750.begin(BH1750::CONTINUOUS_LOW_RES_MODE, 0x5C);
|
||||||
@ -742,14 +753,15 @@ void setupFrontlight()
|
|||||||
{
|
{
|
||||||
preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE);
|
preferences.putBool("flFlashOnUpd", DEFAULT_FL_FLASH_ON_UPDATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float getLightLevel() {
|
float getLightLevel()
|
||||||
|
{
|
||||||
return bh1750.readLightLevel();
|
return bh1750.readLightLevel();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasLightLevel() {
|
bool hasLightLevel()
|
||||||
|
{
|
||||||
return hasLuxSensor;
|
return hasLuxSensor;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@ -784,5 +796,3 @@ String getFsRev()
|
|||||||
fsHash.close();
|
fsHash.close();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
#include "lib/improv.hpp"
|
#include "lib/improv.hpp"
|
||||||
#include "lib/led_handler.hpp"
|
#include "lib/led_handler.hpp"
|
||||||
#include "lib/ota.hpp"
|
#include "lib/ota.hpp"
|
||||||
|
#include "lib/nostr_notify.hpp"
|
||||||
|
|
||||||
#include "lib/price_notify.hpp"
|
#include "lib/price_notify.hpp"
|
||||||
#include "lib/screen_handler.hpp"
|
#include "lib/screen_handler.hpp"
|
||||||
#include "lib/shared.hpp"
|
#include "lib/shared.hpp"
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
#define DEFAULT_HOSTNAME_PREFIX "btclock"
|
#define DEFAULT_HOSTNAME_PREFIX "btclock"
|
||||||
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space"
|
#define DEFAULT_MEMPOOL_INSTANCE "mempool.space"
|
||||||
|
|
||||||
|
#define DEFAULT_USE_NOSTR false
|
||||||
|
#define DEFAULT_NOSTR_NPUB "642317135fd4c4205323b9dea8af3270657e62d51dc31a657c0ec8aab31c6288"
|
||||||
|
#define DEFAULT_NOSTR_RELAY "wss://nostr.dbtc.link"
|
||||||
|
|
||||||
#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30
|
#define DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE 30
|
||||||
#define DEFAULT_MINUTES_FULL_REFRESH 60
|
#define DEFAULT_MINUTES_FULL_REFRESH 60
|
||||||
|
|
||||||
|
123
src/lib/nostr_notify.cpp
Normal file
123
src/lib/nostr_notify.cpp
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
#include "nostr_notify.hpp"
|
||||||
|
|
||||||
|
std::vector<nostr::NostrPool *> pools;
|
||||||
|
nostr::Transport *transport;
|
||||||
|
TaskHandle_t nostrTaskHandle = NULL;
|
||||||
|
|
||||||
|
void setupNostrNotify()
|
||||||
|
{
|
||||||
|
nostr::esp32::ESP32Platform::initNostr(false);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
transport = nostr::esp32::ESP32Platform::getTransport();
|
||||||
|
nostr::NostrPool *pool = new nostr::NostrPool(transport);
|
||||||
|
String relay = preferences.getString("nostrRelay");
|
||||||
|
String pubKey = preferences.getString("nostrPubKey");
|
||||||
|
pools.push_back(pool);
|
||||||
|
// Lets subscribe to the relay
|
||||||
|
String subId = pool->subscribeMany(
|
||||||
|
{relay},
|
||||||
|
{{// we set the filters here (see
|
||||||
|
// https://github.com/nostr-protocol/nips/blob/master/01.md#from-client-to-relay-sending-events-and-creating-subscriptions)
|
||||||
|
{"kinds", {"1"}},
|
||||||
|
{"authors", {pubKey}}}},
|
||||||
|
[&](const String &subId, nostr::SignedNostrEvent *event)
|
||||||
|
{
|
||||||
|
// Received events callback, we can access the event content with
|
||||||
|
// event->getContent() Here you should handle the event, for this
|
||||||
|
// test we will just serialize it and print to console
|
||||||
|
JsonDocument doc;
|
||||||
|
JsonArray arr = doc["data"].to<JsonArray>();
|
||||||
|
event->toSendableEvent(arr);
|
||||||
|
// Access the second element which is the object
|
||||||
|
JsonObject obj = arr[1].as<JsonObject>();
|
||||||
|
|
||||||
|
// Access the "tags" array
|
||||||
|
JsonArray tags = obj["tags"].as<JsonArray>();
|
||||||
|
|
||||||
|
// Flag to check if the tag was found
|
||||||
|
bool tagFound = false;
|
||||||
|
uint medianFee = 0;
|
||||||
|
String typeValue;
|
||||||
|
|
||||||
|
// Iterate over the tags array
|
||||||
|
for (JsonArray tag : tags)
|
||||||
|
{
|
||||||
|
// Check if the tag is an array with two elements
|
||||||
|
if (tag.size() == 2)
|
||||||
|
{
|
||||||
|
const char *key = tag[0];
|
||||||
|
const char *value = tag[1];
|
||||||
|
|
||||||
|
// Check if the key is "type" and the value is "priceUsd"
|
||||||
|
if (strcmp(key, "type") == 0 && (strcmp(value, "priceUsd") == 0 || strcmp(value, "blockHeight") == 0))
|
||||||
|
{
|
||||||
|
typeValue = value;
|
||||||
|
tagFound = true;
|
||||||
|
}
|
||||||
|
else if (strcmp(key, "medianFee") == 0)
|
||||||
|
{
|
||||||
|
medianFee = tag[1].as<uint>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tagFound)
|
||||||
|
{
|
||||||
|
if (typeValue.equals("priceUsd"))
|
||||||
|
{
|
||||||
|
processNewPrice(obj["content"].as<uint>());
|
||||||
|
}
|
||||||
|
else if (typeValue.equals("blockHeight"))
|
||||||
|
{
|
||||||
|
processNewBlock(obj["content"].as<uint>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (medianFee != 0)
|
||||||
|
{
|
||||||
|
processNewBlockFee(medianFee);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[&](const String &subId, const String &reason)
|
||||||
|
{
|
||||||
|
// This is the callback that will be called when the subscription is
|
||||||
|
// closed
|
||||||
|
Serial.println("Subscription closed: " + reason);
|
||||||
|
},
|
||||||
|
[&](const String &subId)
|
||||||
|
{
|
||||||
|
// This is the callback that will be called when the subscription is
|
||||||
|
// EOSE
|
||||||
|
Serial.println("Subscription EOSE: " + subId);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (const std::exception &e)
|
||||||
|
{
|
||||||
|
Serial.println("Error: " + String(e.what()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void nostrTask(void *pvParameters)
|
||||||
|
{
|
||||||
|
int blockFetch = getBlockFetch();
|
||||||
|
processNewBlock(blockFetch);
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
for (nostr::NostrPool *pool : pools)
|
||||||
|
{
|
||||||
|
// Run internal loop: refresh relays, complete pending connections, send
|
||||||
|
// pending messages
|
||||||
|
pool->loop();
|
||||||
|
}
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(100));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setupNostrTask()
|
||||||
|
{
|
||||||
|
xTaskCreate(nostrTask, "nostrTask", 16384, NULL, 10, &nostrTaskHandle);
|
||||||
|
}
|
17
src/lib/nostr_notify.hpp
Normal file
17
src/lib/nostr_notify.hpp
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "shared.hpp"
|
||||||
|
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
#include <esp32/ESP32Platform.h>
|
||||||
|
#include <NostrEvent.h>
|
||||||
|
#include <NostrPool.h>
|
||||||
|
#include <Transport.h>
|
||||||
|
#include <Utils.h>
|
||||||
|
#include "price_notify.hpp"
|
||||||
|
#include "block_notify.hpp"
|
||||||
|
|
||||||
|
void setupNostrNotify();
|
||||||
|
|
||||||
|
void nostrTask(void *pvParameters);
|
||||||
|
void setupNostrTask();
|
@ -97,6 +97,13 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
|
|||||||
if (doc.containsKey("bitcoin"))
|
if (doc.containsKey("bitcoin"))
|
||||||
{
|
{
|
||||||
if (currentPrice != doc["bitcoin"].as<long>())
|
if (currentPrice != doc["bitcoin"].as<long>())
|
||||||
|
{
|
||||||
|
processNewPrice(doc["bitcoin"].as<long>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void processNewPrice(uint newPrice)
|
||||||
{
|
{
|
||||||
uint minSecPriceUpd = preferences.getUInt(
|
uint minSecPriceUpd = preferences.getUInt(
|
||||||
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
|
"minSecPriceUpd", DEFAULT_SECONDS_BETWEEN_PRICE_UPDATE);
|
||||||
@ -106,8 +113,12 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
|
|||||||
(currentTime - lastPriceUpdate) > minSecPriceUpd)
|
(currentTime - lastPriceUpdate) > minSecPriceUpd)
|
||||||
{
|
{
|
||||||
// const unsigned long oldPrice = currentPrice;
|
// const unsigned long oldPrice = currentPrice;
|
||||||
currentPrice = doc["bitcoin"].as<uint>();
|
currentPrice = newPrice;
|
||||||
|
if (lastPriceUpdate == 0 ||
|
||||||
|
(currentTime - lastPriceUpdate) > 120)
|
||||||
|
{
|
||||||
preferences.putUInt("lastPrice", currentPrice);
|
preferences.putUInt("lastPrice", currentPrice);
|
||||||
|
}
|
||||||
lastPriceUpdate = currentTime;
|
lastPriceUpdate = currentTime;
|
||||||
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
|
// if (abs((int)(oldPrice-currentPrice)) > round(0.0015*oldPrice)) {
|
||||||
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
|
if (workQueue != nullptr && (getCurrentScreen() == SCREEN_BTC_TICKER ||
|
||||||
@ -120,8 +131,6 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data)
|
|||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint getLastPriceUpdate()
|
uint getLastPriceUpdate()
|
||||||
{
|
{
|
||||||
|
@ -17,6 +17,8 @@ void onWebsocketPriceMessage(esp_websocket_event_data_t *event_data);
|
|||||||
uint getPrice();
|
uint getPrice();
|
||||||
void setPrice(uint newPrice);
|
void setPrice(uint newPrice);
|
||||||
|
|
||||||
|
void processNewPrice(uint newPrice);
|
||||||
|
|
||||||
bool isPriceNotifyConnected();
|
bool isPriceNotifyConnected();
|
||||||
void stopPriceNotify();
|
void stopPriceNotify();
|
||||||
void restartPriceNotify();
|
void restartPriceNotify();
|
||||||
|
@ -442,7 +442,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
|
|||||||
settings["timePerScreen"].as<uint>() * 60);
|
settings["timePerScreen"].as<uint>() * 60);
|
||||||
}
|
}
|
||||||
|
|
||||||
String strSettings[] = {"hostnamePrefix", "mempoolInstance"};
|
String strSettings[] = {"hostnamePrefix", "mempoolInstance", "nostrPubKey", "nostrRelay"};
|
||||||
|
|
||||||
for (String setting : strSettings)
|
for (String setting : strSettings)
|
||||||
{
|
{
|
||||||
@ -477,7 +477,7 @@ void onApiSettingsPatch(AsyncWebServerRequest *request, JsonVariant &json)
|
|||||||
String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd",
|
String boolSettings[] = {"fetchEurPrice", "ledTestOnPower", "ledFlashOnUpd",
|
||||||
"mdnsEnabled", "otaEnabled", "stealFocus",
|
"mdnsEnabled", "otaEnabled", "stealFocus",
|
||||||
"mcapBigChar", "useSatsSymbol", "useBlkCountdown",
|
"mcapBigChar", "useSatsSymbol", "useBlkCountdown",
|
||||||
"suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure"};
|
"suffixPrice", "disableLeds", "ownDataSource", "flAlwaysOn", "flFlashOnUpd", "mempoolSecure", "useNostr"};
|
||||||
|
|
||||||
for (String setting : boolSettings)
|
for (String setting : boolSettings)
|
||||||
{
|
{
|
||||||
@ -577,6 +577,7 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
|
|||||||
root["mempoolInstance"] =
|
root["mempoolInstance"] =
|
||||||
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
|
preferences.getString("mempoolInstance", DEFAULT_MEMPOOL_INSTANCE);
|
||||||
root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
|
root["mempoolSecure"] = preferences.getBool("mempoolSecure", DEFAULT_MEMPOOL_SECURE);
|
||||||
|
root["useNostr"] = preferences.getBool("useNostr", DEFAULT_USE_NOSTR);
|
||||||
root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER);
|
root["ledTestOnPower"] = preferences.getBool("ledTestOnPower", DEFAULT_LED_TEST_ON_POWER);
|
||||||
root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD);
|
root["ledFlashOnUpd"] = preferences.getBool("ledFlashOnUpd", DEFAULT_LED_FLASH_ON_UPD);
|
||||||
root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS);
|
root["ledBrightness"] = preferences.getUInt("ledBrightness", DEFAULT_LED_BRIGHTNESS);
|
||||||
@ -596,6 +597,9 @@ void onApiSettingsGet(AsyncWebServerRequest *request)
|
|||||||
root["txPower"] = WiFi.getTxPower();
|
root["txPower"] = WiFi.getTxPower();
|
||||||
root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE);
|
root["ownDataSource"] = preferences.getBool("ownDataSource", DEFAULT_OWN_DATA_SOURCE);
|
||||||
|
|
||||||
|
root["nostrPubKey"] = preferences.getString("nostrPubKey", DEFAULT_NOSTR_NPUB);
|
||||||
|
root["nostrRelay"] = preferences.getString("nostrRelay", DEFAULT_NOSTR_RELAY);
|
||||||
|
|
||||||
#ifdef HAS_FRONTLIGHT
|
#ifdef HAS_FRONTLIGHT
|
||||||
root["hasFrontlight"] = true;
|
root["hasFrontlight"] = true;
|
||||||
root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS);
|
root["flMaxBrightness"] = preferences.getUInt("flMaxBrightness", DEFAULT_FL_MAX_BRIGHTNESS);
|
||||||
|
Loading…
Reference in New Issue
Block a user