From 9d0d4271bd638f9748b6743c4aa9e5ed5128e254 Mon Sep 17 00:00:00 2001 From: Rufus King Date: Mon, 27 Jul 2026 20:31:33 -0400 Subject: [PATCH] Added LibreOffice Macro --- Stocks/WebService/libreoffice_macro.txt | 235 ++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 Stocks/WebService/libreoffice_macro.txt diff --git a/Stocks/WebService/libreoffice_macro.txt b/Stocks/WebService/libreoffice_macro.txt new file mode 100644 index 0000000..afc2f21 --- /dev/null +++ b/Stocks/WebService/libreoffice_macro.txt @@ -0,0 +1,235 @@ +' ========================================================================= +' StockFunctions.bas +' Collabora / LibreOffice Basic port of the ONLYOFFICE custom functions. +' +' UPDATED 2026-07-23: STOCKPRICE / STOCKTIME (Finnhub) removed. +' Finnhub was dropped because it has no historical price coverage, which is +' why stockproxy (NAS08) was built in the first place. FUNDPRICE already +' works for any ticker stockproxy knows about, fund or stock, so it now +' doubles as the equity price function. FUNDTIME replaces STOCKTIME and +' returns stockproxy's "resolved_date" field. +' +' NOTE: resolved_date is a DATE (YYYY-MM-DD), not a time-of-day timestamp. +' Finnhub's old STOCKTIME gave true time-of-day resolution; this does not. +' If intraday precision is ever needed, stockproxy's /current endpoint +' would need a real timestamp field added on the backend side. +' +' HOW TO INSTALL IN COLLABORA: +' 1. Open the sheet in Collabora (collabora.kingdezigns.com). +' 2. Tools > Macros > Edit Macros (opens the Basic IDE). +' 3. In the tree, find "" (NOT "My Macros" - +' you want this stored IN the document so it travels with the file). +' 4. Right-click "Standard" under your document > Insert > Module. +' 5. Paste this entire file's contents into the new module. +' (Or, if StockFunctions already exists, replace its contents.) +' 6. Fill in PROXY_SECRET() below with the real value if not already set. +' 7. Save the document (Ctrl+S). Macro security must allow this +' document's macros to run - if prompted, allow them. +' +' USAGE IN CELLS: +' =FUNDPRICE(D4) +' =FUNDPRICE_HIST(D4, TEXT($B$3,"yyyy-mm-dd")) +' =FUNDPRICE(A5) <- replaces old =STOCKPRICE(A5) +' =FUNDTIME(A5) <- replaces old =STOCKTIME(A5) +' ========================================================================= + +Option Explicit + +' ---- Config ------------------------------------------------------------- + +Function PROXY_BASE() As String + PROXY_BASE = "https://stocks.kingdezigns.com" +End Function + +Function PROXY_SECRET() As String + PROXY_SECRET = "90c2528e9b5221c110f7c2c9cd6c65dc" ' <-- from Vaultwarden +End Function + +' ---- Public spreadsheet functions --------------------------------------- + +Function FUNDPRICE(ticker As String) As Variant + Dim sUrl As String, sJson As String, vPrice As Variant + sUrl = PROXY_BASE() & "/current?ticker=" & EncodeUrl(ticker) & "&key=" & PROXY_SECRET() + sJson = HttpGetText(sUrl) + If sJson = "" Then + FUNDPRICE = "#ERROR" + Exit Function + End If + vPrice = JsonNumber(sJson, "price") + If IsNull(vPrice) Then + FUNDPRICE = "#N/A" + Else + FUNDPRICE = vPrice + End If +End Function + +Function FUNDPRICE_HIST(ticker As String, dateStr As String) As Variant + Dim sUrl As String, sJson As String, vPrice As Variant + sUrl = PROXY_BASE() & "/historical?ticker=" & EncodeUrl(ticker) & _ + "&date=" & EncodeUrl(dateStr) & "&key=" & PROXY_SECRET() + sJson = HttpGetText(sUrl) + If sJson = "" Then + FUNDPRICE_HIST = "#ERROR" + Exit Function + End If + vPrice = JsonNumber(sJson, "price") + If IsNull(vPrice) Then + FUNDPRICE_HIST = "#N/A" + Else + FUNDPRICE_HIST = vPrice + End If +End Function + +' Replaces old STOCKTIME(ticker). Returns stockproxy's resolved_date +' (YYYY-MM-DD) — a date, not a time-of-day timestamp. See note at top. +Function FUNDTIME(ticker As String) As Variant + Dim sUrl As String, sJson As String, vDate As Variant + sUrl = PROXY_BASE() & "/current?ticker=" & EncodeUrl(ticker) & "&key=" & PROXY_SECRET() + sJson = HttpGetText(sUrl) + If sJson = "" Then + FUNDTIME = "#ERROR" + Exit Function + End If + vDate = JsonString(sJson, "resolved_date") + If IsNull(vDate) Then + FUNDTIME = "#N/A" + Else + FUNDTIME = vDate + End If +End Function + +' ---- Internal helpers ---------------------------------------------------- + +' Synchronous HTTP GET, returns response body as text, "" on failure. +Function HttpGetText(sUrl As String) As String + Dim oSFA As Object, oStream As Object, oTextStream As Object + Dim sResult As String + + On Error GoTo ErrHandler + oSFA = createUnoService("com.sun.star.ucb.SimpleFileAccess") + oStream = oSFA.openFileRead(sUrl) + oTextStream = createUnoService("com.sun.star.io.TextInputStream") + oTextStream.setInputStream(oStream) + oTextStream.setEncoding("UTF-8") + + sResult = "" + Do While Not oTextStream.isEOF() + sResult = sResult & oTextStream.readLine() & Chr(10) + Loop + oTextStream.closeInput() + HttpGetText = sResult + Exit Function + +ErrHandler: + HttpGetText = "" +End Function + +' Pulls a numeric value out of a flat JSON string for a given key. +' e.g. JsonNumber("{""price"":18.37}", "price") -> 18.37 +' Returns Null if the key isn't found or has no numeric value. +Function JsonNumber(sJson As String, sKey As String) As Variant + Dim iPos As Integer, iStart As Integer, iEnd As Integer + Dim sNum As String, cChar As String + + iPos = InStr(sJson, Chr(34) & sKey & Chr(34)) + If iPos = 0 Then + JsonNumber = Null + Exit Function + End If + + iPos = InStr(iPos, sJson, ":") + If iPos = 0 Then + JsonNumber = Null + Exit Function + End If + + iStart = iPos + 1 + Do While iStart <= Len(sJson) And Mid(sJson, iStart, 1) = " " + iStart = iStart + 1 + Loop + + iEnd = iStart + Do While iEnd <= Len(sJson) + cChar = Mid(sJson, iEnd, 1) + If (cChar >= "0" And cChar <= "9") Or cChar = "." Or cChar = "-" Then + iEnd = iEnd + 1 + Else + Exit Do + End If + Loop + + sNum = Mid(sJson, iStart, iEnd - iStart) + If Len(sNum) = 0 Then + JsonNumber = Null + Else + JsonNumber = CDbl(sNum) + End If +End Function + +' Pulls a quoted STRING value out of a flat JSON string for a given key. +' e.g. JsonString("{""resolved_date"":""2026-07-23""}", "resolved_date") +' -> "2026-07-23" +' Returns Null if the key isn't found or has no quoted value. +Function JsonString(sJson As String, sKey As String) As Variant + Dim iPos As Integer, iColon As Integer, iQuoteStart As Integer, iQuoteEnd As Integer + + iPos = InStr(sJson, Chr(34) & sKey & Chr(34)) + If iPos = 0 Then + JsonString = Null + Exit Function + End If + + iColon = InStr(iPos, sJson, ":") + If iColon = 0 Then + JsonString = Null + Exit Function + End If + + iQuoteStart = InStr(iColon, sJson, Chr(34)) + If iQuoteStart = 0 Then + JsonString = Null + Exit Function + End If + + iQuoteEnd = InStr(iQuoteStart + 1, sJson, Chr(34)) + If iQuoteEnd = 0 Then + JsonString = Null + Exit Function + End If + + JsonString = Mid(sJson, iQuoteStart + 1, iQuoteEnd - iQuoteStart - 1) +End Function + +' Minimal percent-encoder - sufficient for tickers and yyyy-mm-dd strings. +Function EncodeUrl(s As String) As String + Dim i As Integer, c As String, sOut As String + sOut = "" + For i = 1 To Len(s) + c = Mid(s, i, 1) + If (c >= "A" And c <= "Z") Or (c >= "a" And c <= "z") Or _ + (c >= "0" And c <= "9") Or c = "-" Or c = "_" Or c = "." Or c = "~" Then + sOut = sOut & c + Else + sOut = sOut & "%" & Right("0" & Hex(Asc(c)), 2) + End If + Next i + EncodeUrl = sOut +End Function + +Sub RefreshPriceSnapshot + Dim oDoc As Object, oSheet As Object + Dim oSrcRange As Object, oDestRange As Object + Dim oCell As Object + Dim i As Integer + + oDoc = ThisComponent + oSheet = oDoc.Sheets.getByIndex(0) ' adjust index/name if needed + + ' Copy P3:Q53 -> R3:S53 as values only + For i = 2 To 4 ' rows 3 to 53 (0-indexed: row 3 = index 2) + oSheet.getCellByPosition(17, i).setValue(oSheet.getCellByPosition(15, i).getValue()) ' P->R (col P=15, R=17) + oSheet.getCellByPosition(18, i).setValue(oSheet.getCellByPosition(16, i).getValue()) ' Q->S (col Q=16, S=18) + Next i + + MsgBox "Price snapshot refreshed." +End Sub