Compare commits

...
7 Commits
Author SHA1 Message Date
cmallwitzandGitHub 1ec1908948 Merge pull request #161 from Michael-C-Strobel/master
Swap to endpoint for fetching crumb
2026-03-28 16:18:16 +00:00
Michael Strobel 03770b4a2e Swap to endpoint for fetching crumb. 2026-03-07 15:26:53 -08:00
cmallwitz fef8d3e442 Package additional Python modules 2025-05-18 15:27:55 +01:00
cmallwitz af69d7f9fd Bundle Python module "requests" 2025-05-13 18:21:09 +01:00
cmallwitz b5b1ca5d09 README.md update 2025-05-12 22:06:32 +01:00
cmallwitz e50f084886 README.md update 2025-05-12 19:35:48 +01:00
cmallwitz d3e2937286 Workaround for Yahoo HTTPS fingerprinting 2025-05-12 18:11:13 +01:00
10 changed files with 201 additions and 193 deletions
+73 -17
View File
@@ -1,22 +1,75 @@
# Financials-Extension
Version 3.3.0 includes improved cookie handling and somewhat improved logic to deal with network issues.
## Overview
This is a Python based extension for LibreOffice Calc to make market data available in Calc
spreadsheets - currently supporting Yahoo's (FX, crypto, equities, indices, futures, options) and Financial Times'
(FX, equities, indices, futures) websites using old-fashioned web scraping.
Starting with version 3.1.0, we received a contribution to get crypto data directly from Coinbase
## Latest version vs Yahoo HTTPS fingerprinting
### Feedback requested:
Latest version 3.8.0 was created to bypass Yahoo's recently adding crazy HTTPS fingerprinting
to their website. In a step back to before or rather a return to times long gone some Python
modules need to be installed such that LibreOffice can find them - otherwise Yahoo will not work.
Please provide feedback about using the extension [here](https://github.com/cmallwitz/Financials-Extension/issues/10)
Update for version: 3.8.2 - this bundles the Python module 'requests' and dependencies so users only
using 'FT' as source should not require anything else.
### Usage:
Everyone else using 'Yahoo' as source needs to install module 'curl_cffi'.
Under 'Releases' on GitHub [there](https://github.com/cmallwitz/Financials-Extension/releases) is a downloadable **Financials-Extension.oxt** file - load it into Calc
### Ubuntu / Linux Mint / etc.
Install Python curl_cffi module as root (such that LibroOffice can find it)
- Optionally, if you don't have pip3 installed: ```sudo apt install python3-pip```
- Then ```sudo pip3 install curl_cffi --upgrade```
Note: For a normal Python script just installing curl_cffi is enough to bypass Yahoo's HTTPS fingerprinting.
Because LibreOffice on Linux is loading the stock curl library long before executing the extension
code directly, a second step are required.
The second bit requires a download of [curl-impersonate](https://github.com/lwthiker/curl-impersonate/releases) e.g.
(currently) libcurl-impersonate-v0.6.1.x86_64-linux-gnu.tar.gz - unpack it somewhere
Then I used the below (adjust your location of libcurl-impersonate-chrome.so) to run LibreOffice Calc
directly from command line - alternatively you could define/export LD_PRELOAD and CURL_IMPERSONATE
e.g. in /etc/environment or ~/.bashrc - but make sure the variables are really set when you run LibreOffice.
Note: the setting chrome101 is just that - a setting on what browser to "impersonate". You don't
need to use or install Chrome for this.
```
LD_PRELOAD=/tmp/curl-impersonate/libcurl-impersonate-chrome.so CURL_IMPERSONATE=chrome101 /usr/lib/libreoffice/program/soffice.bin --calc
```
In LibreOffice Calc this I can see something like the below in the output
from `=GETREALTIME("SUPPORT")` and the examples.ods file from this repo can load data
for Yahoo again.
```
...
requests=curl_cffi_0.10.0
LD_PRELOAD=/tmp/curl-impersonate/libcurl-impersonate-chrome.so
CURL_IMPERSONATE=chrome101
curl_version="libcurl/8.1.1 BoringSSL zlib/1.2.11 brotli/1.0.9 nghttp2/1.56.0"
```
### Windows
- Download the script https://bootstrap.pypa.io/get-pip.py to your computer
- Start a Command Prompt (CMD) as Administrator on the command prompt run (change path as required)
```"c:\Program Files\LibreOffice\program\python.exe" c:\temp\get-pip.py``` and then
```"c:\Program Files\LibreOffice\program\python.exe" -m pip install curl_cffi --upgrade```
- Add a new user environment variable CURL_IMPERSONATE, setting it to value chrome101, the Linux
LD_PRELOAD is not needed for the LibreOffice 7.1 I tested this with.
## Usage of extension:
Under 'Releases' on GitHub is a [downloadable](https://github.com/cmallwitz/Financials-Extension/releases) **Financials-Extension.oxt** file - load it into Calc
under menu item: Tools, Extension Manager...
Please make sure, not to rename the OXT file when downloading and before installing: LO will mess up the installation otherwise and the extension won't work.
@@ -31,7 +84,8 @@ Getting data should be as simple as having this in a cell:
Codes 21 and 90 stand for "last price" and "close" (see below), respectively.
Only Yahoo has historic data available.
There is a file **examples.ods** there too with usage examples and possible arguments to functions.
There is a file **examples.ods** in the same Release area with usage examples
and possible arguments to functions.
You have to check the respective websites to work out what symbol is the right one for you. Make sure today or the date
requested is a trading day (exchange is not closed). If a website doesn't have
@@ -121,26 +175,28 @@ refresh things.
### Build:
You will need the LibreOffice SDK installed.
I only ever tried building on a Linux box.
On my system (Ubuntu) I installed packages: libreoffice-dev libreoffice-java-common libreoffice-script-provider-python
You need to install LibreOffice SDK packages: libreoffice-dev libreoffice-java-common libreoffice-script-provider-python
Since the Yahoo HTTPS fingerprinting issue, additionally curl_cffi needs to be installed (see beginning of README)
\# depending on your location...
cd ~/tech/IdeaProjects/Financials-Extension/
cd ~/tech/Financials-Extension/
python3 -m unittest discover src
\# Assuming curl-cffi is installed, LD_PRELOAD is not required here
CURL_IMPERSONATE=chrome101 python3 -m unittest discover src
\# This builds file **Financials-Extension.oxt**
./compile.sh
### Tested with:
- Windows 10 / LibreOffice Calc 7.1.2.2 / Python 3.8.8
- Ubuntu 22.04.1 / LibreOffice Calc 7.3.7.2 / Python 3.10.6
- MacOS 10.15.7 / LibreOffice Calc 7.2.0.4 / Python 3.8.10
- Ubuntu 22.04.5 / LibreOffice Calc 7.3.7.2 / Python 3.10.12
(Previous versions)
(Previously)
- Windows 10 / LibreOffice Calc 7.1.2.2 / Python 3.8.8
- MacOS 10.15.7 / LibreOffice Calc 7.2.0.4 / Python 3.8.10
- Debian 10.3 / LibreOffice Calc 6.1.5.2 / Python 3.7.3
- Ubuntu 20.04.5 / LibreOffice Calc 6.4.7.2 / Python 3.8.10
- Ubuntu 18.04.5 / LibreOffice Calc 6 / Python 3.6.9
+29 -4
View File
@@ -59,27 +59,52 @@ cp -f "${PWD}"/src/financials_ft.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_yahoo.py "${PWD}"/build/
cp -f "${PWD}"/src/financials_coinbase.py "${PWD}"/build/
# this copies python modules dateutil, pytz, pyparsing to extension so it doesn't have to be installed by user
# this copies python some modules to extension so they doesn't have to be installed by user
TMPFILE=`mktemp`
# https://pypi.org/project/python-dateutil/
wget "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE dateutil/\* -d "${PWD}"/build/
rm $TMPFILE
wget "https://files.pythonhosted.org/packages/9c/3d/a121f284241f08268b21359bd425f7d4825cffc5ac5cd0e1b3d82ffd2b10/pytz-2024.1-py2.py3-none-any.whl" -O $TMPFILE
# https://pypi.org/project/pytz/
wget "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE pytz/\* -d "${PWD}"/build/
rm $TMPFILE
# https://pypi.org/project/pyparsing/
# lastest version of "single-file" pyparsing 2.x - used by Ubuntu 22.04 as python3-pyparsing
wget "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE pyparsing.py -d "${PWD}"/build/
rm $TMPFILE
# Windows LibreOffice 7.1 Python is missing this...
wget "https://files.pythonhosted.org/packages/d9/5a/e7c31adbe875f2abbb91bd84cf2dc52d792b5a01506781dbcf25c91daf11/six-1.16.0-py2.py3-none-any.whl" -O $TMPFILE
# https://pypi.org/project/six/
wget "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE six.py -d "${PWD}"/build/
rm $TMPFILE
# https://pypi.org/project/requests/
wget "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE requests/\* -d "${PWD}"/build/
rm $TMPFILE
# https://pypi.org/project/urllib3/
# urllib3-2.2.3 is last version supporting Python 3.8 used by LibreOffice 7.1
wget "https://files.pythonhosted.org/packages/ce/d9/5f4c13cecde62396b0d3fe530a50ccea91e7dfc1ccf0e09c228841bb5ba8/urllib3-2.2.3-py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE urllib3/\* -d "${PWD}"/build/
rm $TMPFILE
# https://pypi.org/project/certifi/
wget "https://files.pythonhosted.org/packages/4a/7e/3db2bd1b1f9e95f7cddca6d6e75e2f2bd9f51b1246e546d88addca0106bd/certifi-2025.4.26-py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE certifi/\* -d "${PWD}"/build/
rm $TMPFILE
# https://pypi.org/project/idna/
wget "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl" -O $TMPFILE
unzip $TMPFILE idna/\* -d "${PWD}"/build/
rm $TMPFILE
echo "Package into oxt file..."
pushd "${PWD}"/build/
zip -r "${PWD}"/Financials-Extension.zip ./*
BIN
View File
Binary file not shown.
+58 -139
View File
@@ -8,18 +8,11 @@
# version 3 of the License, or (at your option) any later version.
import codecs
import gzip
import logging
import os
import pathlib
import random
import select
import urllib.request
import urllib.parse
from http import cookiejar
from http.client import HTTPConnection, HTTPSConnection, HTTPException
from importlib import util
from datacode import Datacode
logger = logging.getLogger(__name__)
@@ -28,12 +21,24 @@ logger = logging.getLogger(__name__)
# logger.setLevel(logging.DEBUG)
class RedirectException(HTTPException):
def __init__(self, location):
self.location = location
curl_cffi_present = not util.find_spec("curl_cffi") is None
requests_present = not util.find_spec("requests") is None
if curl_cffi_present:
logger.debug("Importing curl_cffi...")
from curl_cffi import requests, __version__ as requests_version, __name__ as requests_name
elif requests_present:
logger.debug("Importing requests...")
import requests
requests_version = requests.__version__
requests_name = requests.__name__
else:
raise Exception("Neither curl_cffi nor requests found.")
# import requests
class HttpException(HTTPException):
class HttpException(Exception):
def __init__(self, url, response):
self.url = url
self.response = response
@@ -45,150 +50,63 @@ class HttpException(HTTPException):
return f"url='{self.url}' status='{self.response}'"
if self.response.headers:
h = '\n'.join(sorted(self.response.headers.__str__().splitlines(), key=lambda l: l.lower()))
return f"url='{self.url}' status={self.response.status} reason='{self.response.reason}'{h}\n"
return f"url='{self.url}' status={self.response.status_code} reason='{self.response.reason}' headers={h}\n"
else:
return f"url='{self.url}' status={self.response.status} reason='{self.response.reason}'"
return f"url='{self.url}' status={self.response.status_code} reason='{self.response.reason}'"
class BaseClient:
def __init__(self):
self.connections = {}
self.cookies = cookiejar.CookieJar()
self.last_url = None
self.redirect_count = 0 # will be set later
self.redirect_count = 0
self.basedir = os.path.join(str(pathlib.Path.home()), '.financials-extension')
os.makedirs(self.basedir, exist_ok=True)
user_agents = [
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:129.0) Gecko/20100101 Firefox/129.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:130.0) Gecko/20100101 Firefox/130.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:132.0) Gecko/20100101 Firefox/132.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:133.0) Gecko/20100101 Firefox/133.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:134.0) Gecko/20100101 Firefox/134.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:135.0) Gecko/20100101 Firefox/135.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:136.0) Gecko/20100101 Firefox/136.0',
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:137.0) Gecko/20100101 Firefox/137.0',
]
self.default_headers = {
'User-Agent': random.sample(user_agents, 1)[0],
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0'
}
if curl_cffi_present:
self.session = requests.Session()
if logger.isEnabledFor(logging.DEBUG) and self.session.curl:
self.session.curl.debug()
else:
self.session = requests.Session()
self.session.headers.update({'User-Agent': random.sample(user_agents, 1)[0],
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate',
'Accept-Language': 'en-US,en;q=0.5',
'Connection': 'keep-alive',
'Cache-Control': 'max-age=0',
})
self.response = None
self.session.max_redirects = 5
def request(self, method: str, url: str, data=None, headers={}, **kwargs):
_headers = self.default_headers.copy()
if headers:
for key, value in headers.items():
_headers[key] = value
if method == 'POST' and 'Content-Type' not in _headers:
_headers['Content-Type'] = 'application/x-www-form-urlencoded'
connection = None
scheme, _, host, path = url.split('/', 3)
if (scheme, host) in self.connections:
connection = self.connections.get((scheme, host))
if connection and select.select([connection.sock], [], [], 0)[0]:
connection.close()
connection = None
if not connection:
logger.debug('Creating connection --------------------------------------------------')
connection = HTTPConnection(host, **kwargs) if scheme == 'http:' else HTTPSConnection(host, **kwargs)
logger.debug('Creating request -----------------------------------------------------')
logger.debug("%s %s", method, url)
self.last_url = url
# generate and add cookie headers
request = urllib.request.Request(url)
self.cookies.add_cookie_header(request)
if request.get_header('Cookie'):
_headers['Cookie'] = request.get_header('Cookie')
for key, value in _headers.items():
logger.debug('Header: %s=%s', key, value)
# request
connection.request(method, '/' + path, data, _headers)
response = connection.getresponse()
logger.debug('Processing response --------------------------------------------------')
logger.debug('response.status=%s', response.status)
for key, value in response.getheaders():
logger.debug('Header: %s=%s', key, value)
self.cookies.extract_cookies(response, request)
self.connections[(scheme, host)] = connection
return response
def urlopen(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
if cookies:
for c in cookies:
self.cookies.set_cookie(c)
def urlopen(self, url, data=None):
self.last_url = None
self.response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
text = self.response.read()
resp = self.session.request('POST' if data else 'GET', url, data=data)
# Allow redirects - used by Yahoo for some cookie based consent
self.redirect_count = 5
if 400 <= resp.status_code < 500:
if resp.headers.get('X-Cache') == 'Error from cloudfront':
resp = self.session.request('POST' if data else 'GET', url, data=data)
# (for Yahoo) AWS CloudFront occasionally returns an incorrect, cached error responses
# try mitigating by re-requesting straight away
if 400 <= self.response.status < 500:
if self.response.getheader('X-Cache') == 'Error from cloudfront':
self.response = self.request('POST' if data else 'GET', url, data, headers, **kwargs)
text = self.response.read()
if resp.status_code >= 400:
logger.warning("url='%s' status=%s reason='%s' headers=%s", resp.url,
resp.status_code, resp.reason,
'\n'.join(sorted(resp.headers.__str__().splitlines(), key=lambda l: l.lower())))
raise HttpException(url, resp)
while 300 <= self.response.status < 400 and self.redirect_count >= 0:
self.redirect_count = len(resp.history)
self.last_url = resp.url
self.redirect_count -= 1
location = str(self.response.getheader('Location'))
location = location.replace(' ', '%20') # FT bug workaround - this should not be necessary
if location and redirect:
if location.startswith('/'):
scheme, _, host, path = url.split('/', 3)
location = '{}//{}{}'.format(scheme, host, location)
self.response = self.request('GET', location, None, headers, **kwargs)
text = self.response.read()
else:
raise RedirectException(location)
if self.response.status >= 400:
logger.warning("last_url='%s' status=%s reason='%s' headers=%s", self.last_url, self.response.status,
self.response.reason,
'\n'.join(sorted(self.response.headers.__str__().splitlines(), key=lambda l: l.lower())))
raise HttpException(url, self.response)
if self.response.getheader('Content-Encoding') == 'gzip':
text = gzip.decompress(text)
content_type = self.response.headers.get_content_charset()
if content_type is None:
content_type = 'utf-8'
text = codecs.decode(text, encoding=content_type, errors='ignore')
return text
return resp.text
def get_ticker(self):
@@ -397,10 +315,11 @@ class BaseClient:
return None
def version(self):
return requests_name + "_" + requests_version
def curl(self):
return curl_version
def close(self):
for connection in self.connections.values():
try:
connection.close()
except BaseException:
pass
self.connections = {}
self.session.close()
+14 -1
View File
@@ -240,7 +240,8 @@ class FinancialsImpl(unohelper.Base, Financials):
if e.tag.endswith('version'):
version = e.attrib['value']
s = 'ctx={}\nid(self)={}\nversion={}\nfile={}\ncwd={}\nhome={}\nuname={}\npid={}\nsys.executable={}\nsys.version={}\nsys.path={}\nlocale={}\ndefaultlocale={}\ndateutil={}\npytz={}\npyparsing={}\nsix={}'.format(
s = ('ctx={}\nid(self)={}\nversion={}\nfile={}\ncwd={}\nhome={}\nuname={}\npid={}\nsys.executable={}\nsys.version={}\nsys.path={}\n' +
'locale={}\ndefaultlocale={}\ndateutil={}\npytz={}\npyparsing={}\nsix={}\nrequests={}').format(
self.ctx,
id(self),
version,
@@ -258,8 +259,20 @@ class FinancialsImpl(unohelper.Base, Financials):
pytz.__version__,
pyparsing.__version__,
six.__version__,
self.ft.version()
)
ld_preload = os.environ.get('LD_PRELOAD')
if ld_preload:
s += f"\nLD_PRELOAD={ld_preload}"
curl_impersonate = os.environ.get('CURL_IMPERSONATE')
if curl_impersonate:
s += f"\nCURL_IMPERSONATE={curl_impersonate}"
if 'curl_cffi' in self.ft.version():
s += f"\ncurl_version=\"{self.ft.session.curl.version().decode()}\""
if datacode:
s = '{}\ntype(datacode)={}\nstr(datacode)={}'.format(
s,
+1 -1
View File
@@ -59,7 +59,7 @@ class Coinbase(BaseClient):
url = 'https://api.exchange.coinbase.com/products/{}/stats'.format(ticker)
try:
text = self.urlopen(url, redirect=True, data=None, headers=None)
text = self.urlopen(url)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
del self.realtime[ticker]
+1 -1
View File
@@ -76,7 +76,7 @@ class FT(BaseClient):
url = f'https://markets.ft.com/data/{asset_class}/tearsheet/summary?s={urllib.parse.quote_plus(ticker)}'
try:
text = self.urlopen(url, redirect=True, data=None, headers=None)
text = self.urlopen(url)
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s last_url=%s redirect_count=%s", ticker, datacode, self.last_url, self.redirect_count)
del self.realtime[ticker]
+23 -28
View File
@@ -119,7 +119,7 @@ class Yahoo(BaseClient):
def handleCookiesAndConsent(self, url, ticker, datacode, html_file):
try:
text = self.urlopen(url, redirect=True)
text = self.urlopen(url)
except BaseException as e:
logger.exception("BaseException (1) ticker=%s datacode=%s last_url=%s redirect_count=%s %s",
ticker, datacode, self.last_url, self.redirect_count, e)
@@ -155,7 +155,7 @@ class Yahoo(BaseClient):
data[d.attrib['name']] = d.attrib['value']
try:
text = self.urlopen(self.last_url, redirect=True, data=urllib.parse.urlencode(data))
text = self.urlopen(self.last_url, data=data)
except BaseException as e:
logger.exception("BaseException (4) ticker=%s datacode=%s last_url=%s redirect_count=%s %s",
ticker, datacode, self.last_url, self.redirect_count, e)
@@ -168,6 +168,21 @@ class Yahoo(BaseClient):
return text
def fetch_crumb(self):
"""Refreshes the crumb using the current session."""
try:
# Grab cookies
self.session.get("https://finance.yahoo.com/quote/SPY", timeout=10)
# Request crumb endpoint
response = self.session.get("https://query1.finance.yahoo.com/v1/test/getcrumb", timeout=10)
if response.status_code == 200:
return response.text.strip()
except Exception as e:
logger.error(f"Failed to fetch crumb: {e}")
return None
def getRealtime(self, ticker, datacode):
"""
@@ -195,33 +210,13 @@ class Yahoo(BaseClient):
tick = self.realtime[ticker]
if not self.crumb:
url = 'https://finance.yahoo.com/quote/{}?p={}'.format(ticker, ticker)
text = self.handleCookiesAndConsent(url, ticker, datacode, f'yahoo-{ticker}.html')
if text is None:
new_crumb = self.fetch_crumb()
if new_crumb:
self.crumb = new_crumb
logger.debug(f"Crumb successfully set: {self.crumb}")
else:
del self.realtime[ticker]
return 'Yahoo.getRealtime({}, {}) - handleCookiesAndConsent'.format(ticker, datacode)
# crumbs like 'TKkC\u002FZBwoUA' may contain unicode _text_ (not encoded code points)
try:
r = r'\bcrumb=([^"]{11,})"'
pattern = re.compile(r)
match = pattern.search(text)
if match:
self.crumb = urllib.parse.unquote(match.group(1).encode('unicode-escape').decode('ascii'))
logger.debug(f"crumb='{match.group(1)}' self.crumb='{self.crumb}'")
else:
r = r'"crumb"\s*:\s*"([^"]{11,})"'
pattern = re.compile(r)
match = pattern.search(text)
if match:
self.crumb = urllib.parse.unquote(match.group(1).encode('unicode-escape').decode('ascii'))
logger.debug(f"crumb='{match.group(1)}' self.crumb='{self.crumb}'")
except BaseException as e:
logger.exception("BaseException ticker=%s datacode=%s", ticker, datacode)
del self.realtime[ticker]
return 'Yahoo.getRealtime({}, {}) - crumb: {}'.format(ticker, datacode, e)
return f'Yahoo.getRealtime({ticker}, {datacode}) - failed to fetch crumb'
if not self.crumb:
return 'Yahoo.getRealtime({}, {}) - crumb missing'.format(ticker, datacode)
+1 -1
View File
@@ -14,7 +14,7 @@ import os
cur_dir = os.getcwd()
addin_id = "com.financials.getinfo"
addin_version = "3.7.2"
addin_version = "3.8.2"
addin_displayname = "Financial Market Extension"
addin_publisher_link = "https://github.com/cmallwitz/Financials-Extension"
addin_publisher_name = "The Publisher"
+1 -1
View File
@@ -24,7 +24,7 @@ import testutils
financials = financials.createInstance(None)
def urlopen_fail(self, url, redirect=True, data=None, headers={}, cookies=[], **kwargs):
def urlopen_fail(self, url, data=None):
raise baseclient.HttpException(url, 'ERROR: simulated urlopen() failed')