commit 7767979538be49ca378feb6fc346413ab62055b5 Author: liudecai Date: Sun Jun 28 17:36:44 2026 +0000 Full blog engine source: build_blog.py, content, deploy scripts diff --git a/README.md b/README.md new file mode 100644 index 0000000..d70cc29 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# GFIL BLOG — Trading Insights & Free Tools + +The content engine behind [blog.quant-view.xyz](https://blog.quant-view.xyz) — a multi-language trading education site with 80+ free calculators, professional market analysis, and SEO-optimized articles. + +## What's here + +- **17 in-depth articles** (EN/ZH/ES/AR) on forex, gold, risk management, and trading technology +- **80+ free trading calculators** — position size, pip value, Fibonacci, ATR, Kelly, pivot points, and more +- **Full SEO/GEO stack** — Schema JSON-LD, hreflang, sitemap (339 URLs), robots.txt, llms.txt +- **Automated deployment pipeline** — build, upload, and server-side promotion scripts + +## How it works + +``` +blog_content/ → build_blog.py → output/ → RackNerd /var/www/blog/ +tools/ → → (static HTML served by Nginx) +deploy_scripts/ → daily cron jobs on server +``` + +## Quick start + +```bash +# Build locally (no deploy) +python build_blog.py --no-upload + +# Build and deploy (requires .env with SSH credentials) +python build_blog.py + +# Preview locally +python build_blog.py --no-upload --serve +``` + +## Repository structure + +| Directory | Purpose | +|-----------|---------| +| `blog_content/` | Article HTML source (17 articles × 4 languages) | +| `tools/` | 145 calculator pages + GEO entity pages + images | +| `deploy_scripts/` | Automation: daily promotion, SEO monitoring, IndexNow | +| `chrome_extension/` | Chrome extension source | +| `colab_notebooks/` | Google Colab trading notebooks | +| `config.py` | Configuration loader (reads from `.env`) | + +## Languages + +English · 中文 · Español · العربية + +## Links + +- Blog: [blog.quant-view.xyz](https://blog.quant-view.xyz) +- Terminal: [gfil-intel.xyz](https://gfil-intel.xyz) +- Community: [t.me/GFIL_Trading](https://t.me/GFIL_Trading) · [Discord](https://discord.gg/GMmMCD4MCr) diff --git a/add_robots_location.py b/add_robots_location.py new file mode 100644 index 0000000..d1830d5 --- /dev/null +++ b/add_robots_location.py @@ -0,0 +1,70 @@ +"""Safely add robots.txt location block to gfil-lab.com Nginx config""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Use python on remote to do precise insertion instead of sed +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Backup first +cp /etc/nginx/sites-enabled/gfil /etc/nginx/sites-enabled/gfil.bak.before-robots + +# Use python to insert robots.txt block precisely before "location / {" +python3 -c " +import re +with open(\"/etc/nginx/sites-enabled/gfil\", \"r\") as f: + content = f.read() + +robots_block = \"\"\" location = /robots.txt { + alias /var/www/gfil-lab/robots.txt; + default_type text/plain; + } + +\"\"\" + +# Insert before the first \"location / {\" +if \"robots.txt\" not in content: + content = content.replace(\" location / {\", robots_block + \" location / {\") + with open(\"/etc/nginx/sites-enabled/gfil\", \"w\") as f: + f.write(content) + print(\"INSERTED robots.txt block\") +else: + print(\"robots.txt block already exists, skipping\") +" + +# Verify +echo "=== Updated config ===" +cat /etc/nginx/sites-enabled/gfil + +# Test +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "SUCCESS: Nginx reloaded" +else + echo "FAILED: rolling back" + cp /etc/nginx/sites-enabled/gfil.bak.before-robots /etc/nginx/sites-enabled/gfil + nginx -t 2>&1 + systemctl reload nginx + echo "Rolled back to original" +fi + +# Verify site still works +curl -s -o /dev/null -w "Site: HTTP %{{http_code}}" http://localhost/ 2>/dev/null + +# Verify robots.txt +curl -s http://localhost/robots.txt 2>/dev/null | head -5 +'""" +stdin, stdout, stderr = jd.exec_command(cmd, timeout=30) +print(stdout.read().decode()) +jd.close() diff --git a/add_robots_v2.py b/add_robots_v2.py new file mode 100644 index 0000000..0b52c97 --- /dev/null +++ b/add_robots_v2.py @@ -0,0 +1,118 @@ +"""Safely add robots.txt location block to gfil-lab.com Nginx config""" +import paramiko +import os + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +# Write the Python fix script to a temp file +FIX_SCRIPT = r''' +import shutil +src = "/etc/nginx/sites-enabled/gfil" +bak = "/etc/nginx/sites-enabled/gfil.bak.before-robots" +shutil.copy2(src, bak) + +with open(src, "r") as f: + content = f.read() + +robots_block = " location = /robots.txt {\n alias /var/www/gfil-lab/robots.txt;\n default_type text/plain;\n }\n\n" + +if "robots.txt" not in content: + content = content.replace(" location / {", robots_block + " location / {") + with open(src, "w") as f: + f.write(content) + print("INSERTED") +else: + print("ALREADY_EXISTS") +''' + +# Write script locally +script_path = os.path.join(os.environ.get("TEMP", "/tmp"), "fix_nginx.py") +with open(script_path, "w") as f: + f.write(FIX_SCRIPT) + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Upload fix script to JD Cloud +sftp = jd.open_sftp() +sftp.put(script_path, "/tmp/fix_nginx.py") +sftp.close() + +# SCP to gfil-lab server, run, verify +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Copy fix script from JD Cloud +echo "Step 1: Upload fix script..." +cat /dev/null # placeholder + +# Actually the script is on JD Cloud, need to scp it first +'""" + +# Two-step: first scp script to target, then run it +scp_cmd = f"sshpass -p '{LAB_PASS}' scp -o StrictHostKeyChecking=no /tmp/fix_nginx.py {LAB_USER}@{LAB_HOST}:/tmp/fix_nginx.py" +stdin, stdout, stderr = jd.exec_command(scp_cmd, timeout=15) +stdout.channel.recv_exit_status() +print(f"SCP: done") + +# Now run it on target +run_cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +echo "=== Running fix script ===" +python3 /tmp/fix_nginx.py + +echo "=== Updated config ===" +cat /etc/nginx/sites-enabled/gfil + +echo "=== Nginx test ===" +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "SUCCESS: Nginx reloaded" +else + echo "FAILED: rolling back" + cp /etc/nginx/sites-enabled/gfil.bak.before-robots /etc/nginx/sites-enabled/gfil + nginx -t 2>&1 && systemctl reload nginx + echo "Rolled back" +fi + +echo "=== Site check ===" +curl -s -o /dev/null -w "Site: HTTP %{http_code}" http://localhost/ 2>/dev/null +echo "" + +echo "=== robots.txt check ===" +curl -s http://localhost/robots.txt 2>/dev/null | head -8 + +rm -f /tmp/fix_nginx.py +'""" +stdin, stdout, stderr = jd.exec_command(run_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() + +# External verify +print("\n=== External verification ===") +import urllib.request +for domain in ['gfil-lab.com', 'gfil-intel.xyz']: + try: + req = urllib.request.Request(f'https://{domain}/robots.txt', + headers={'User-Agent': 'Mozilla/5.0'}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + has_gptbot = 'GPTBot' in content + print(f" {domain}: {r.status} | GPTBot={has_gptbot} | {len(content)} chars") + except Exception as e: + print(f" {domain}: {e}") + +# Also verify main site still works +try: + req = urllib.request.Request('https://gfil-lab.com/', + headers={'User-Agent': 'Mozilla/5.0'}) + r = urllib.request.urlopen(req, timeout=10) + print(f" gfil-lab.com homepage: {r.status} OK") +except Exception as e: + print(f" gfil-lab.com homepage: ERROR {e}") diff --git a/audit.py b/audit.py new file mode 100644 index 0000000..ac17fe5 --- /dev/null +++ b/audit.py @@ -0,0 +1,245 @@ +import urllib.request +import urllib.error +import re +import ssl +import time +import xml.etree.ElementTree as ET +from html.parser import HTMLParser + +ctx = ssl.create_default_context() +ctx.check_hostname = False +ctx.verify_mode = ssl.CERT_NONE + +BAD_GITHUB_PATTERN = "github.com/liudapao880807-arch" + +results = [] + +def log(msg): + print(msg) + results.append(msg) + +class LinkExtractor(HTMLParser): + def __init__(self): + super().__init__() + self.hrefs = [] + def handle_starttag(self, tag, attrs): + if tag == "a": + for k, v in attrs: + if k == "href" and v: + self.hrefs.append(v) + +def fetch(url, timeout=30): + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)"}) + try: + resp = urllib.request.urlopen(req, timeout=timeout, context=ctx) + code = resp.getcode() + body = resp.read().decode("utf-8", errors="replace") + return code, body + except urllib.error.HTTPError as e: + return e.code, e.read().decode("utf-8", errors="replace") if e.fp else "" + except Exception as e: + return -1, str(e) + +def check_url_status(url, timeout=20): + req = urllib.request.Request(url, method="HEAD", headers={"User-Agent": "Mozilla/5.0"}) + try: + resp = urllib.request.urlopen(req, timeout=timeout, context=ctx) + return resp.getcode() + except urllib.error.HTTPError as e: + return e.code + except Exception: + return -1 + +def extract_links(html): + parser = LinkExtractor() + try: + parser.feed(html) + except: + pass + return parser.hrefs + +def resolve_link(base_url, href): + if href.startswith("http://") or href.startswith("https://"): + return href + if href.startswith("//"): + return "https:" + href + if href.startswith("/"): + from urllib.parse import urlparse + p = urlparse(base_url) + return f"{p.scheme}://{p.netloc}{href}" + return href + +def check_page(url, label): + log(f"\n{'='*80}") + log(f"[{label}] Checking: {url}") + code, body = fetch(url) + log(f" Status: {code}") + log(f" Size: {len(body)} chars") + + if code != 200: + log(f" *** ERROR: Non-200 status code!") + return [] + + content_stripped = re.sub(r'<[^>]+>', '', body).strip() + word_count = len(content_stripped.split()) + log(f" Word count (approx): {word_count}") + if word_count < 50: + log(f" *** WARNING: Very low content — possibly blank or broken page!") + + bad_links = re.findall(BAD_GITHUB_PATTERN, body) + if bad_links: + log(f" *** BAD LINKS FOUND: github.com/liudapao880807-arch appears {len(bad_links)} time(s)!") + for m in re.finditer(r'href="[^"]*liudapao880807-arch[^"]*"', body): + log(f" -> {m.group()}") + else: + log(f" No github.com/liudapao880807-arch links (good)") + + hrefs = extract_links(body) + log(f" Total links found: {len(hrefs)}") + + internal = [] + external = [] + for h in hrefs: + resolved = resolve_link(url, h) + if "blog.quant-view.xyz" in resolved: + internal.append(resolved) + elif resolved.startswith("http"): + external.append(resolved) + + log(f" Internal links: {len(internal)}") + log(f" External links: {len(external)}") + + all_unique = list(set(internal + external)) + broken = [] + ok = [] + errors_detail = [] + + for link in all_unique: + if link.endswith((".css", ".js", ".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".woff", ".woff2", ".ttf", ".eot")): + continue + if "mailto:" in link or "javascript:" in link: + continue + time.sleep(0.3) + status = check_url_status(link) + if status == -1: + broken.append((link, "CONNECTION_ERROR")) + errors_detail.append(f" BROKEN (connection error): {link}") + elif status >= 400: + broken.append((link, status)) + errors_detail.append(f" BROKEN ({status}): {link}") + else: + ok.append((link, status)) + + log(f" Checked {len(all_unique)} unique non-asset links") + log(f" OK: {len(ok)}") + log(f" Broken/Errors: {len(broken)}") + for d in errors_detail: + log(d) + + return broken + +def check_sitemap(url, label): + log(f"\n{'='*80}") + log(f"[{label}] Checking sitemap: {url}") + code, body = fetch(url) + log(f" Status: {code}") + if code != 200: + log(f" *** ERROR: Sitemap not accessible!") + return + + urls_in_sitemap = [] + try: + root = ET.fromstring(body) + ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"} + for loc in root.findall(".//sm:loc", ns): + urls_in_sitemap.append(loc.text.strip()) + for loc in root.findall(".//sm:url/sm:loc", ns): + if loc.text.strip() not in urls_in_sitemap: + urls_in_sitemap.append(loc.text.strip()) + except ET.ParseError: + urls_found = re.findall(r'\s*(https?://[^<]+)\s*', body) + urls_in_sitemap = urls_found + + log(f" URLs in sitemap: {len(urls_in_sitemap)}") + + if len(urls_in_sitemap) == 0: + log(f" *** WARNING: No URLs found in sitemap!") + else: + sample = urls_in_sitemap[:5] + for u in sample: + log(f" Sample: {u}") + + broken_count = 0 + for u in urls_in_sitemap[:20]: + time.sleep(0.2) + status = check_url_status(u) + if status >= 400 or status == -1: + log(f" BROKEN in sitemap ({status}): {u}") + broken_count += 1 + if broken_count == 0: + log(f" First 20 sitemap URLs all OK") + else: + log(f" {broken_count} broken URLs in first 20 sitemap entries") + +article_urls = [ + ("Article", "https://blog.quant-view.xyz/position-size-calculator-guide.html"), + ("Article", "https://blog.quant-view.xyz/gold-trading-2026-guide.html"), + ("Article", "https://blog.quant-view.xyz/ssh-tunnel-deployment-china.html"), + ("Article", "https://blog.quant-view.xyz/github-seo-trading-tools.html"), + ("Article", "https://blog.quant-view.xyz/gold-pip-value-calculator-wrong.html"), + ("Article", "https://blog.quant-view.xyz/bloomberg-alternative.html"), + ("Article", "https://blog.quant-view.xyz/why-retail-traders-lose-money.html"), + ("Article", "https://blog.quant-view.xyz/order-flow-trading.html"), +] + +tool_urls = [ + ("Tool", "https://blog.quant-view.xyz/tools/position-size-formula.html"), + ("Tool", "https://blog.quant-view.xyz/tools/xauusd-trading-guide.html"), + ("Tool", "https://blog.quant-view.xyz/tools/btc-position-size-calculator.html"), + ("Tool", "https://blog.quant-view.xyz/tools/risk-management-guide.html"), + ("Tool", "https://blog.quant-view.xyz/tools/tradingview-vs-mt5.html"), + ("Tool", "https://blog.quant-view.xyz/tools/kelly-criterion-formula.html"), + ("Tool", "https://blog.quant-view.xyz/tools/forex-position-size-calculator.html"), + ("Tool", "https://blog.quant-view.xyz/tools/pip-calculator-eurgbp.html"), + ("Tool", "https://blog.quant-view.xyz/tools/sp500-position-size-calculator.html"), + ("Tool", "https://blog.quant-view.xyz/tools/position-size-calculator-100000-dollar-account.html"), +] + +sitemap_urls = [ + ("Sitemap", "https://blog.quant-view.xyz/sitemap.xml"), + ("Sitemap", "https://blog.quant-view.xyz/sitemap-tools.xml"), + ("Sitemap", "https://blog.quant-view.xyz/sitemap-posts.xml"), +] + +log("=" * 80) +log("BLOG AUDIT: https://blog.quant-view.xyz") +log(f"Date: 2026-06-28") +log("=" * 80) + +all_broken = [] +all_bad_github = [] + +for label, url in article_urls: + broken = check_page(url, label) + all_broken.extend(broken) + +for label, url in tool_urls: + broken = check_page(url, label) + all_broken.extend(broken) + +for label, url in sitemap_urls: + check_sitemap(url, label) + +log("\n" + "=" * 80) +log("SUMMARY") +log("=" * 80) + +log(f"\nTotal broken links found: {len(all_broken)}") +for link, status in all_broken: + log(f" [{status}] {link}") + +output = "\n".join(results) +with open(r"D:\GFIL_BLOG\audit_results.txt", "w", encoding="utf-8") as f: + f.write(output) + +log(f"\nResults written to D:\\GFIL_BLOG\\audit_results.txt") diff --git a/audit_results.txt b/audit_results.txt new file mode 100644 index 0000000..dcd53e5 --- /dev/null +++ b/audit_results.txt @@ -0,0 +1,317 @@ +================================================================================ +BLOG AUDIT: https://blog.quant-view.xyz +Date: 2026-06-28 +================================================================================ + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/position-size-calculator-guide.html + Status: 200 + Size: 20417 chars + Word count (approx): 1512 + No github.com/liudapao880807-arch links (good) + Total links found: 19 + Internal links: 15 + External links: 4 + Checked 16 unique non-asset links + OK: 15 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Position%20Size%20Calculator%20%E2%80%94%20The%20Complete%20Guide%20to%20Forex%20Risk%20Management%20in%202026&url=https://blog.quant-view.xyz/position-size-calculator-guide.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/gold-trading-2026-guide.html + Status: 200 + Size: 23961 chars + Word count (approx): 1881 + No github.com/liudapao880807-arch links (good) + Total links found: 20 + Internal links: 16 + External links: 4 + Checked 16 unique non-asset links + OK: 15 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Gold%20XAUUSD%20Trading%202026%20%E2%80%94%20Technical%20Tools%2C%20Price%20Drivers%20%26%20Free%20Calculators&url=https://blog.quant-view.xyz/gold-trading-2026-guide.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/ssh-tunnel-deployment-china.html + Status: 200 + Size: 16684 chars + Word count (approx): 923 + No github.com/liudapao880807-arch links (good) + Total links found: 15 + Internal links: 11 + External links: 4 + Checked 12 unique non-asset links + OK: 11 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=SSH%20Tunnel%20Deployment%20for%20Trading%20Platforms%20%E2%80%94%20Lessons%20from%20China&url=https://blog.quant-view.xyz/ssh-tunnel-deployment-china.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/github-seo-trading-tools.html + Status: 200 + Size: 17375 chars + Word count (approx): 1051 + No github.com/liudapao880807-arch links (good) + Total links found: 16 + Internal links: 11 + External links: 5 + Checked 13 unique non-asset links + OK: 12 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=GitHub%20SEO%20for%20Trading%20Tools%20%E2%80%94%203%20Mistakes%20That%20Kill%20Your%20Rankings&url=https://blog.quant-view.xyz/github-seo-trading-tools.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/gold-pip-value-calculator-wrong.html + Status: 200 + Size: 17447 chars + Word count (approx): 870 + No github.com/liudapao880807-arch links (good) + Total links found: 16 + Internal links: 11 + External links: 5 + Checked 14 unique non-asset links + OK: 13 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Why%20Most%20Position%20Size%20Calculators%20Get%20Gold%20Wrong&url=https://blog.quant-view.xyz/gold-pip-value-calculator-wrong.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/bloomberg-alternative.html + Status: 200 + Size: 15966 chars + Word count (approx): 864 + No github.com/liudapao880807-arch links (good) + Total links found: 17 + Internal links: 13 + External links: 4 + Checked 15 unique non-asset links + OK: 14 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Bloomberg%20Terminal%20Alternatives&url=https://blog.quant-view.xyz/bloomberg-alternative.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/why-retail-traders-lose-money.html + Status: 200 + Size: 17425 chars + Word count (approx): 1096 + No github.com/liudapao880807-arch links (good) + Total links found: 17 + Internal links: 13 + External links: 4 + Checked 15 unique non-asset links + OK: 14 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Why%2087%25%20of%20Retail%20Traders%20Lose%20Money&url=https://blog.quant-view.xyz/why-retail-traders-lose-money.html + +================================================================================ +[Article] Checking: https://blog.quant-view.xyz/order-flow-trading.html + Status: 200 + Size: 17228 chars + Word count (approx): 1049 + No github.com/liudapao880807-arch links (good) + Total links found: 16 + Internal links: 12 + External links: 4 + Checked 14 unique non-asset links + OK: 13 + Broken/Errors: 1 + BROKEN (403): https://twitter.com/intent/tweet?text=Order%20Flow%20Trading&url=https://blog.quant-view.xyz/order-flow-trading.html + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/position-size-formula.html + Status: 200 + Size: 6979 chars + Word count (approx): 596 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/xauusd-trading-guide.html + Status: 200 + Size: 5431 chars + Word count (approx): 360 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/btc-position-size-calculator.html + Status: 200 + Size: 5053 chars + Word count (approx): 316 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/risk-management-guide.html + Status: 200 + Size: 6055 chars + Word count (approx): 426 + No github.com/liudapao880807-arch links (good) + Total links found: 7 + Internal links: 6 + External links: 1 + Checked 6 unique non-asset links + OK: 6 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/tradingview-vs-mt5.html + Status: 200 + Size: 6584 chars + Word count (approx): 319 + No github.com/liudapao880807-arch links (good) + Total links found: 5 + Internal links: 3 + External links: 2 + Checked 5 unique non-asset links + OK: 5 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/kelly-criterion-formula.html + Status: 200 + Size: 4811 chars + Word count (approx): 290 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/forex-position-size-calculator.html + Status: 200 + Size: 5548 chars + Word count (approx): 346 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/pip-calculator-eurgbp.html + Status: 200 + Size: 4543 chars + Word count (approx): 259 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/sp500-position-size-calculator.html + Status: 200 + Size: 4784 chars + Word count (approx): 282 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Tool] Checking: https://blog.quant-view.xyz/tools/position-size-calculator-100000-dollar-account.html + Status: 200 + Size: 5214 chars + Word count (approx): 342 + No github.com/liudapao880807-arch links (good) + Total links found: 4 + Internal links: 3 + External links: 1 + Checked 4 unique non-asset links + OK: 4 + Broken/Errors: 0 + +================================================================================ +[Sitemap] Checking sitemap: https://blog.quant-view.xyz/sitemap.xml + Status: 200 + URLs in sitemap: 3 + Sample: https://blog.quant-view.xyz/sitemap-tools.xml + Sample: https://blog.quant-view.xyz/sitemap-posts.xml + Sample: https://blog.quant-view.xyz/sitemap-geo.xml + First 20 sitemap URLs all OK + +================================================================================ +[Sitemap] Checking sitemap: https://blog.quant-view.xyz/sitemap-tools.xml + Status: 200 + URLs in sitemap: 289 + Sample: https://blog.quant-view.xyz/tools/about-gfil.html + Sample: https://blog.quant-view.xyz/tools/about-liudecai.html + Sample: https://blog.quant-view.xyz/tools/ai-prompts.html + Sample: https://blog.quant-view.xyz/tools/ai-trading-guide.html + Sample: https://blog.quant-view.xyz/tools/api-reference.html + First 20 sitemap URLs all OK + +================================================================================ +[Sitemap] Checking sitemap: https://blog.quant-view.xyz/sitemap-posts.xml + Status: 200 + URLs in sitemap: 81 + Sample: https://blog.quant-view.xyz/ + Sample: https://blog.quant-view.xyz/gfil-boss-panel-v70-review.html + Sample: https://blog.quant-view.xyz/zh/gfil-boss-panel-v70-review.html + Sample: https://blog.quant-view.xyz/es/gfil-boss-panel-v70-review.html + Sample: https://blog.quant-view.xyz/ar/gfil-boss-panel-v70-review.html + First 20 sitemap URLs all OK + +================================================================================ +SUMMARY +================================================================================ + +--- PAGE STATUS --- +All 8 article pages: 200 OK, all have real content (870-1881 words) +All 10 tool pages: 200 OK, all have real content (259-596 words) +All 3 sitemaps: 200 OK + +--- BROKEN LINKS --- +Total broken links found: 8 +All 8 are twitter.com/intent/tweet share links returning 403. +These are NOT real content issues — Twitter/X blocks HEAD requests from +non-browser user agents. These share links work fine in an actual browser. + + [403] twitter share link on position-size-calculator-guide.html + [403] twitter share link on gold-trading-2026-guide.html + [403] twitter share link on ssh-tunnel-deployment-china.html + [403] twitter share link on github-seo-trading-tools.html + [403] twitter share link on gold-pip-value-calculator-wrong.html + [403] twitter share link on bloomberg-alternative.html + [403] twitter share link on why-retail-traders-lose-money.html + [403] twitter share link on order-flow-trading.html + +--- BAD GITHUB LINKS --- +github.com/liudapao880807-arch: ZERO occurrences found across all 18 pages. +The old GitHub links have been fully replaced. + +--- SITEMAP COVERAGE --- +sitemap.xml: 3 child sitemaps (tools, posts, geo) +sitemap-tools.xml: 289 tool page URLs +sitemap-posts.xml: 81 post URLs (includes /zh/, /es/, /ar/ localized versions) +First 20 URLs in each sitemap all return 200 OK. + +--- OTHER NOTES --- +- Tool pages are smaller (4.5-7KB) than article pages (16-24KB), which is expected +- No 404 errors on any internal links +- No connection errors on any external links (besides Twitter 403 false positive) +- No blank or empty pages detected diff --git a/build_blog.py b/build_blog.py new file mode 100644 index 0000000..0f93e89 --- /dev/null +++ b/build_blog.py @@ -0,0 +1,581 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +"""GFIL BLOG 构建脚本 — 多语言 SEO + 工具页""" +import os, re, json, io, sys, argparse, random, socket, paramiko, shutil, time +from datetime import datetime +from urllib.parse import quote +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +CONTENT_DIR = os.path.join(BASE_DIR, "blog_content") +OUTPUT_DIR = os.path.join(BASE_DIR, "output") +SITE_URL = "https://blog.quant-view.xyz" +SITE_NAME = "GFIL Trading Insights" +GOOGLE_VERIFY = os.environ.get("GFIL_GOOGLE_VERIFY", "") +GA_ID = os.environ.get("GFIL_GA_ID", "") +SSH_HOST = os.environ.get("GFIL_SSH_HOST", "") +SSH_PORT = int(os.environ.get("GFIL_SSH_PORT", "22")) +SSH_USER = os.environ.get("GFIL_SSH_USER", "root") +SSH_PASS = os.environ.get("GFIL_SSH_PASS", "") +REMOTE_DIR = "/var/www/blog/" +HTTP_PROXY = os.environ.get("GFIL_HTTP_PROXY", "") + +# ===== 多语言配置 ===== +LANGUAGES = [ + {'code': 'en', 'dir': '', 'label': 'English', 'locale': 'en_US', 'flag': 'US', 'dir_attr': 'ltr'}, + {'code': 'zh', 'dir': 'zh', 'label': 'Chinese', 'locale': 'zh_CN', 'flag': 'CN', 'dir_attr': 'ltr'}, + {'code': 'es', 'dir': 'es', 'label': 'Espanol', 'locale': 'es_ES', 'flag': 'ES', 'dir_attr': 'ltr'}, + {'code': 'ar', 'dir': 'ar', 'label': 'Arabic', 'locale': 'ar_SA', 'flag': 'SA', 'dir_attr': 'rtl'}, +] +SAME_AS_JSON = json.dumps(['https://gfil-intel.xyz', 'https://t.me/GFIL_Trading', 'https://discord.gg/GMmMCD4MCr']) +def LANG(key, code): + """Get translated text""" + d = LANG_DICT.get(key, {}) + return d.get(code, d.get("en", key)) + +LANG_DICT = { + "site_name": {"en": "GFIL Trading Insights", "zh": "GFIL 交易洞察", "es": "GFIL Trading Insights", "ar": "GFIL Trading Insights"}, + "nav_sub": {"en": "Institutional-grade market intelligence", "zh": "机构级市场情报", "es": "Inteligencia de mercado institucional", "ar": "ذكاء سوقي على المستوى المؤسسي"}, + "tools_title": {"en": "30+ Free Trading Calculators", "zh": "30+款免费交易计算器", "es": "30+ Calculadoras de Trading Gratis", "ar": "30+ حاسبة تداول مجانية"}, + "tools_desc": {"en": "Position size, pip value, profit factor, Fibonacci, ATR, Kelly, and more", "zh": "仓位计算、点值、盈亏比、斐波那契、ATR、凯利公式等", "es": "Tamanio de posicion, valor pip, factor de beneficio, Fibonacci, ATR, Kelly y mas", "ar": "حجم الصفقة، قيمة النقطة، عامل الربح، فيبوناتشي، ATR، كيلي والمزيد"}, + "tools_try": {"en": "Try All 30+ Tools", "zh": "试用全部30+款工具", "es": "Probar las 30+ Herramientas", "ar": "جرب كل 30+ أداة"}, + "tools_nav": {"en": "Free Trading Tools", "zh": "免费交易工具", "es": "Herramientas Gratis", "ar": "أدوات تداول مجانية"}, + "cta_title": {"en": "Ready for Institutional-Grade Trading?", "zh": "准备好体验机构级交易了吗?", "es": "Listo para Trading de Nivel Institucional?", "ar": "هل أنت مستعد للتداول على المستوى المؤسسي؟"}, + "cta_desc": {"en": "Get real-time market intelligence with sub-50ms WebSocket data.", "zh": "获取50毫秒级WebSocket实时市场数据。", "es": "Obten inteligencia de mercado en tiempo real con datos WebSocket.", "ar": "احصل على ذكاء سوقي فوري مع بيانات WebSocket."}, + "cta_btn": {"en": "Access GFIL Terminal", "zh": "进入GFIL终端", "es": "Acceder al Terminal", "ar": "الدخول إلى منصة GFIL"}, + "cta_tg": {"en": "Telegram", "zh": "电报群", "es": "Telegram", "ar": "تيليجرام"}, + "nav_back": {"en": "Back to GFIL Trading Insights", "zh": "返回", "es": "Volver", "ar": "عودة"}, + "footer_main": {"en": "Main Site", "zh": "主站", "es": "Sitio Principal", "ar": "الموقع الرئيسي"}, + "footer_tools": {"en": "Free Tools", "zh": "免费工具", "es": "Herramientas Gratis", "ar": "أدوات مجانية"}, + "footer_terminal": {"en": "GFIL Terminal", "zh": "GFIL终端", "es": "Terminal GFIL", "ar": "منصة GFIL"}, + "contact": {"en": "Telegram: @GFIL_Trading | Discord", "zh": "微信: LDP161109 | QQ: 1987199079", "es": "Telegram: @GFIL_Trading | Discord", "ar": "Telegram: @GFIL_Trading | Discord"}, + "share": {"en": "Share:", "zh": "分享:", "es": "Compartir:", "ar": "مشاركة:"}, + "related": {"en": "You May Also Like", "zh": "相关推荐", "es": "Tambien Te Puede Gustar", "ar": "قد يعجبك أيضا"}, + "news_title": {"en": "Get Weekly Trading Insights", "zh": "获取每周交易洞察", "es": "Analisis Semanal", "ar": "تحليلات أسبوعية"}, + "news_desc": {"en": "Subscribe for exclusive analysis.", "zh": "订阅获取独家分析。", "es": "Suscribete para analisis exclusivo.", "ar": "اشترك للحصول على تحليلات حصرية."}, + "news_btn": {"en": "Subscribe", "zh": "订阅", "es": "Suscribirse", "ar": "اشتراك"}, + "news_ph": {"en": "Your email address", "zh": "您的邮箱地址", "es": "Tu correo electronico", "ar": "بريدك الإلكتروني"}, +} + +# ===== 文章列表 ===== +ARTICLES = [ + {'id': 'art-1', 'num': '1', 'title': 'GFIL BOSS PANEL v7.0 Review', 'desc': 'In-depth review of GFIL BOSS PANEL v7.0: real-time WebSocket data, 30+ instruments, 20+ professional indicators, AI-powered analysis, and sub-50ms latency. How it compares to TradingView and MetaTrader.', 'tags': ['GFIL', 'Review', 'Trading Terminal'], 'filename': 'gfil-boss-panel-v70-review.html', 'date': '2026-05-25', 'related': [3, 7, 10]}, + {'id': 'art-2', 'num': '2', 'title': 'Gold XAUUSD Trading in 2026', 'desc': 'Complete gold trading strategy using order flow analysis: cumulative delta, volume profile, absorption patterns, and GOFO rates. Why traditional RSI and MACD fail on XAUUSD and what to use instead.', 'tags': ['Gold', 'XAUUSD', 'Trading Strategy'], 'filename': 'gold-xauusd-trading-2026.html', 'date': '2026-05-24', 'related': [4, 6, 9]}, + {'id': 'art-3', 'num': '3', 'title': 'TradingView vs GFIL BOSS', 'desc': 'Side-by-side comparison: TradingView REST polling (500ms-3s delay) vs GFIL WebSocket streaming (<50ms). NFP data: 30 REST updates vs 6,000 WebSocket pushes. What scalpers and day traders need to know.', 'tags': ['TradingView', 'Comparison', 'Speed'], 'filename': 'tradingview-vs-gfil-boss.html', 'date': '2026-05-23', 'related': [1, 7, 10]}, + {'id': 'art-4', 'num': '4', 'title': 'Why 87% of Retail Traders Lose Money', 'desc': 'Data-driven analysis of why 87% of retail forex traders lose money: poor risk management, oversized positions, lack of edge, emotional trading, and data asymmetry vs institutions. How to be in the 13%.', 'tags': ['Trading Psychology', 'Data', 'Analysis'], 'filename': 'why-retail-traders-lose-money.html', 'date': '2026-05-22', 'related': [5, 7, 8]}, + {'id': 'art-5', 'num': '5', 'title': 'Your Trading Activity is Being Tracked', 'desc': 'How brokers, HFT firms, and market makers track your stop losses, entry patterns, and position sizes. Understanding the surveillance ecosystem and how to protect your trading strategy.', 'tags': ['Security', 'Privacy', 'Trading'], 'filename': 'trading-activity-tracked.html', 'date': '2026-05-21', 'related': [4, 7, 10]}, + {'id': 'art-6', 'num': '6', 'title': 'Forex Scalping in 2026', 'desc': 'Modern forex scalping strategies for 2026: London open breakout, Asian range fade, news spike scalping. Why sub-50ms WebSocket data is non-negotiable for consistent scalping profits.', 'tags': ['Forex', 'Scalping', 'Strategy'], 'filename': 'forex-scalping-2026.html', 'date': '2026-05-20', 'related': [2, 4, 7]}, + {'id': 'art-7', 'num': '7', 'title': 'How Institutional Traders See Market Moves 15 Minutes Before Retail', 'desc': 'Institutional order flow tools revealed: cumulative delta divergence, absorption at key levels, POC migration, and dark pool prints. How institutions front-run retail order flow with professional data terminals.', 'tags': ['Institutional', 'Market Intelligence', 'Insider'], 'filename': 'institutional-traders-see-market-moves.html', 'date': '2026-05-19', 'related': [1, 3, 8]}, + {'id': 'art-8', 'num': '8', 'title': 'The Rise of AI-Driven Market Intelligence', 'desc': 'How AI models (DeepSeek, Claude, GPT) are transforming market analysis: multi-timeframe pattern detection, sentiment analysis from news feeds, and real-time anomaly detection across 30+ instruments simultaneously.', 'tags': ['AI', 'Market Analysis', 'Future'], 'filename': 'ai-driven-market-intelligence.html', 'date': '2026-05-18', 'related': [4, 7, 10]}, + {'id': 'art-9', 'num': '9', 'title': 'WTI Crude Oil in 2026', 'desc': 'WTI crude oil trading strategy: EIA inventory momentum, OPEC+ volatility plays, crack spread correlation. Energy sector analysis with order flow tools for oil traders.', 'tags': ['WTI', 'Crude Oil', 'Energy'], 'filename': 'wti-crude-oil-2026.html', 'date': '2026-05-17', 'related': [2, 4, 6]}, + {'id': 'art-10', 'num': '10', 'title': 'GFIL BOSS PANEL FAQ', 'desc': 'Complete FAQ for GFIL BOSS PANEL: supported instruments, WebSocket data sources, available indicators, subscription plans, language support, and how to get started with institutional-grade trading tools.', 'tags': ['GFIL', 'FAQ', 'Getting Started'], 'filename': 'gfil-boss-panel-faq.html', 'date': '2026-05-16', 'related': [1, 3, 7]}, + {'id': 'art-11', 'num': '11', 'title': 'Order Flow Trading', 'desc': 'Complete guide to order flow trading: footprint charts, cumulative delta, volume profile, absorption, iceberg orders, and POC migration. The institutional approach to reading market microstructure.', 'tags': ['Order Flow', 'Institutional', 'Trading Strategy'], 'filename': 'order-flow-trading.html', 'date': '2026-06-02', 'related': [4, 7, 13]}, + {'id': 'art-12', 'num': '12', 'title': 'Bloomberg Terminal Alternatives', 'desc': 'Best free and low-cost Bloomberg Terminal alternatives for retail traders in 2026. Compare features, data sources, latency, and pricing across 7 professional trading platforms.', 'tags': ['Bloomberg', 'Trading Platform', 'Comparison'], 'filename': 'bloomberg-alternative.html', 'date': '2026-06-02', 'related': [1, 3, 10]}, + {'id': 'art-13', 'num': '13', 'title': 'WebSocket vs REST API', 'desc': 'Technical deep-dive: WebSocket vs REST API for real-time trading data. NFP first minute benchmark — REST delivers 30 data points, WebSocket pushes 6,000. Latency analysis with code examples.', 'tags': ['WebSocket', 'Technology', 'Data Latency'], 'filename': 'websocket-vs-rest-api.html', 'date': '2026-06-02', 'related': [3, 6, 7]}, + {'id': 'art-14', 'num': '14', 'title': 'How to Track Trading Signal Performance Like a Hedge Fund', 'desc': 'Professional signal tracking methodology: Sharpe ratio, profit factor, max drawdown, win rate by session, expectancy. Build a hedge-fund-grade performance dashboard with free tools.', 'tags': ['Signal Tracking', 'Performance', 'Risk Management'], 'filename': 'trading-signal-tracking.html', 'date': '2026-06-02', 'related': [1, 5, 10]}, + {'id': 'art-15', 'num': '15', 'title': 'Anonymous Trading Platform', 'desc': 'How to trade anonymously: privacy-first platforms, no-KYC options, VPN/seedbox setup for traders. Protect your edge from broker surveillance and strategy copying.', 'tags': ['Privacy', 'Security', 'Anonymous Trading'], 'filename': 'anonymous-trading-platform.html', 'date': '2026-06-02', 'related': [5, 8, 12]}, + {'id': 'art-16', 'num': '16', 'title': 'Position Size Calculator — The Complete Guide to Forex Risk Management in 2026', 'desc': 'Master position sizing: the mathematical formula, three professional risk models (Fixed Fractional, Kelly Criterion, Fixed Ratio), common mistakes that kill accounts, and a free calculator that handles cross-pair pip values and account currency conversion. CFTC data shows 74% of failing traders risk over 5% per trade.', 'tags': ['Risk Management', 'Position Sizing', 'Forex Education'], 'filename': 'position-size-calculator-guide.html', 'date': '2026-06-27', 'related': [4, 5, 14]}, + {'id': 'art-17', 'num': '17', 'title': 'Gold XAUUSD Trading 2026 — Technical Tools, Price Drivers & Free Calculators', 'desc': 'Professional gold trading framework for 2026: central bank buying de-dollarization, real yield analysis, ATR-based stops vs fixed pip stops, Fibonacci pivot points for gold, institutional COT/ETF flow indicators, and the critical gold position size formula that differs from forex. Complete with institutional desk risk protocol.', 'tags': ['Gold', 'XAUUSD', 'Technical Analysis', 'Risk Management'], 'filename': 'gold-trading-2026-guide.html', 'date': '2026-06-27', 'related': [2, 6, 14]}, + {'id': 'art-18', 'num': '18', 'title': 'SSH Tunnel Deployment for Trading Platforms — Lessons from China', 'desc': 'How to deploy a trading platform from China when ISPs block overseas SSH: the JD Cloud jumphost architecture, tar.gz deployment optimization, and the Cloudflare CDN cache trap that wasted an entire audit cycle. With Python Paramiko code examples.', 'tags': ['Infrastructure', 'Deployment', 'SSH', 'Engineering'], 'filename': 'ssh-tunnel-deployment-china.html', 'date': '2026-06-28', 'related': [13, 3, 10]}, + {'id': 'art-19', 'num': '19', 'title': 'GitHub SEO for Trading Tools — 3 Mistakes That Kill Your Rankings', 'desc': 'Real-world lessons from deploying an open-source trading calculator library: shadow-banned GitHub accounts creating 204 dead links, fake repos in knowledge graphs destroying AI credibility, inconsistent numbers failing E-E-A-T checks, and the three-layer GitHub strategy that actually works.', 'tags': ['SEO', 'GitHub', 'Open Source', 'GEO'], 'filename': 'github-seo-trading-tools.html', 'date': '2026-06-28', 'related': [14, 5, 8]}, + {'id': 'art-20', 'num': '20', 'title': 'Why Most Position Size Calculators Get Gold Wrong', 'desc': 'XAUUSD pip value is $1.00 per lot, not $10.00 like EURUSD. Most forex calculators use the wrong formula for gold, silver, crypto, and indices — producing position sizes 10x too large. Complete breakdown of correct formulas for 6 instrument types with pip value comparison table.', 'tags': ['Gold', 'XAUUSD', 'Position Sizing', 'Risk Management'], 'filename': 'gold-pip-value-calculator-wrong.html', 'date': '2026-06-28', 'related': [2, 17, 16]}, +] + +# ===== 文章标题翻译 ===== +ART_TITLES = { + 'zh': { + '1': 'GFIL BOSS PANEL v7.0 评测 — 新一代交易终端', + '2': '2026年黄金XAUUSD交易策略 — 订单流分析法', + '3': 'TradingView vs GFIL BOSS — 延迟速度全面对比', + '4': '为什么87%的散户交易者亏钱 — 数据揭示真相', + '5': '你的交易活动正在被追踪 — 如何保护你的策略', + '6': '2026外汇剥头皮策略 — 低延迟交易指南', + '7': '机构交易者如何比你早15分钟看到市场动向', + '8': 'AI驱动市场分析的崛起 — 人工智能如何改变交易', + '9': '2026年WTI原油交易策略 — 能源市场深度分析', + '10': 'GFIL BOSS PANEL 常见问题 — 新手入门指南', + '11': '订单流交易 — 机构交易员的核心武器', + '12': '彭博终端替代品 — 免费和低成本的交易平台选择', + '13': 'WebSocket vs REST API — 交易延迟的真相', + '14': '如何像对冲基金一样追踪交易信号绩效', + '15': '匿名交易平台 — 保护你的交易隐私', + }, + 'es': { + '1': 'GFIL BOSS PANEL v7.0 — Revisión del Terminal de Trading', + '2': 'Trading de Oro XAUUSD en 2026 — Estrategia con Order Flow', + '3': 'TradingView vs GFIL BOSS — Comparativa de Velocidad', + '4': 'Por Qué el 87% de los Traders Minoristas Pierde Dinero', + '5': 'Tu Actividad de Trading Está Siendo Rastreada', + '6': 'Scalping en Forex 2026 — Guía de Trading de Baja Latencia', + '7': 'Cómo los Institucionales Ven los Movimientos 15 Minutos Antes', + '8': 'El Auge del Análisis de Mercado con Inteligencia Artificial', + '9': 'Trading de Petróleo WTI en 2026 — Análisis del Mercado Energético', + '10': 'GFIL BOSS PANEL — Preguntas Frecuentes', + '11': 'Order Flow Trading — El Arma Secreta de los Institucionales', + '12': 'Alternativas a Bloomberg Terminal — Gratis y de Bajo Costo', + '13': 'WebSocket vs REST API — La Verdad Sobre la Latencia', + '14': 'Cómo Rastrear el Rendimiento de Señales Como un Hedge Fund', + '15': 'Plataforma de Trading Anónima — Protege tu Privacidad', + '16': 'Calculadora de Tamano de Posicion — Guia Completa de Gestion de Riesgo Forex 2026', + '17': 'Trading de Oro XAUUSD 2026 — Herramientas Tecnicas, Drivers de Precio y Calculadoras Gratis', + }, + 'ar': { + '1': 'مراجعة GFIL BOSS PANEL v7.0 — منصة تداول الجيل الجديد', + '2': 'تداول الذهب XAUUSD في 2026 — استراتيجية تدفق الأوامر', + '3': 'TradingView مقابل GFIL BOSS — مقارنة السرعة والأداء', + '4': 'لماذا يخسر 87% من المتداولين الأفراد أموالهم', + '5': 'نشاط تداولك مراقب — كيف تحمي استراتيجيتك', + '6': 'سكالبينج الفوركس 2026 — دليل التداول منخفض التأخير', + '7': 'كيف يرى المتداولون المؤسسيون تحركات السوق قبلك بـ15 دقيقة', + '8': 'صعود تحليل السوق بالذكاء الاصطناعي', + '9': 'تداول نفط WTI في 2026 — تحليل سوق الطاقة', + '10': 'GFIL BOSS PANEL — الأسئلة الشائعة', + '11': 'تداول تدفق الأوامر — السلاح السري للمؤسسات', + '12': 'بدائل بلومبرج — منصات تداول مجانية ومنخفضة التكلفة', + '13': 'WebSocket مقابل REST API — حقيقة سرعة البيانات', + '14': 'كيف تتابع أداء إشارات التداول مثل صناديق التحوط', + '15': 'منصة تداول مجهولة — حماية خصوصية تداولك', + '16': 'حاسبة حجم الصفقة — الدليل الكامل لإدارة مخاطر الفوركس 2026', + '17': 'تداول الذهب XAUUSD 2026 — الأدوات التقنية ومحركات الأسعار والآلات الحاسبة المجانية', + }, +} + +def clean_blog_content(html): + html = re.sub(r'href="https://telegra\.ph/([^"]+)"', r'href="/\\1"', html) + return html + +def estimate_reading_time(text): + words = len(text.split()) + return max(1, round(words / 200)) + +def _get_lang(lc): + for l in LANGUAGES: + if l["code"] == lc: return l + return LANGUAGES[0] + +def make_lang_bar(current_lc, is_index=False, filename=""): + """Generate language bar HTML with correct active highlighting.""" + parts = [] + for lang in LANGUAGES: + code = lang["code"] + d = lang["dir"] # "" for en, "zh"/"es"/"ar" for others + if is_index: + href = f"/{d}/" if d else "/" + else: + href = f"/{d}/{filename}" if d else f"/{filename}" + is_active = (code == current_lc) + color = "#ffcc00" if is_active else "#888" + weight = "font-weight:bold;" if is_active else "" + parts.append(f'{lang["label"]}') + return '
' + "".join(parts) + '
' + +# ===== HTML模板 ===== +HTML_TPL = '\n\n\n\n\n{title} - {sname}\n\n\n\n\n{hreflang}\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n{lang_bar}\n
{nav_back} | {tools_nav}
\n
{content}
\n
{tags_html}
\n
{share_label}TwitterTelegram
\n

{news_title}

{news_desc}

\n

{cta_title}

{cta_desc}

{cta_btn} →Telegram
\n
👤
LiuDecaiFounder, GFIL

10+ years in forex, gold, and quantitative trading. Built GFIL Terminal to give retail traders the same tools institutions use. Focused on WebSocket data, order flow analysis, and AI-driven market intelligence.

\n\n\n\n' + +INDEX_TPL = '\n\n\n\n{sname} - Trading Insights\n\n\n\n{hreflang}\n\n\n\n\n\n\n{lang_bar}\n
{sname} | {tools_nav}
\n

{sname}

{nav_sub}

\n

{tools_title}

{tools_desc}

{tools_try} →Position SizePip Value
\n{articles}\n

{cta_title}

{cta_desc}

{cta_btn} →Telegram
\n
👤
LiuDecaiFounder, GFIL

10+ years in forex, gold, and quantitative trading. Built GFIL Terminal to give retail traders the same tools institutions use. Focused on WebSocket data, order flow analysis, and AI-driven market intelligence.

\n\n\n' + +# ===== 生成函数 ===== +def generate_article(art, lc="en"): + """生成单篇文章HTML""" + li = _get_lang(lc) + ld = li["dir"]; ldp = ld+"/" if ld else "" + cp = os.path.join(CONTENT_DIR, ld, f'article_{art["num"]}.html') + if not os.path.exists(cp): cp = os.path.join(CONTENT_DIR, f'article_{art["num"]}.html') + if not os.path.exists(cp): return None + + with open(cp, "r", encoding="utf-8") as f: raw = f.read() + content = clean_blog_content(raw) + title = ART_TITLES.get(lc,{}).get(art["num"], art["title"]) + # Inject H1 heading at top of article content + content = f'

{title}

\n{content}' + desc = art["desc"] + kw = ", ".join(art["tags"]) + tags_html = " ".join(f'{t}' for t in art["tags"]) + rt = estimate_reading_time(raw) + fn = f'{ldp}{art["filename"]}' + + hl = "" + for l in LANGUAGES: + d = l["dir"]+"/" if l["dir"] else "" + hl += f'\n' + hl += f'' + + html = HTML_TPL.format( + lc=li["locale"][:2], ldir=li["dir_attr"], title=title, desc=desc, kw=kw, sname=LANG("site_name", lc), + su=SITE_URL, fn=fn, hreflang=hl, gv=GOOGLE_VERIFY, ga=GA_ID, date=art["date"], + og_image=f"{SITE_URL}/og/{art['filename'].replace('.html','.svg')}", + content=content, tags_html=tags_html, nav_back=LANG("nav_back", lc), tools_nav=LANG("tools_nav", lc), + share_label=LANG("share", lc), share_text=quote(title), news_title=LANG("news_title", lc), + news_desc=LANG("news_desc", lc), news_btn=LANG("news_btn", lc), news_ph=LANG("news_ph", lc), + cta_title=LANG("cta_title", lc), cta_desc=LANG("cta_desc", lc), cta_btn=LANG("cta_btn", lc), cta_tg=LANG("cta_tg", lc), footer_tools=LANG("footer_tools", lc), footer_terminal=LANG("footer_terminal", lc), contact=LANG("contact", lc), + lang_bar=make_lang_bar(lc, is_index=False, filename=art["filename"]), + nav_home=f"{ld}/" if ld else "" + ) + + op = os.path.join(OUTPUT_DIR, fn) + os.makedirs(os.path.dirname(op) if ld else OUTPUT_DIR, exist_ok=True) + with open(op, "w", encoding="utf-8") as f: f.write(html) + print(f' ✓ [{lc}] {fn}') + return op + +def generate_index(lc="en"): + """生成各语言首页""" + li = _get_lang(lc); ld = li["dir"]; ldp = ld+"/" if ld else "" + sname = LANG("site_name", lc) + arts = "" + for a in ARTICLES: + t = ART_TITLES.get(lc,{}).get(a["num"], a["title"]) + arts += f'

{t}

{a["desc"]}

{" ".join(f"{x}" for x in a["tags"])} | {a["date"]}
\n' + + hl = "" + for l in LANGUAGES: + d = "/"+l["dir"]+"/" if l["dir"] else "/" + hl += f'\n' + hl += f'' + + ip = os.path.join(OUTPUT_DIR, ld, "index.html") if ld else os.path.join(OUTPUT_DIR, "index.html") + os.makedirs(os.path.dirname(ip) if ld else OUTPUT_DIR, exist_ok=True) + with open(ip, "w", encoding="utf-8") as f: + f.write(INDEX_TPL.format(lc=li["locale"][:2], ldir=li["dir_attr"], sname=sname, su=SITE_URL, ldir_url=ldp, hreflang=hl, ga=GA_ID, nav_sub=LANG("nav_sub",lc), tools_title=LANG("tools_title",lc), tools_desc=LANG("tools_desc",lc), tools_try=LANG("tools_try",lc), tools_nav=LANG("tools_nav",lc), cta_title=LANG("cta_title",lc), cta_desc=LANG("cta_desc",lc), cta_btn=LANG("cta_btn",lc), cta_tg=LANG("cta_tg",lc), footer_tools=LANG("footer_tools",lc), footer_terminal=LANG("footer_terminal",lc), articles=arts, lang_bar=make_lang_bar(lc, is_index=True))) + print(f' ✓ [{lc}] index.html') + +def generate_sitemap(): + """Generate sitemap_index.xml + sub-sitemaps by content type for better Google crawl""" + today = datetime.now().strftime('%Y-%m-%d') + url = lambda p: f' {SITE_URL}/{p}{today}\n' + tools_dir = os.path.join(BASE_DIR, 'tools') + total = 0 + + # --- sitemap-tools.xml: all calculator/tool pages --- + sm_tools = '\n\n' + for f in sorted(os.listdir(tools_dir)): + if f.endswith('.html') and not f.startswith('_'): + sm_tools += url(f'tools/{f}') + for sub in ['zh', 'es', 'ar']: + sm_tools += url(f'tools/{sub}/') + sd = os.path.join(tools_dir, sub) + if os.path.isdir(sd): + for f in sorted(os.listdir(sd)): + if f.endswith('.html'): + sm_tools += url(f'tools/{sub}/{f}') + # MD endpoints + md_dir = os.path.join(tools_dir, 'md') + if os.path.isdir(md_dir): + for f in sorted(os.listdir(md_dir)): + if f.endswith('.md'): + sm_tools += url(f'tools/md/{f}') + sm_tools += '' + with open(os.path.join(OUTPUT_DIR,"sitemap-tools.xml"),"w",encoding="utf-8") as f: + f.write(sm_tools) + tc = sm_tools.count('') + total += tc + print(f' sitemap-tools.xml ({tc} URLs)') + + # --- sitemap-posts.xml: blog articles (4 languages) --- + sm_posts = '\n\n' + sm_posts += url('') + for a in ARTICLES: + sm_posts += url(a['filename']) + for d in ['zh', 'es', 'ar']: + sm_posts += url(f'{d}/{a["filename"]}') + sm_posts += '' + with open(os.path.join(OUTPUT_DIR,"sitemap-posts.xml"),"w",encoding="utf-8") as f: + f.write(sm_posts) + pc = sm_posts.count('') + total += pc + print(f' sitemap-posts.xml ({pc} URLs)') + + # --- sitemap-geo.xml: research/docs/benchmark/entity/comparison/info pages --- + geo_pages = [ + 'tools/research.html', 'tools/docs.html', 'tools/roadmap.html', + 'tools/changelog.html', 'tools/benchmark.html', 'tools/datasets.html', + 'tools/status.html', 'tools/entity.html', 'tools/media.html', + 'tools/ai-prompts.html', 'tools/gfil-faq.html', 'tools/about-gfil.html', + 'tools/about-liudecai.html', 'tools/market-snapshot.html', + 'tools/gfil-vs-mt5.html', 'tools/gfil-vs-ctrader.html', + 'tools/gfil-vs-ninjatrader.html', 'tools/metatrader-vs-gfil.html', + 'tools/tradingview-alternative.html', 'tools/tradingview-free-alternative.html', + 'tools/tradingview-alert-limit-alternative.html', + 'tools/free-bloomberg-terminal-alternative.html', + 'tools/state-of-retail-forex-2026.html', + 'tools/index.html', 'tools/links.html', 'tools/link-to-us.html', + 'tools/disclaimer.html', 'tools/privacy.html', 'tools/terms.html', + 'tools/chrome-extension.html', 'tools/widgets.html', + 'tools/widget-position-size.html', 'tools/widget-performance.html', + 'tools/trading-journal-template.html', 'tools/trading-plan-template.html', + ] + sm_geo = '\n\n' + for p in geo_pages: + fpath = os.path.join(tools_dir, p.replace('tools/', '')) + if os.path.exists(fpath): + sm_geo += url(p) + sm_geo += '' + with open(os.path.join(OUTPUT_DIR,"sitemap-geo.xml"),"w",encoding="utf-8") as f: + f.write(sm_geo) + gc = sm_geo.count('') + total += gc + print(f' sitemap-geo.xml ({gc} URLs)') + + # --- sitemap_index.xml: master index --- + idx = '\n\n' + for name, count in [('tools', tc), ('posts', pc), ('geo', gc)]: + idx += f' {SITE_URL}/sitemap-{name}.xml{today}\n' + idx += '' + with open(os.path.join(OUTPUT_DIR,"sitemap.xml"),"w",encoding="utf-8") as f: + f.write(idx) + print(f' sitemap.xml (index → {total} URLs across 3 sub-sitemaps)') + +def generate_robots(): + robots = f'''User-agent: * +Allow: / + +# === AI Search Crawlers (GEO — Generative Engine Optimization) === +User-agent: OAI-SearchBot +Allow: / +User-agent: ChatGPT-User +Allow: / +User-agent: GPTBot +Allow: / +User-agent: ClaudeBot +Allow: / +User-agent: anthropic-ai +Allow: / +User-agent: PerplexityBot +Allow: / +User-agent: Google-Extended +Allow: / +User-agent: GoogleOther +Allow: / +# === China AI Search Crawlers === +User-agent: Bytespider +Allow: / +User-agent: DeepSeekBot +Allow: / +User-agent: KimiBot +Allow: / +User-agent: Baiduspider +Allow: / +# === Russia === +User-agent: YandexBot +Allow: / + +Sitemap: {SITE_URL}/sitemap.xml +''' + with open(os.path.join(OUTPUT_DIR,"robots.txt"),"w",encoding="utf-8") as f: + f.write(robots) + +def generate_llms(): + """Generate llms.txt and llms-full.txt for AI search discoverability""" + # Build tool list from actual files + tools_dir = os.path.join(BASE_DIR, 'tools') + tool_list = [] + for f in sorted(os.listdir(tools_dir)): + if f.endswith('.html') and not f.startswith('_'): + name = f.replace('.html','').replace('-',' ').title() + tool_list.append(f'- {name}: {SITE_URL}/tools/{f}') + + llms = f'''# GFIL Trading Insights +> Professional forex, gold, crypto, and indices trading tools and education. 22 free trading calculators calculators covering all asset classes, real-time market data, AI analysis, in 4 languages. + +## About +GFIL Trading Insights ({SITE_URL}) is the educational companion to GFIL Terminal (https://gfil-intel.xyz) — a browser-based trading platform with WebSocket real-time data (<50ms), 30+ instruments, and multi-model AI market analysis. Founded by LiuDecai with 10+ years in forex, gold, and quantitative trading. + +## Core Calculators (Open Source) +Python + JavaScript library: https://pypi.org/project/gfil-calculators/ | Mirror: https://gitlab.com/liudecai110/gfil-trading-calculators +- Position Size Calculator: lot size from account balance, risk%, stop loss — covers forex, gold (XAUUSD), crypto (BTC, ETH, SOL), indices (SPX500, NAS100, DAX) +- Pip Value Calculator: pip value for 30+ instruments with proper contract sizes +- ATR Calculator: Average True Range with Wilder's smoothing, ATR-based stop loss and take profit +- Fibonacci Calculator: retracement and extension levels with all standard ratios +- Kelly Criterion Calculator: optimal position sizing from win rate and payoff ratio +- Drawdown Calculator: max drawdown, recovery needed, consecutive loss analysis +- Margin Calculator: required margin for leveraged positions +- Risk/Reward Calculator: R:R ratio and break-even win rate +- Compound Interest Calculator: growth projections with monthly contributions + +## Key Educational Articles +- Position Size Complete Guide: {SITE_URL}/position-size-calculator-guide.html +- Gold XAUUSD Trading 2026: {SITE_URL}/gold-trading-2026-guide.html +- Why 87% of Retail Traders Lose Money: {SITE_URL}/why-retail-traders-lose-money.html +- Order Flow Trading Guide: {SITE_URL}/order-flow-trading.html +- WebSocket vs REST API for Trading: {SITE_URL}/websocket-vs-rest-api.html +- Bloomberg Terminal Alternatives: {SITE_URL}/bloomberg-alternative.html +- TradingView vs GFIL: {SITE_URL}/tradingview-vs-gfil-boss.html + +## All Tools Index +{SITE_URL}/tools/ + +## AI-Readable Endpoints (Markdown) +- MD Index: {SITE_URL}/tools/md/ + +## Formulas (for AI citation) +- Position Size = (Account Balance x Risk%) / (Stop Loss Pips x Pip Value per Lot) +- Pip Value = Pip Size x Contract Size x Exchange Rate +- True Range = max(High-Low, |High-PrevClose|, |Low-PrevClose|); ATR = SMA(TR, 14) +- Kelly % = Win Rate - (Loss Rate / Win-Loss Ratio) +- Drawdown % = (Peak - Current) / Peak x 100; Recovery % = (1/(1-DD%)) - 1 x 100 +- Fibonacci Retracement = High - (High-Low) x Ratio (23.6%, 38.2%, 50%, 61.8%, 78.6%) +- Required Margin = (Lot Size x Contract Size x Entry Price) / Leverage + +## Structured Data +- Schema: SoftwareApplication, FAQPage, Article, Organization, BreadcrumbList, Speakable, MathSolver, FinancialProduct +- Sitemap: {SITE_URL}/sitemap.xml (339 URLs across 3 sub-sitemaps: tools, posts, geo) +- RSS: {SITE_URL}/rss.xml + +## API Reference (REST + WebSocket) +Base URL: https://gfil-intel.xyz | Auth: X-API-Key header | Format: JSON + +### REST Endpoints +- GET /api/symbols — All instruments with metadata (symbol, type, pip_size, contract_size) +- GET /api/market/:symbol — OHLCV + real-time quote +- GET /api/market/:symbol/quote — Lightweight bid/ask only +- GET /api/signals — Public signal feed +- GET /api/news — Curated financial news with AI sentiment scores +- GET /api/session — Active session stats + +### WebSocket Channels +- WSS /ws/market — Real-time price stream (<50ms) +- WSS /ws/orderflow — Order flow: cumulative delta, bid/ask volume imbalance +- WSS /ws/signals — Live signal publishing feed + +Full docs: {SITE_URL}/tools/api-reference.html + +## Languages +English (/) · 中文 (/zh/) · Espanol (/es/) · العربية (/ar/) + +## Product +- GFIL Terminal: https://gfil-intel.xyz +- Telegram: https://t.me/GFIL_Trading +- Discord: https://discord.gg/GMmMCD4MCr +''' + with open(os.path.join(OUTPUT_DIR,"llms.txt"),"w",encoding="utf-8") as f: + f.write(llms) + # Also copy to tools/ for the tools subdirectory + tools_llms = os.path.join(OUTPUT_DIR,"tools","llms.txt") + os.makedirs(os.path.dirname(tools_llms), exist_ok=True) if not os.path.exists(os.path.dirname(tools_llms)) else None + with open(tools_llms,"w",encoding="utf-8") as f: + f.write(llms.replace(SITE_URL+'/', SITE_URL+'/tools/').replace('blog.quant-view.xyz/tools/', 'blog.quant-view.xyz/tools/')) + +def generate_rss(): + from datetime import datetime + items = '' + for a in ARTICLES: + art_date = datetime.strptime(a["date"], '%Y-%m-%d').strftime('%a, %d %b %Y 00:00:00 +0000') + items += f'{a["title"]}{SITE_URL}/{a["filename"]}{a["desc"]}{art_date}\n' + with open(os.path.join(OUTPUT_DIR,"rss.xml"),"w",encoding="utf-8") as f: + f.write(f'\n{SITE_NAME}{SITE_URL}Trading insights and free tools{items}') + +# ===== 上传函数 ===== +def _proxy_sock(): + """HTTP CONNECT代理连接""" + h,p = HTTP_PROXY.split(":") if ":" in HTTP_PROXY else (HTTP_PROXY,80) + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(20) + s.connect((h,int(p))) + s.sendall(f'CONNECT {SSH_HOST}:{SSH_PORT} HTTP/1.1\r\nHost: {SSH_HOST}:{SSH_PORT}\r\n\r\n'.encode()) + r = b'' + while b'\r\n\r\n' not in r: r += s.recv(4096) + if b'200' not in r: raise Exception(f'Proxy: {r.decode()[:100]}') + return s + +def upload_to_server(): + """上传所有文件到服务器""" + print('\n=== Uploading ===') + sock = HTTP_PROXY and _proxy_sock() or None + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + kw = dict(hostname=SSH_HOST,port=SSH_PORT,username=SSH_USER,password=SSH_PASS,timeout=20,banner_timeout=30,allow_agent=False,look_for_keys=False) + if sock: kw["sock"] = sock + ssh.connect(**kw) + sftp = ssh.open_sftp() + c = 0 + for root,dirs,files in os.walk(OUTPUT_DIR): + for f in files: + if f.endswith(('.html','.xml','.txt','.svg')): + local = os.path.join(root,f) + rel = os.path.relpath(local,OUTPUT_DIR).replace("\\","/") + remote = REMOTE_DIR.rstrip("/") + "/" + rel + rdir = os.path.dirname(remote) + ssh.exec_command(f'mkdir -p {rdir}',timeout=5) + time.sleep(0.03) + sftp.put(local,remote) + c += 1 + if c % 20 == 0: print(f' {c} files...') + # Tools pages (root level) + tools_dir = os.path.join(BASE_DIR,"tools") + for f in os.listdir(tools_dir): + if f.endswith('.html') or f.endswith('.zip'): + sftp.put(os.path.join(tools_dir,f), f'{REMOTE_DIR}tools/{f}') + c += 1 + # Tools language subdirs (ar/es/zh) + md endpoints + for sub in ['ar','es','zh','md']: + sub_dir = os.path.join(tools_dir, sub) + remote_sub = f'{REMOTE_DIR}tools/{sub}' + ssh.exec_command(f'mkdir -p {remote_sub}', timeout=5) + if os.path.exists(sub_dir): + for f in os.listdir(sub_dir): + if f.endswith(('.html','.md','.txt','.csv','.json')): + sftp.put(os.path.join(sub_dir, f), f'{remote_sub}/{f}') + c += 1 + sftp.close() + ssh.exec_command(f'chmod -R 644 {REMOTE_DIR}*.html {REMOTE_DIR}tools/*.html {REMOTE_DIR}tools/*/*.md 2>/dev/null; chmod 755 {REMOTE_DIR} {REMOTE_DIR}tools/ {REMOTE_DIR}tools/ar {REMOTE_DIR}tools/es {REMOTE_DIR}tools/zh {REMOTE_DIR}tools/md', timeout=10) + ssh.close() + print(f' ✓ {c} files uploaded') + return True + +def generate_og_images(): + """Generate SVG-based OG images for each article and the index page.""" + og_dir = os.path.join(OUTPUT_DIR, "og") + os.makedirs(og_dir, exist_ok=True) + c = 0 + + def _make_svg(title, subtitle="GFIL Trading Insights"): + # Truncate title to fit in SVG + display = title[:50] + ("..." if len(title) > 50 else "") + return f''' + + + {display} + {subtitle} + blog.quant-view.xyz + +''' + + # Generate for each article (EN only, used by all languages) + for art in ARTICLES: + svg_path = os.path.join(og_dir, f'{art["filename"].replace(".html", ".svg")}') + with open(svg_path, "w", encoding="utf-8") as f: + f.write(_make_svg(art["title"], " | ".join(art["tags"][:3]))) + c += 1 + + # Generate for index page + with open(os.path.join(og_dir, "index.svg"), "w", encoding="utf-8") as f: + f.write(_make_svg("GFIL Trading Insights", "Professional Forex, Gold, Crypto Trading Tools")) + c += 1 + + print(f' og images: {c} SVGs generated') + +# ===== 主入口 ===== +if __name__ == "__main__": + p = argparse.ArgumentParser() + p.add_argument("--serve", action="store_true") + p.add_argument("--no-upload", action="store_true") + a = p.parse_args() + os.makedirs(OUTPUT_DIR, exist_ok=True) + print("=== GFIL BLOG Build ===\n") + for l in LANGUAGES: + print(f'--- [{l["code"]}] ---') + for art in ARTICLES: generate_article(art, l["code"]) + for l in LANGUAGES: + print(f' [{l["code"]}] index') + generate_index(l["code"]) + print('\n--- SEO ---') + generate_sitemap() + generate_robots() + generate_llms() + generate_rss() + generate_og_images() + if not a.no_upload: + try: upload_to_server() + except Exception as e: print(f' Upload: {e}') + print('\n=== Done ===') + diff --git a/check_backup.py b/check_backup.py new file mode 100644 index 0000000..39a070d --- /dev/null +++ b/check_backup.py @@ -0,0 +1,68 @@ +"""RESTORE: Write correct Nginx config for gfil-lab.com""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +# This is the CORRECT config based on what we saw from the probe output +# (before our robots.txt changes broke it) +CORRECT_CONFIG = r"""server { + listen 80; + server_name quant-node.com gold-node.xyz gfil-intel.xyz quant-view.xyz; + + location /static/ { + alias /GFIL/static/; + expires 30d; + add_header Cache-Control "public, immutable"; + } + + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /socket.io/ { + proxy_pass http://127.0.0.1:5000/socket.io/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_buffering off; + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + } +} +""" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# First backup current broken config, then write the correct one +restore_cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Backup broken config +cp /etc/nginx/sites-enabled/gfil /etc/nginx/sites-enabled/gfil.bak.broken + +# Check if there is a gfil config in sites-available (original) +echo "=== sites-available ===" +ls -la /etc/nginx/sites-available/gfil* 2>/dev/null + +# Check the default config too +echo "=== default config ===" +cat /etc/nginx/sites-enabled/default +'""" +stdin, stdout, stderr = jd.exec_command(restore_cmd, timeout=30) +print(stdout.read().decode()) + +jd.close() diff --git a/check_github_links.py b/check_github_links.py new file mode 100644 index 0000000..4eb8062 --- /dev/null +++ b/check_github_links.py @@ -0,0 +1,31 @@ +"""Check all GitHub links on live pages""" +import urllib.request +import re + +pages = ['entity.html', 'media.html', 'gfil-faq.html', 'about-gfil.html'] + +for page in pages: + url = f'https://blog.quant-view.xyz/tools/{page}' + try: + r = urllib.request.urlopen(url, timeout=15) + html = r.read().decode() + gh_links = re.findall(r'href="(https://github\.com/[^"]+)"', html) + gh_text = re.findall(r'github\.com/[\w\-/]+', html) + print(f'\n=== {page} ===') + print(f'GitHub href links: {len(gh_links)}') + for link in set(gh_links): + print(f' href: {link}') + print(f'GitHub text refs: {len(gh_text)}') + for t in set(gh_text): + print(f' text: {t}') + # Also check for liudecai or liudapao + if 'liudecai' in html: + for line in html.split('\n'): + if 'liudecai' in line: + print(f' LIUDECAI: {line.strip()[:200]}') + if 'liudapao' in html: + for line in html.split('\n'): + if 'liudapao' in line: + print(f' LIUDAPAO: {line.strip()[:200]}') + except Exception as e: + print(f'{page}: ERROR {e}') diff --git a/check_https_robots.py b/check_https_robots.py new file mode 100644 index 0000000..2809bd5 --- /dev/null +++ b/check_https_robots.py @@ -0,0 +1,13 @@ +import urllib.request, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +req = urllib.request.Request("https://gfil-lab.com/robots.txt", headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}) +r = urllib.request.urlopen(req, timeout=10) +content = r.read().decode() +print("Status:", r.status) +print("Size:", len(content), "chars") +print("GPTBot:", "GPTBot" in content) +print("PerplexityBot:", "PerplexityBot" in content) +print("ClaudeBot:", "ClaudeBot" in content) +print() +print(content) diff --git a/check_robots_file.py b/check_robots_file.py new file mode 100644 index 0000000..640893e --- /dev/null +++ b/check_robots_file.py @@ -0,0 +1,19 @@ +"""Check robots.txt file content on gfil-lab.com server""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +cmd = "sshpass -p '" + LAB_PASS + "' ssh -o StrictHostKeyChecking=no " + LAB_USER + "@" + LAB_HOST + " 'echo \"=== File content ===\" && cat /var/www/gfil-lab/robots.txt && echo \"\" && echo \"=== File size ===\" && wc -c /var/www/gfil-lab/robots.txt && echo \"=== Direct curl (bypass CF) ===\" && curl -s http://localhost/robots.txt 2>/dev/null'" +stdin, stdout, stderr = jd.exec_command(cmd, timeout=20) +print(stdout.read().decode()) +jd.close() diff --git a/config.py b/config.py new file mode 100644 index 0000000..13e3e56 --- /dev/null +++ b/config.py @@ -0,0 +1,92 @@ +""" +GFIL BLOG 统一配置加载器 +用法: from config import TELEGRAPH_TOKEN, BLOG_URL, ... +所有密钥从 .env 加载,修改密钥只需改 .env 一个文件 +""" +import os +import sys + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +ENV_FILE = os.path.join(BASE_DIR, ".env") + +def _load_env(): + """手动解析 .env,零依赖""" + env = {} + if os.path.exists(ENV_FILE): + with open(ENV_FILE, "r", encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + key, _, val = line.partition("=") + env[key.strip()] = val.strip().strip('"').strip("'") + return env + +_env = _load_env() + +def _get(key, default=""): + """优先环境变量,其次 .env 文件""" + return os.environ.get(key) or _env.get(key, default) + +# --- 服务器 --- +SSH_HOST = _get("SSH_HOST", "") +SSH_USER = _get("SSH_USER", "root") +SSH_PASS = _get("SSH_PASS") +SSH_PORT = int(_get("SSH_PORT", "22")) + +# --- API Keys --- +TELEGRAPH_TOKEN = _get("TELEGRAPH_TOKEN") +GITHUB_TOKEN = _get("GITHUB_TOKEN") +GITHUB_TOKEN_ALT = _get("GITHUB_TOKEN_ALT") +TG_BOT_TOKEN = _get("TG_BOT_TOKEN") +DISCORD_WEBHOOK = _get("DISCORD_WEBHOOK") +DEEPSEEK_KEY = _get("DEEPSEEK_KEY") +DEVTO_KEY = _get("DEVTO_KEY") +INDEXNOW_KEY = _get("INDEXNOW_KEY") +SCREENSHOTONE_KEY = _get("SCREENSHOTONE_KEY") +SHOTSTACK_KEY = _get("SHOTSTACK_KEY") +BUFFER_TOKEN = _get("BUFFER_TOKEN") + +# --- Google --- +GOOGLE_EMAIL = _get("GOOGLE_EMAIL") +GOOGLE_PASS = _get("GOOGLE_PASS") +SMTP_PASS = _get("SMTP_PASS") +GA_ID = _get("GA_ID") +GOOGLE_VERIFY = _get("GOOGLE_VERIFY") + +# --- URLs --- +BLOG_URL = _get("BLOG_URL", "https://blog.quant-view.xyz") +TERMINAL_URL = _get("TERMINAL_URL", "https://gfil-intel.xyz") +LANDING_URL = _get("LANDING_URL", "https://gold-node.xyz") +TG_CHANNEL = _get("TG_CHANNEL", "https://t.me/GFIL_Trading") +DISCORD_INVITE = _get("DISCORD_INVITE", "https://discord.gg/GMmMCD4MCr") +TOOLS_URL = f"{BLOG_URL}/tools/" + +# --- 本地代理 --- +HTTP_PROXY = _get("HTTP_PROXY") + +# --- 邮箱 --- +MAILERLITE_GROUP_ID = "189341033455158511" + +# --- 密钥状态检查 --- +def check_config(): + """打印配置状态,快速发现过期密钥""" + keys = { + "Telegraph": TELEGRAPH_TOKEN, + "GitHub": GITHUB_TOKEN, + "TG Bot": TG_BOT_TOKEN, + "Discord": DISCORD_WEBHOOK, + "DeepSeek": DEEPSEEK_KEY, + } + print("=== 配置状态检查 ===") + for name, val in keys.items(): + if not val: + print(f" {name}: MISSING!") + elif "expired" in val.lower() or "invalid" in val.lower(): + print(f" {name}: EXPIRED/INVALID") + else: + masked = val[:15] + "..." if len(val) > 15 else val + print(f" {name}: {masked}") + +if __name__ == "__main__": + check_config() diff --git a/deploy_and_verify.py b/deploy_and_verify.py new file mode 100644 index 0000000..2c6589d --- /dev/null +++ b/deploy_and_verify.py @@ -0,0 +1,119 @@ +import urllib.request, sys, io, paramiko, os, time +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +# === Step 1: Connect and run the fix on server === +print("=== STEP 1: Connect and modify ===") +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Upload fix script +FIX_SCRIPT = r''' +import shutil +src = "/etc/nginx/sites-enabled/gfil" +bak = "/etc/nginx/sites-enabled/gfil.bak.before-robots" +shutil.copy2(src, bak) +with open(src, "r") as f: + content = f.read() +robots_block = " location = /robots.txt {\n alias /var/www/gfil-lab/robots.txt;\n default_type text/plain;\n }\n\n" +if "robots.txt" not in content: + content = content.replace(" location / {", robots_block + " location / {") + with open(src, "w") as f: + f.write(content) + print("INSERTED") +else: + print("ALREADY_EXISTS") +''' +script_path = r"C:\Users\thinkpad\AppData\Local\Temp\kilo\fix_nginx.py" +with open(script_path, "w") as f: + f.write(FIX_SCRIPT) + +sftp = jd.open_sftp() +sftp.put(script_path, "/tmp/fix_nginx.py") +sftp.close() + +# SCP to target +scp_cmd = "sshpass -p '" + LAB_PASS + "' scp -o StrictHostKeyChecking=no /tmp/fix_nginx.py " + LAB_USER + "@" + LAB_HOST + ":/tmp/fix_nginx.py" +stdin, stdout, stderr = jd.exec_command(scp_cmd, timeout=15) +stdout.channel.recv_exit_status() + +# Run on target +run_cmd = "sshpass -p '" + LAB_PASS + "' ssh -o StrictHostKeyChecking=no " + LAB_USER + "@" + LAB_HOST + " 'python3 /tmp/fix_nginx.py && nginx -t 2>&1 && (systemctl reload nginx && echo NGINX_OK) || (cp /etc/nginx/sites-enabled/gfil.bak.before-robots /etc/nginx/sites-enabled/gfil && nginx -t 2>&1 && systemctl reload nginx && echo ROLLBACK_OK) && rm -f /tmp/fix_nginx.py'" +stdin, stdout, stderr = jd.exec_command(run_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() + +time.sleep(2) + +# === STEP 2: Code verification === +print("\n=== STEP 2: Code verification (automated) ===") + +# 2a. Verify Nginx config diff +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +verify_cmd = "sshpass -p '" + LAB_PASS + "' ssh -o StrictHostKeyChecking=no " + LAB_USER + "@" + LAB_HOST + " 'echo DIFF: && diff /etc/nginx/sites-enabled/gfil /etc/nginx/sites-enabled/gfil.bak.before-robots && echo IDENTICAL || echo MODIFIED && echo CONFIG: && cat /etc/nginx/sites-enabled/gfil && echo NGINX_TEST: && nginx -t 2>&1'" +stdin, stdout, stderr = jd.exec_command(verify_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() + +# 2b. External HTTP checks +print("=== External HTTP checks ===") +for domain in ["gfil-lab.com", "gfil-intel.xyz"]: + # Homepage + try: + req = urllib.request.Request("https://" + domain + "/", headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + print(f" {domain} homepage: {r.status} OK, size={len(r.read())} bytes") + except Exception as e: + print(f" {domain} homepage: ERROR {e}") + + # robots.txt + try: + req = urllib.request.Request("https://" + domain + "/robots.txt", headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + has_gptbot = "GPTBot" in content + has_perplexity = "PerplexityBot" in content + has_sitemap = "Sitemap:" in content + print(f" {domain} robots.txt: {r.status} | GPTBot={has_gptbot} | PerplexityBot={has_perplexity} | Sitemap={has_sitemap} | {len(content)} chars") + except Exception as e: + print(f" {domain} robots.txt: ERROR {e}") + +# 2c. Googlebot simulation +print("\n=== Googlebot simulation ===") +try: + req = urllib.request.Request("https://gfil-lab.com/robots.txt", headers={"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + print(f" Googlebot -> gfil-lab.com/robots.txt: {r.status} OK, {len(content)} chars") + print(f" First 3 lines: {content[:150]}") +except Exception as e: + print(f" Googlebot check: ERROR {e}") + +# === STEP 3: Human observation checklist === +print("\n" + "=" * 60) +print("STEP 3: HUMAN OBSERVATION CHECKLIST") +print("=" * 60) +print("Please manually verify in your browser:") +print() +print("1. Open https://gfil-lab.com/ -> should load normally (EN homepage)") +print("2. Open https://gfil-lab.com/robots.txt -> should show full AI-crawler robots.txt") +print("3. Open https://gfil-intel.xyz/robots.txt -> should show robots.txt with Sitemap: https://gfil-intel.xyz/sitemap.xml") +print("4. Check login function works: https://gfil-lab.com/login") +print("5. Check WebSocket: terminal page should connect") +print() +print("If anything is broken, rollback command:") +print(" ssh root@216.144.233.14") +print(" cp /etc/nginx/sites-enabled/gfil.bak.before-robots /etc/nginx/sites-enabled/gfil") +print(" systemctl reload nginx") diff --git a/deploy_robots.py b/deploy_robots.py new file mode 100644 index 0000000..a85bbe7 --- /dev/null +++ b/deploy_robots.py @@ -0,0 +1,103 @@ +"""Deploy robots.txt to gfil-lab.com (216.144.233.14) and gfil-intel.xyz (107.174.186.162) via JD Cloud""" +import paramiko +import os +import time + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +# gfil-lab.com server +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" # from .env GFIL_SSH_PASSWORD + +# gfil-intel.xyz server (same as blog) +RN_HOST = "107.174.186.162" +RN_USER = "root" +RN_PASS = "liudapao2026" + +TEMP_DIR = r"C:\Users\thinkpad\AppData\Local\Temp\kilo" + +def deploy(): + # Step 1: Connect to JD Cloud + print("[1/4] Connecting to JD Cloud...") + jd = paramiko.SSHClient() + jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + sftp = jd.open_sftp() + print(" Connected") + + # Step 2: Upload robots.txt files to JD Cloud + print("\n[2/4] Uploading robots.txt files to JD Cloud...") + sftp.put(os.path.join(TEMP_DIR, "robots_gfil_lab.txt"), "/tmp/robots_gfil_lab.txt") + sftp.put(os.path.join(TEMP_DIR, "robots_gfil_intel.txt"), "/tmp/robots_gfil_intel.txt") + print(" Uploaded") + + # Step 3: Deploy to gfil-lab.com (216.144.233.14) + print("\n[3/4] Deploying to gfil-lab.com (216.144.233.14)...") + # Find the web root - could be /GFIL/static/ or Nginx serves from elsewhere + # The Flask app serves static from /GFIL/static/, but robots.txt needs to be at web root + # Nginx config shows: location /static/ { alias /GFIL/static/; } + # For robots.txt at root, we need to place it where Nginx can serve it + # Best approach: add to Nginx config or put in a root directory + + deploy_cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Find Nginx config and web root +echo "=== Finding web root ===" +grep -r "root " /etc/nginx/sites-enabled/ 2>/dev/null | head -5 +grep -r "root " /etc/nginx/conf.d/ 2>/dev/null | head -5 +ls -la /var/www/ 2>/dev/null +ls -la /GFIL/static/ 2>/dev/null | head -5 + +# Deploy robots.txt - try multiple locations +cp /tmp/robots_gfil_lab.txt /GFIL/robots.txt 2>/dev/null +cp /tmp/robots_gfil_lab.txt /GFIL/static/robots.txt 2>/dev/null +cp /tmp/robots_gfil_lab.txt /var/www/html/robots.txt 2>/dev/null +mkdir -p /var/www/gfil-lab.com 2>/dev/null +cp /tmp/robots_gfil_lab.txt /var/www/gfil-lab.com/robots.txt 2>/dev/null + +# Check if Nginx has a root directive we can use +cat /etc/nginx/sites-enabled/gfil-lab.com 2>/dev/null || cat /etc/nginx/conf.d/gfil-lab.com.conf 2>/dev/null || echo "No gfil-lab nginx config found" + +echo "=== Verifying ===" +find / -name "robots.txt" -maxdepth 4 2>/dev/null | head -5 +'""" + stdin, stdout, stderr = jd.exec_command(deploy_cmd, timeout=30) + output = stdout.read().decode() + print(output[:2000]) + + # Step 4: Deploy to gfil-intel.xyz (107.174.186.162 - RackNerd) + print("\n[4/4] Deploying to gfil-intel.xyz (RackNerd 107.174.186.162)...") + deploy_cmd2 = f"""sshpass -p '{RN_PASS}' ssh -o StrictHostKeyChecking=no {RN_USER}@{RN_HOST} ' +# Find where gfil-intel.xyz is served from +echo "=== Finding gfil-intel.xyz web root ===" +grep -r "gfil-intel" /etc/nginx/ 2>/dev/null | head -10 +ls -la /var/www/ 2>/dev/null | head -10 +find /var/www -name "index.html" -maxdepth 3 2>/dev/null | head -5 + +# Deploy robots.txt to likely locations +cp /tmp/robots_gfil_intel.txt /var/www/gfil-intel.xyz/robots.txt 2>/dev/null +cp /tmp/robots_gfil_intel.txt /var/www/html/robots.txt 2>/dev/null +cp /tmp/robots_gfil_intel.txt /var/www/blog/robots.txt 2>/dev/null + +echo "=== Nginx config ===" +grep -l "gfil-intel" /etc/nginx/sites-enabled/ 2>/dev/null +cat /etc/nginx/sites-enabled/gfil-intel* 2>/dev/null | head -30 + +echo "=== Verifying ===" +find /var/www -name "robots.txt" -maxdepth 3 2>/dev/null +'""" + stdin, stdout, stderr = jd.exec_command(deploy_cmd2, timeout=30) + output = stdout.read().decode() + print(output[:2000]) + + # Cleanup + jd.exec_command("rm -f /tmp/robots_gfil_lab.txt /tmp/robots_gfil_intel.txt", timeout=5) + sftp.close() + jd.close() + print("\nDone!") + +if __name__ == "__main__": + deploy() diff --git a/deploy_robots_v2.py b/deploy_robots_v2.py new file mode 100644 index 0000000..7024fa2 --- /dev/null +++ b/deploy_robots_v2.py @@ -0,0 +1,178 @@ +"""Deploy robots.txt to gfil-lab.com via Nginx location block + reload""" +import paramiko +import time + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +ROBOTS_CONTENT = """User-agent: * +Allow: / + +# === AI Search Crawlers (GEO) === +User-agent: OAI-SearchBot +Allow: / +User-agent: ChatGPT-User +Allow: / +User-agent: GPTBot +Allow: / +User-agent: ClaudeBot +Allow: / +User-agent: anthropic-ai +Allow: / +User-agent: PerplexityBot +Allow: / +User-agent: Google-Extended +Allow: / +User-agent: GoogleOther +Allow: / +# === China AI Search Crawlers === +User-agent: Bytespider +Allow: / +User-agent: DeepSeekBot +Allow: / +User-agent: KimiBot +Allow: / +User-agent: Baiduspider +Allow: / +# === Russia === +User-agent: YandexBot +Allow: / + +Sitemap: https://gfil-lab.com/sitemap.xml +""" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +print("[1/3] Creating robots.txt and updating Nginx on gfil-lab.com...") + +# Write robots.txt and add Nginx location block +# Use sed to add location block before the existing "location /" block +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Create robots.txt file +mkdir -p /var/www/gfil-lab +cat > /var/www/gfil-lab/robots.txt << '"'"'ROBOTSEOF'"'"' +{ROBOTS_CONTENT} +ROBOTSEOF + +# Verify file was created +echo "=== robots.txt content ===" +cat /var/www/gfil-lab/robots.txt + +# Add Nginx location block for robots.txt +# Insert before "location /" in the gfil config +if ! grep -q "robots.txt" /etc/nginx/sites-enabled/gfil; then + sed -i "/location \\/ {{/i\\\\n location = /robots.txt {{\\n alias /var/www/gfil-lab/robots.txt;\\n default_type text/plain;\\n }}" /etc/nginx/sites-enabled/gfil + echo "Nginx config updated" +else + echo "robots.txt location already exists" +fi + +echo "=== Updated Nginx config ===" +cat /etc/nginx/sites-enabled/gfil + +# Test and reload Nginx +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "Nginx reloaded successfully" +else + echo "Nginx config test FAILED - not reloading" +fi + +# Verify robots.txt is accessible +sleep 1 +curl -s -o /dev/null -w "%{{http_code}}" http://localhost/robots.txt 2>/dev/null || echo "curl failed" +'""" + +stdin, stdout, stderr = jd.exec_command(cmd, timeout=30) +output = stdout.read().decode() +print(output[:3000]) + +print("\n[2/3] Verifying gfil-intel.xyz also gets robots.txt (it proxies gfil-lab.com)...") +cmd2 = f"""sshpass -p '{RN_PASS}' ssh -o StrictHostKeyChecking=no {RN_USER}@107.174.186.162 ' +# gfil-intel.xyz proxies to gfil-lab.com, so robots.txt should come through +# But it also has sub_filter that replaces gfil-lab.com -> $host +# Test locally +curl -s -o /dev/null -w "%{{http_code}}" -H "Host: gfil-intel.xyz" http://localhost/robots.txt 2>/dev/null || echo "direct test failed" + +# Also check if gfil-mask nginx config needs a separate robots.txt location +if ! grep -q "robots.txt" /etc/nginx/sites-available/gfil-mask; then + echo "Need to add robots.txt location to gfil-mask config too" + + mkdir -p /var/www/gfil-intel + cat > /var/www/gfil-intel/robots.txt << '"'"'ROBOTSEOF'"'"' +User-agent: * +Allow: / + +User-agent: OAI-SearchBot +Allow: / +User-agent: ChatGPT-User +Allow: / +User-agent: GPTBot +Allow: / +User-agent: ClaudeBot +Allow: / +User-agent: anthropic-ai +Allow: / +User-agent: PerplexityBot +Allow: / +User-agent: Google-Extended +Allow: / +User-agent: GoogleOther +Allow: / +User-agent: Bytespider +Allow: / +User-agent: DeepSeekBot +Allow: / +User-agent: KimiBot +Allow: / +User-agent: Baiduspider +Allow: / +User-agent: YandexBot +Allow: / + +Sitemap: https://gfil-intel.xyz/sitemap.xml +ROBOTSEOF + + # Add location block before the main location / + sed -i "/location \\/ {{/i\\\\n location = /robots.txt {{\\n alias /var/www/gfil-intel/robots.txt;\\n default_type text/plain;\\n }}" /etc/nginx/sites-available/gfil-mask + echo "gfil-mask config updated" + + nginx -t 2>&1 + if [ $? -eq 0 ]; then + systemctl reload nginx + echo "Nginx reloaded on RackNerd" + else + echo "Nginx config FAILED on RackNerd" + fi +else + echo "robots.txt location already in gfil-mask" +fi +'""" +stdin, stdout, stderr = jd.exec_command(cmd2, timeout=30) +output = stdout.read().decode() +print(output[:2000]) + +print("\n[3/3] Final verification from external...") +jd.close() + +# Test from local +import urllib.request +for domain in ['gfil-lab.com', 'gfil-intel.xyz']: + try: + req = urllib.request.Request(f'https://{domain}/robots.txt', + headers={'User-Agent': 'Mozilla/5.0'}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + has_gptbot = 'GPTBot' in content + print(f" {domain}: {r.status}, GPTBot={has_gptbot}, {len(content)} chars") + except Exception as e: + print(f" {domain}: {e}") diff --git a/deploy_robots_v3.py b/deploy_robots_v3.py new file mode 100644 index 0000000..c7d19df --- /dev/null +++ b/deploy_robots_v3.py @@ -0,0 +1,138 @@ +"""Fix Nginx config on gfil-lab.com and deploy robots.txt to gfil-intel.xyz""" +import paramiko +import time + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +RN_HOST = "107.174.186.162" +RN_USER = "root" +RN_PASS = "liudapao2026" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# === Fix gfil-lab.com Nginx config === +print("[1/3] Fixing Nginx config on gfil-lab.com...") +fix_cmd = """sshpass -p 'Kt9V72Tx2c48ChikKU' ssh -o StrictHostKeyChecking=no root@216.144.233.14 ' +# Remove the broken line with standalone "n" +sed -i "/^n location = \/robots\.txt/d" /etc/nginx/sites-enabled/gfil + +# Check current state +echo "=== Current config (robots.txt area) ===" +grep -n "robots" /etc/nginx/sites-enabled/gfil + +# If robots.txt location block already exists and is clean, just test +# If not, add it properly +if grep -q "robots.txt" /etc/nginx/sites-enabled/gfil; then + echo "robots.txt block exists, checking syntax..." +else + echo "Adding robots.txt location block..." + sed -i "/location \/ {/i\\ location = /robots.txt {\\n alias /var/www/gfil-lab/robots.txt;\\n default_type text/plain;\\n }" /etc/nginx/sites-enabled/gfil +fi + +# Show the config around the robots block +grep -n -B2 -A5 "robots" /etc/nginx/sites-enabled/gfil + +# Test nginx +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "Nginx reloaded OK" +else + echo "Nginx test still failing, showing full config..." + cat -n /etc/nginx/sites-enabled/gfil +fi +'""" +stdin, stdout, stderr = jd.exec_command(fix_cmd, timeout=30) +print(stdout.read().decode()[:3000]) + +# === Deploy robots.txt to gfil-intel.xyz on RackNerd === +print("\n[2/3] Deploying robots.txt to gfil-intel.xyz (RackNerd)...") +intel_cmd = """sshpass -p 'liudapao2026' ssh -o StrictHostKeyChecking=no root@107.174.186.162 ' +mkdir -p /var/www/gfil-intel + +# Write robots.txt +cat > /var/www/gfil-intel/robots.txt << '"'"'EOF'"'"' +User-agent: * +Allow: / + +User-agent: OAI-SearchBot +Allow: / +User-agent: ChatGPT-User +Allow: / +User-agent: GPTBot +Allow: / +User-agent: ClaudeBot +Allow: / +User-agent: anthropic-ai +Allow: / +User-agent: PerplexityBot +Allow: / +User-agent: Google-Extended +Allow: / +User-agent: GoogleOther +Allow: / +User-agent: Bytespider +Allow: / +User-agent: DeepSeekBot +Allow: / +User-agent: KimiBot +Allow: / +User-agent: Baiduspider +Allow: / +User-agent: YandexBot +Allow: / + +Sitemap: https://gfil-intel.xyz/sitemap.xml +EOF + +# Verify +echo "=== robots.txt ===" +cat /var/www/gfil-intel/robots.txt | head -5 + +# Add Nginx location block to gfil-mask if not already there +if grep -q "robots.txt" /etc/nginx/sites-available/gfil-mask; then + echo "robots.txt block already in gfil-mask" +else + echo "Adding robots.txt to gfil-mask..." + sed -i "/location \\/ {/i\\ location = /robots.txt {\\n alias /var/www/gfil-intel/robots.txt;\\n default_type text/plain;\\n }" /etc/nginx/sites-available/gfil-mask +fi + +# Test and reload +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "Nginx reloaded on RackNerd" +else + echo "Nginx test FAILED on RackNerd" + cat -n /etc/nginx/sites-available/gfil-mask | head -40 +fi +'""" +stdin, stdout, stderr = jd.exec_command(intel_cmd, timeout=30) +print(stdout.read().decode()[:2000]) + +# === Verify both from external === +print("\n[3/3] External verification...") +jd.close() + +import urllib.request +for domain in ['gfil-lab.com', 'gfil-intel.xyz', 'blog.quant-view.xyz']: + try: + req = urllib.request.Request(f'https://{domain}/robots.txt', + headers={'User-Agent': 'Mozilla/5.0'}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + has_gptbot = 'GPTBot' in content + has_perplexity = 'PerplexityBot' in content + has_sitemap = 'Sitemap:' in content + print(f" {domain}: {r.status} OK | GPTBot={has_gptbot} | PerplexityBot={has_perplexity} | Sitemap={has_sitemap} | {len(content)} chars") + except Exception as e: + print(f" {domain}: {e}") diff --git a/deploy_via_jdcloud.py b/deploy_via_jdcloud.py new file mode 100644 index 0000000..e9ad3f2 --- /dev/null +++ b/deploy_via_jdcloud.py @@ -0,0 +1,122 @@ +"""Deploy blog to RackNerd via JD Cloud jumphost""" +import paramiko +import os +import time +import glob + +# JD Cloud jumphost +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +# RackNerd target +RN_HOST = "107.174.186.162" +RN_USER = "root" +RN_PASS = "liudapao2026" +RN_DIR = "/var/www/blog/" + +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) + +def deploy(): + # Find latest packages + output_pkgs = sorted(glob.glob(os.path.join(BASE_DIR, "blog-deploy-*.tar.gz"))) + tools_pkgs = sorted(glob.glob(os.path.join(BASE_DIR, "blog-tools-*.tar.gz"))) + + if not output_pkgs or not tools_pkgs: + print("ERROR: No deployment packages found. Run build first.") + return + + output_pkg = output_pkgs[-1] + tools_pkg = tools_pkgs[-1] + print(f"Deploying: {os.path.basename(output_pkg)}, {os.path.basename(tools_pkg)}") + + # Step 1: Connect to JD Cloud + print("\n[1/5] Connecting to JD Cloud jumphost...") + jd = paramiko.SSHClient() + jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + print(" Connected to JD Cloud") + + # Step 2: Upload packages to JD Cloud + print("\n[2/5] Uploading packages to JD Cloud...") + sftp = jd.open_sftp() + + remote_output = f"/tmp/{os.path.basename(output_pkg)}" + remote_tools = f"/tmp/{os.path.basename(tools_pkg)}" + + for local, remote in [(output_pkg, remote_output), (tools_pkg, remote_tools)]: + size_mb = os.path.getsize(local) / (1024*1024) + print(f" Uploading {os.path.basename(local)} ({size_mb:.1f} MB)...") + sftp.put(local, remote) + print(f" Uploaded to {remote}") + + sftp.close() + + # Step 3: SCP from JD Cloud to RackNerd + print("\n[3/5] SCP packages to RackNerd...") + # Install sshpass if not present + jd.exec_command("which sshpass || apt-get install -y sshpass", timeout=30) + time.sleep(2) + + scp_cmd = f"sshpass -p '{RN_PASS}' scp -o StrictHostKeyChecking=no {remote_output} {remote_tools} {RN_USER}@{RN_HOST}:/tmp/" + print(f" Running: {scp_cmd[:80]}...") + stdin, stdout, stderr = jd.exec_command(scp_cmd, timeout=120) + exit_code = stdout.channel.recv_exit_status() + if exit_code != 0: + err = stderr.read().decode() + print(f" SCP error (exit {exit_code}): {err[:200]}") + # Try again + print(" Retrying...") + time.sleep(3) + stdin, stdout, stderr = jd.exec_command(scp_cmd, timeout=120) + exit_code = stdout.channel.recv_exit_status() + if exit_code != 0: + print(f" SCP failed again: {stderr.read().decode()[:200]}") + jd.close() + return + print(" SCP complete") + + # Step 4: Extract on RackNerd + print("\n[4/5] Extracting on RackNerd...") + extract_cmd = f"""sshpass -p '{RN_PASS}' ssh -o StrictHostKeyChecking=no {RN_USER}@{RN_HOST} ' +cd {RN_DIR} +echo "Extracting output..." +tar -xzf /tmp/{os.path.basename(output_pkg)} +echo "Extracting tools..." +mkdir -p tools +tar -xzf /tmp/{os.path.basename(tools_pkg)} -C tools/ +echo "Setting permissions..." +chmod -R 644 *.html tools/*.html 2>/dev/null +chmod 755 . tools/ tools/ar tools/es tools/zh tools/md 2>/dev/null +echo "Cleaning up /tmp..." +rm -f /tmp/{os.path.basename(output_pkg)} /tmp/{os.path.basename(tools_pkg)} +echo "DONE" +'""" + stdin, stdout, stderr = jd.exec_command(extract_cmd, timeout=60) + output = stdout.read().decode() + err = stderr.read().decode() + print(f" Output: {output}") + if err: + print(f" Stderr: {err[:200]}") + + # Step 5: Verify + print("\n[5/5] Verifying deployment...") + verify_cmd = f"""sshpass -p '{RN_PASS}' ssh -o StrictHostKeyChecking=no {RN_USER}@{RN_HOST} ' +echo "HTML files in root: $(ls {RN_DIR}*.html 2>/dev/null | wc -l)" +echo "HTML files in tools: $(ls {RN_DIR}tools/*.html 2>/dev/null | wc -l)" +echo "Sitemap size: $(wc -l < {RN_DIR}sitemap.xml 2>/dev/null)" +echo "IndexNow key: $(cat {RN_DIR}72aa77b68704abcfada4020ba81f7c5a.txt 2>/dev/null | head -1)" +echo "BingSiteAuth: $(cat {RN_DIR}BingSiteAuth.xml 2>/dev/null | head -1)" +'""" + stdin, stdout, stderr = jd.exec_command(verify_cmd, timeout=30) + print(stdout.read().decode()) + + # Clean up JD Cloud temp files + jd.exec_command(f"rm -f {remote_output} {remote_tools}", timeout=10) + + jd.close() + print("\nDeployment complete!") + +if __name__ == "__main__": + deploy() diff --git a/diagnose_domains.py b/diagnose_domains.py new file mode 100644 index 0000000..6665801 --- /dev/null +++ b/diagnose_domains.py @@ -0,0 +1,61 @@ +"""Diagnose gfil-lab.com redirect loop and gfil-intel.xyz bot blocking""" +import urllib.request +import http.client + +# Test gfil-lab.com +print("=== gfil-lab.com ===") +try: + r = urllib.request.urlopen("https://gfil-lab.com", timeout=15) + print(f"Status: {r.status}") + print(f"URL after redirects: {r.url}") + print(f"Headers: {dict(r.headers)}") +except urllib.error.HTTPError as e: + print(f"HTTPError: {e.code} {e.reason}") + print(f"Headers: {dict(e.headers)}") + print(f"Redirect location: {e.headers.get('Location', 'N/A')}") +except Exception as e: + print(f"Error: {type(e).__name__}: {e}") + +# Test with manual redirect following +print("\n=== Manual redirect trace for gfil-lab.com ===") +url = "https://gfil-lab.com" +for i in range(10): + try: + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + print(f"Hop {i}: {url} -> {r.url} (status {r.status})") + break + except urllib.error.HTTPError as e: + loc = e.headers.get("Location", "") + print(f"Hop {i}: {url} -> {e.code} {e.reason}, Location: {loc}") + if loc: + url = loc + else: + break + except Exception as e: + print(f"Hop {i}: {url} -> Error: {e}") + break + +# Test gfil-intel.xyz +print("\n=== gfil-intel.xyz ===") +try: + req = urllib.request.Request("https://gfil-intel.xyz", headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}) + r = urllib.request.urlopen(req, timeout=15) + print(f"Status: {r.status}") + print(f"Size: {len(r.read())} bytes") +except urllib.error.HTTPError as e: + print(f"HTTPError: {e.code} {e.reason}") + body = e.read().decode()[:500] + print(f"Body: {body}") +except Exception as e: + print(f"Error: {type(e).__name__}: {e}") + +# Check DNS +print("\n=== DNS Check ===") +import socket +for domain in ["gfil-lab.com", "gfil-intel.xyz"]: + try: + ips = socket.getaddrinfo(domain, 443, socket.AF_INET) + print(f"{domain}: {[x[4][0] for x in ips[:3]]}") + except Exception as e: + print(f"{domain}: DNS error - {e}") diff --git a/diagnose_gfillab.py b/diagnose_gfillab.py new file mode 100644 index 0000000..7b7e8bd --- /dev/null +++ b/diagnose_gfillab.py @@ -0,0 +1,97 @@ +"""Diagnose exactly what's blocking gfil-lab.com""" +import urllib.request, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +# Test gfil-lab.com with various approaches +print("=== gfil-lab.com 诊断 ===\n") + +# 1. Plain request (no UA) +print("[1] 无 User-Agent:") +try: + r = urllib.request.urlopen("https://gfil-lab.com/", timeout=10) + print(f" Status: {r.status}, URL: {r.url}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") + print(f" Server: {e.headers.get('Server','?')}") + print(f" CF-Ray: {e.headers.get('CF-Ray','?')}") + body = e.read().decode()[:300] + print(f" Body: {body}") + +# 2. With browser UA +print("\n[2] 浏览器 User-Agent:") +try: + req = urllib.request.Request("https://gfil-lab.com/", headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36" + }) + r = urllib.request.urlopen(req, timeout=10) + print(f" Status: {r.status}, URL: {r.url}") + print(f" Size: {len(r.read())} bytes") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") + body = e.read().decode()[:300] + print(f" Body: {body}") + +# 3. Googlebot UA +print("\n[3] Googlebot User-Agent:") +try: + req = urllib.request.Request("https://gfil-lab.com/", headers={ + "User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" + }) + r = urllib.request.urlopen(req, timeout=10) + print(f" Status: {r.status}, URL: {r.url}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") + body = e.read().decode()[:300] + print(f" Body: {body}") + +# 4. Bingbot UA +print("\n[4] Bingbot User-Agent:") +try: + req = urllib.request.Request("https://gfil-lab.com/", headers={ + "User-Agent": "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" + }) + r = urllib.request.urlopen(req, timeout=10) + print(f" Status: {r.status}, URL: {r.url}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") + body = e.read().decode()[:300] + print(f" Body: {body}") + +# 5. robots.txt +print("\n[5] robots.txt:") +try: + req = urllib.request.Request("https://gfil-lab.com/robots.txt", headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + print(f" Status: {r.status}") + print(f" Content: {content[:500]}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") + body = e.read().decode()[:300] + print(f" Body: {body}") + +# 6. Check CF headers for challenge type +print("\n[6] 检查响应头详情:") +try: + req = urllib.request.Request("https://gfil-lab.com/", headers={ + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" + }) + r = urllib.request.urlopen(req, timeout=10) + for h in ['Server', 'CF-Ray', 'CF-Cache-Status', 'X-Content-Type-Options', 'Content-Type']: + print(f" {h}: {r.headers.get(h, 'N/A')}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code}") + for h in ['Server', 'CF-Ray', 'CF-Cache-Status', 'Content-Type']: + print(f" {h}: {e.headers.get(h, 'N/A')}") + +# 7. HTTP (non-HTTPS) test +print("\n[7] HTTP (非HTTPS):") +try: + r = urllib.request.urlopen("http://gfil-lab.com/", timeout=10) + print(f" Status: {r.status}, URL: {r.url}") +except urllib.error.HTTPError as e: + print(f" HTTP {e.code} {e.reason}") +except Exception as e: + print(f" Error: {type(e).__name__}: {e}") diff --git a/final_audit.py b/final_audit.py new file mode 100644 index 0000000..6f9c9f8 --- /dev/null +++ b/final_audit.py @@ -0,0 +1,89 @@ +"""Final audit: verify all reviewer fixes are live""" +import urllib.request +import re, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +SITE = "https://blog.quant-view.xyz" + +def fetch(path): + try: + url = f"{SITE}/{path}" + r = urllib.request.urlopen(url, timeout=15) + return r.read().decode() + except Exception as e: + return f"ERROR: {e}" + +results = [] + +# 1. entity.html - zero github links, zero liudecai-one +html = fetch("tools/entity.html") +gh = len(re.findall(r'github\.com', html)) +l1 = html.count('liudecai-one') +results.append(("entity.html: GitHub links = 0", gh == 0)) +results.append(("entity.html: liudecai-one = 0", l1 == 0)) + +# 2. GitHub SEO article +html = fetch("github-seo-trading-tools.html") +has_npm = 'npmjs.com' in html +has_gitlab = 'gitlab.com/liudecai110' in html +has_degithub = 'de-GitHub' in html +# PyPI should appear in After block exactly once (not duplicated) +after_block = re.search(r'// After:.*?]', html, re.DOTALL) +if after_block: + after_text = after_block.group() + pypi_in_after = after_text.count('https://pypi.org/project/gfil-calculators/') + npm_in_after = 'npmjs.com' in after_text + gitlab_in_after = 'gitlab.com' in after_text +else: + pypi_in_after = -1 + npm_in_after = False + gitlab_in_after = False +results.append(("article19: After block PyPI count = 1", pypi_in_after == 1)) +results.append(("article19: After block has npm", npm_in_after)) +results.append(("article19: After block has GitLab", gitlab_in_after)) +results.append(("article19: Mistake#1 updated (de-GitHub)", has_degithub)) +results.append(("article19: overall has npm link", has_npm)) +results.append(("article19: overall has GitLab link", has_gitlab)) + +# 3. Footer - English pages use Telegram, not WeChat +html_en = fetch("") +html_zh = fetch("zh/") +en_no_wechat = 'LDP161109' not in html_en +en_has_telegram = '@GFIL_Trading' in html_en or 'Telegram' in html_en +zh_has_wechat = 'LDP161109' in html_zh +results.append(("EN index: NO WeChat/QQ", en_no_wechat)) +results.append(("EN index: HAS Telegram", en_has_telegram)) +results.append(("ZH index: HAS WeChat/QQ", zh_has_wechat)) + +# 4. research.html - "GitHub" label fixed to "PyPI" +html = fetch("tools/research.html") +# Check that PyPI link text is NOT "GitHub" +pypi_with_github_label = bool(re.search(r'pypi\.org[^>]*>GitHub', html)) +results.append(("research.html: no mislabeled GitHub->PyPI", not pypi_with_github_label)) + +# 5. gfil-faq.html - has GitLab, no "GitHub" label on PyPI +html = fetch("tools/gfil-faq.html") +has_gitlab_faq = 'gitlab.com/liudecai110' in html +results.append(("gfil-faq.html: has GitLab link", has_gitlab_faq)) + +# 6. media.html - zero github links +html = fetch("tools/media.html") +gh = len(re.findall(r'github\.com', html)) +results.append(("media.html: GitHub links = 0", gh == 0)) + +# Print results +print("=" * 70) +print("FINAL AUDIT: Reviewer Fixes Verification") +print("=" * 70) +all_pass = True +for label, passed in results: + status = "PASS" if passed else "FAIL" + if not passed: + all_pass = False + print(f" [{status}] {label}") + +print() +if all_pass: + print("ALL CHECKS PASSED") +else: + print("SOME CHECKS FAILED") diff --git a/final_check.py b/final_check.py new file mode 100644 index 0000000..1376e32 --- /dev/null +++ b/final_check.py @@ -0,0 +1,25 @@ +import urllib.request, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +for domain in ["gfil-lab.com", "gfil-intel.xyz", "blog.quant-view.xyz"]: + try: + url = "https://" + domain + "/robots.txt?nocache=1" + req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + gb = "GPTBot" in content + cb = "ClaudeBot" in content + pb = "PerplexityBot" in content + sm = "Sitemap" in content + print(domain + ": " + str(r.status) + " | " + str(len(content)) + " chars | GPTBot=" + str(gb) + " ClaudeBot=" + str(cb) + " PerplexityBot=" + str(pb) + " Sitemap=" + str(sm)) + except Exception as e: + print(domain + ": " + str(e)) + +# Also check homepage still works +for domain in ["gfil-lab.com", "gfil-intel.xyz"]: + try: + req = urllib.request.Request("https://" + domain + "/", headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + print(domain + " homepage: " + str(r.status) + " OK") + except Exception as e: + print(domain + " homepage: ERROR " + str(e)) diff --git a/fix_and_verify.py b/fix_and_verify.py new file mode 100644 index 0000000..65adbf3 --- /dev/null +++ b/fix_and_verify.py @@ -0,0 +1,125 @@ +import paramiko, sys, io, time +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +# Fix script that modifies BOTH server blocks +FIX_SCRIPT = r''' +import shutil + +# Fix the "default" config (server_name: gfil-lab.com - this is the one that actually serves gfil-lab.com) +src = "/etc/nginx/sites-enabled/default" +bak = "/etc/nginx/sites-enabled/default.bak.before-robots" +shutil.copy2(src, bak) + +with open(src, "r") as f: + content = f.read() + +robots_block = " location = /robots.txt {\n alias /var/www/gfil-lab/robots.txt;\n default_type text/plain;\n }\n\n" + +if "robots.txt" not in content: + content = content.replace(" location / {", robots_block + " location / {") + with open(src, "w") as f: + f.write(content) + print("INSERTED into default") +else: + print("ALREADY in default") +''' +script_path = r"C:\Users\thinkpad\AppData\Local\Temp\kilo\fix_default.py" +with open(script_path, "w") as f: + f.write(FIX_SCRIPT) + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +sftp = jd.open_sftp() +sftp.put(script_path, "/tmp/fix_default.py") +sftp.close() + +# SCP and run +scp_cmd = "sshpass -p '" + LAB_PASS + "' scp -o StrictHostKeyChecking=no /tmp/fix_default.py " + LAB_USER + "@" + LAB_HOST + ":/tmp/fix_default.py" +stdin, stdout, stderr = jd.exec_command(scp_cmd, timeout=15) +stdout.channel.recv_exit_status() + +run_cmd = "sshpass -p '" + LAB_PASS + "' ssh -o StrictHostKeyChecking=no " + LAB_USER + "@" + LAB_HOST + " 'python3 /tmp/fix_default.py && echo --- && cat /etc/nginx/sites-enabled/default && echo --- && nginx -t 2>&1 && (systemctl reload nginx && echo NGINX_OK) || (cp /etc/nginx/sites-enabled/default.bak.before-robots /etc/nginx/sites-enabled/default && nginx -t && systemctl reload nginx && echo ROLLBACK_OK) && rm -f /tmp/fix_default.py'" +stdin, stdout, stderr = jd.exec_command(run_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() + +time.sleep(2) + +# === STEP 2: Code verification === +print("\n=== STEP 2: CODE VERIFICATION ===") + +# 2a. External check +import urllib.request +all_pass = True +for domain in ["gfil-lab.com", "gfil-intel.xyz"]: + # Homepage + try: + req = urllib.request.Request("https://" + domain + "/", headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + print(f" {domain} homepage: {r.status} OK") + except Exception as e: + print(f" {domain} homepage: ERROR {e}") + all_pass = False + + # robots.txt + try: + req = urllib.request.Request("https://" + domain + "/robots.txt", headers={"User-Agent": "Mozilla/5.0"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + checks = { + "GPTBot": "GPTBot" in content, + "ClaudeBot": "ClaudeBot" in content, + "PerplexityBot": "PerplexityBot" in content, + "Sitemap": "Sitemap:" in content, + "Size>500": len(content) > 500, + } + status = "PASS" if all(checks.values()) else "FAIL" + if not all(checks.values()): + all_pass = False + print(f" {domain} robots.txt: [{status}] {dict(checks)}") + except Exception as e: + print(f" {domain} robots.txt: ERROR {e}") + all_pass = False + +# Googlebot +try: + req = urllib.request.Request("https://gfil-lab.com/robots.txt", headers={"User-Agent": "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + print(f" Googlebot -> gfil-lab.com/robots.txt: {r.status}, GPTBot={'GPTBot' in content}") +except Exception as e: + print(f" Googlebot check: ERROR {e}") + all_pass = False + +print() +if all_pass: + print("ALL CODE CHECKS PASSED") +else: + print("SOME CHECKS FAILED") + +# === STEP 3: Human checklist === +print("\n" + "=" * 60) +print("STEP 3: HUMAN OBSERVATION CHECKLIST") +print("=" * 60) +print() +print("Please manually verify:") +print(" 1. https://gfil-lab.com/ -> loads normally") +print(" 2. https://gfil-lab.com/robots.txt -> shows FULL version with GPTBot/ClaudeBot/PerplexityBot") +print(" 3. https://gfil-intel.xyz/robots.txt -> shows robots.txt") +print(" 4. https://gfil-lab.com/login -> login page works") +print() +print("Rollback if needed:") +print(" ssh root@216.144.233.14") +print(" cp /etc/nginx/sites-enabled/default.bak.before-robots /etc/nginx/sites-enabled/default") +print(" cp /etc/nginx/sites-enabled/gfil.bak.before-robots /etc/nginx/sites-enabled/gfil") +print(" systemctl reload nginx") diff --git a/fix_sites_enabled.py b/fix_sites_enabled.py new file mode 100644 index 0000000..6c4a77f --- /dev/null +++ b/fix_sites_enabled.py @@ -0,0 +1,43 @@ +"""Remove broken backup file from sites-enabled, keep only clean gfil""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Remove the broken backup from sites-enabled (nginx loads ALL files in this dir) +rm -f /etc/nginx/sites-enabled/gfil.bak.broken + +# Verify only clean files remain +echo "=== sites-enabled contents ===" +ls -la /etc/nginx/sites-enabled/ + +# Show current gfil config +echo "=== Current gfil config ===" +cat /etc/nginx/sites-enabled/gfil + +# Test +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "SUCCESS: Nginx restored and reloaded" +else + echo "FAILED" +fi + +# Final check - site responding +curl -s -o /dev/null -w "HTTP status" http://localhost/ 2>/dev/null +'""" +stdin, stdout, stderr = jd.exec_command(cmd, timeout=30) +print(stdout.read().decode()) +jd.close() diff --git a/npm_package/README.md b/npm_package/README.md new file mode 100644 index 0000000..6e0bce4 --- /dev/null +++ b/npm_package/README.md @@ -0,0 +1,36 @@ +# Trading Calculators + +Free trading calculators for forex, gold, and crypto traders. + +## Installation +```bash +npm install @liudecai-one/trading-calculators-free +``` + +## Usage +```js +const { positionSize, pipValue, fibonacci } = require('@liudecai-one/trading-calculators-free'); + +// Position size: account=$10K, risk=1%, stop=20 pips +const size = positionSize(10000, 1, 20); +console.log({ size }); // 0.5 lots + +// Fibonacci retracement levels +const fib = fibonacci(1.1200, 1.1000); +console.log({ fib }); +``` + +## Free Web Tools +- [Position Size Calculator](https://blog.quant-view.xyz/tools/position-size-calculator.html) +- [Pip Value Calculator](https://blog.quant-view.xyz/tools/pip-calculator.html) +- [Margin Calculator](https://blog.quant-view.xyz/tools/margin-calculator.html) +- [Fibonacci Calculator](https://blog.quant-view.xyz/tools/fibonacci-calculator.html) +- [ATR Calculator](https://blog.quant-view.xyz/tools/atr-calculator.html) +- [Risk-Reward Calculator](https://blog.quant-view.xyz/tools/risk-reward-calculator.html) +- [Drawdown Calculator](https://blog.quant-view.xyz/tools/drawdown-calculator.html) + +## More Resources +- [Trading Blog](https://blog.quant-view.xyz) +- [GFIL Terminal](http://gfil-intel.xyz/) +- [Telegram Community](https://t.me/GFIL_Trading) +- [Discord Server](https://discord.gg/GMmMCD4MCr) diff --git a/npm_package/index.js b/npm_package/index.js new file mode 100644 index 0000000..4aa15c6 --- /dev/null +++ b/npm_package/index.js @@ -0,0 +1,29 @@ +/** + * Free Trading Calculators + * Web versions: https://blog.quant-view.xyz/tools/ + * Telegram: https://t.me/GFIL_Trading + * Discord: https://discord.gg/GMmMCD4MCr + */ + +function positionSize(accountBalance, riskPercent, stopLossPips, pipValue = 10) { + const riskAmount = accountBalance * (riskPercent / 100); + return parseFloat((riskAmount / (stopLossPips * pipValue)).toFixed(2)); +} + +function pipValue(currencyPair, lotSize = 1) { + const rates = { "EURUSD": 10, "GBPUSD": 10, "USDJPY": 9.3, "XAUUSD": 10 }; + return rates[currencyPair] * lotSize || 10; +} + +function fibonacci(high, low) { + const diff = high - low; + return { + "0.236": high - diff * 0.236, + "0.382": high - diff * 0.382, + "0.500": high - diff * 0.500, + "0.618": high - diff * 0.618, + "0.786": high - diff * 0.786, + }; +} + +module.exports = { positionSize, pipValue, fibonacci }; diff --git a/npm_package/package.json b/npm_package/package.json new file mode 100644 index 0000000..c7fc81f --- /dev/null +++ b/npm_package/package.json @@ -0,0 +1,22 @@ +{ + "name": "@liudecai-one/trading-calculators-free", + "version": "1.0.0", + "description": "Free trading calculators: position size, pip value, margin, Fibonacci, ATR. Web versions at blog.quant-view.xyz/tools/", + "main": "index.js", + "keywords": [ + "trading", + "forex", + "calculator", + "position-size", + "pip-calculator", + "margin-calculator", + "fibonacci", + "atr" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://gitlab.com/liudecai110/gfil-trading-calculators.git" + }, + "homepage": "https://blog.quant-view.xyz/tools/" +} \ No newline at end of file diff --git a/probe_nginx.py b/probe_nginx.py new file mode 100644 index 0000000..05924ca --- /dev/null +++ b/probe_nginx.py @@ -0,0 +1,57 @@ +"""Deep probe: find Nginx configs and deploy robots.txt correctly""" +import paramiko +import time + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +RN_HOST = "107.174.186.162" +RN_USER = "root" +RN_PASS = "liudapao2026" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# === gfil-lab.com (216.144.233.14) === +print("=== gfil-lab.com (216.144.233.14) ===") +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +echo "--- All Nginx configs ---" +ls -la /etc/nginx/sites-enabled/ 2>/dev/null +ls -la /etc/nginx/conf.d/ 2>/dev/null + +echo "--- Full Nginx config ---" +cat /etc/nginx/sites-enabled/default 2>/dev/null | head -50 +cat /etc/nginx/conf.d/default.conf 2>/dev/null | head -50 + +echo "--- Find gfil-lab server block ---" +grep -rl "gfil-lab\\|server_name" /etc/nginx/ 2>/dev/null + +echo "--- Nginx test ---" +nginx -T 2>/dev/null | grep -A3 "server_name" | head -30 +'""" +stdin, stdout, stderr = jd.exec_command(cmd, timeout=30) +print(stdout.read().decode()[:3000]) + +# === gfil-intel.xyz (RackNerd) === +print("\n=== gfil-intel.xyz (RackNerd) ===") +cmd2 = f"""sshpass -p '{RN_PASS}' ssh -o StrictHostKeyChecking=no {RN_USER}@{RN_HOST} ' +echo "--- gfil-intel Nginx config ---" +cat /etc/nginx/sites-available/gfil-mask 2>/dev/null | head -60 + +echo "--- Sites enabled ---" +ls -la /etc/nginx/sites-enabled/ 2>/dev/null + +echo "--- Check if gfil-mask is enabled ---" +ls -la /etc/nginx/sites-enabled/gfil-mask 2>/dev/null +'""" +stdin, stdout, stderr = jd.exec_command(cmd2, timeout=30) +print(stdout.read().decode()[:3000]) + +jd.close() diff --git a/purge_and_verify.py b/purge_and_verify.py new file mode 100644 index 0000000..1fedce0 --- /dev/null +++ b/purge_and_verify.py @@ -0,0 +1,58 @@ +import urllib.request, json, sys, io, time +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +# Purge gfil-intel.xyz zone +CF_TOKEN = "cfut_xRDcVVOTE9hLWxe8fFUG1Xgv7aQVtV0ytWFYZKimffcc10e3" +ZONE_ID = "a107f45f3d82e7209447cec396504d0b" + +headers = { + "Authorization": "Bearer " + CF_TOKEN, + "Content-Type": "application/json" +} + +purge_data = json.dumps({"files": ["https://gfil-intel.xyz/robots.txt"]}).encode("utf-8") +req = urllib.request.Request( + f"https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/purge_cache", + data=purge_data, + headers=headers, + method="POST" +) +r = urllib.request.urlopen(req, timeout=15) +result = json.loads(r.read().decode()) +print("gfil-intel.xyz purge:", result.get("success", False)) + +# Also purge blog.quant-view.xyz +ZONE_ID_BLOG = "3712ed1fcbd198c3ec6bd9758563a8d5" +purge_data2 = json.dumps({"files": ["https://blog.quant-view.xyz/robots.txt"]}).encode("utf-8") +req2 = urllib.request.Request( + f"https://api.cloudflare.com/client/v4/zones/{ZONE_ID_BLOG}/purge_cache", + data=purge_data2, + headers=headers, + method="POST" +) +r2 = urllib.request.urlopen(req2, timeout=15) +result2 = json.loads(r2.read().decode()) +print("blog.quant-view.xyz purge:", result2.get("success", False)) + +# Wait +print("\nWaiting 5 seconds...") +time.sleep(5) + +# Verify all 3 domains +print("\n=== VERIFICATION ===") +for domain in ["gfil-lab.com", "gfil-intel.xyz", "blog.quant-view.xyz"]: + try: + req = urllib.request.Request("https://" + domain + "/robots.txt", + headers={"User-Agent": "Mozilla/5.0", "Cache-Control": "no-cache", "Pragma": "no-cache"}) + r = urllib.request.urlopen(req, timeout=10) + content = r.read().decode() + checks = { + "GPTBot": "GPTBot" in content, + "ClaudeBot": "ClaudeBot" in content, + "PerplexityBot": "PerplexityBot" in content, + "SizeOK": len(content) > 100 + } + status = "PASS" if all(checks.values()) else "FAIL" + print(f" [{status}] {domain}: {len(content)} chars | {checks}") + except Exception as e: + print(f" [FAIL] {domain}: {e}") diff --git a/purge_cf_cache.py b/purge_cf_cache.py new file mode 100644 index 0000000..26b8c71 --- /dev/null +++ b/purge_cf_cache.py @@ -0,0 +1,85 @@ +"""Purge Cloudflare cache for robots.txt on gfil-lab.com""" +import urllib.request, json, sys, io +sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8") + +# gfil-lab.com Cloudflare zone +CF_TOKEN = "cfut_xRDcVVOTE9hLWxe8fFUG1Xgv7aQVtV0ytWFYZKimffcc10e3" +# Need zone ID for gfil-lab.com - get it first +headers = { + "Authorization": "Bearer " + CF_TOKEN, + "Content-Type": "application/json" +} + +# List zones to find gfil-lab.com zone ID +req = urllib.request.Request("https://api.cloudflare.com/client/v4/zones", headers=headers) +r = urllib.request.urlopen(req, timeout=15) +data = json.loads(r.read().decode()) +for z in data.get("result", []): + print(f" Zone: {z['name']} -> ID: {z['id']}") + +# Find gfil-lab.com zone +zone_id = None +for z in data.get("result", []): + if z["name"] == "gfil-lab.com": + zone_id = z["id"] + break + +if not zone_id: + print("ERROR: gfil-lab.com zone not found") + sys.exit(1) + +print(f"\nPurging cache for gfil-lab.com (zone: {zone_id})...") + +# Purge specific URLs +purge_data = json.dumps({ + "files": [ + "https://gfil-lab.com/robots.txt", + "https://gfil-intel.xyz/robots.txt" + ] +}).encode("utf-8") + +req = urllib.request.Request( + f"https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache", + data=purge_data, + headers=headers, + method="POST" +) +r = urllib.request.urlopen(req, timeout=15) +result = json.loads(r.read().decode()) +print(f" Purge result: {result.get('success', False)}") + +# Also try gfil-intel.xyz zone +zone_id2 = None +for z in data.get("result", []): + if z["name"] == "gfil-intel.xyz": + zone_id2 = z["id"] + break + +if zone_id2: + print(f"\nPurging cache for gfil-intel.xyz (zone: {zone_id2})...") + purge_data2 = json.dumps({"files": ["https://gfil-intel.xyz/robots.txt"]}).encode("utf-8") + req2 = urllib.request.Request( + f"https://api.cloudflare.com/client/v4/zones/{zone_id2}/purge_cache", + data=purge_data2, + headers=headers, + method="POST" + ) + r2 = urllib.request.urlopen(req2, timeout=15) + result2 = json.loads(r2.read().decode()) + print(f" Purge result: {result2.get('success', False)}") + +# Wait and verify +import time +print("\nWaiting 3 seconds for cache purge...") +time.sleep(3) + +print("\n=== Verification after purge ===") +import urllib.request as ur +for domain in ["gfil-lab.com", "gfil-intel.xyz"]: + try: + req = ur.Request("https://" + domain + "/robots.txt", headers={"User-Agent": "Mozilla/5.0", "Cache-Control": "no-cache"}) + r = ur.urlopen(req, timeout=10) + content = r.read().decode() + print(f" {domain}: {r.status} | {len(content)} chars | GPTBot={'GPTBot' in content} | ClaudeBot={'ClaudeBot' in content}") + except Exception as e: + print(f" {domain}: {e}") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..c59cac4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +paramiko>=3.0 diff --git a/restore_nginx.py b/restore_nginx.py new file mode 100644 index 0000000..79307bb --- /dev/null +++ b/restore_nginx.py @@ -0,0 +1,40 @@ +"""RESTORE: Copy clean config from sites-available/gfil""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Check sites-available/gfil content first, then copy it over +restore_cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +# Show the clean original +echo "=== sites-available/gfil (original clean) ===" +cat /etc/nginx/sites-available/gfil + +# Copy clean version over the broken one +cp /etc/nginx/sites-available/gfil /etc/nginx/sites-enabled/gfil + +# Test +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "RESTORED: Nginx reloaded with clean config" +else + echo "STILL FAILING after restore" +fi + +# Verify site is back +curl -s -o /dev/null -w "%{{http_code}}" http://localhost/ 2>/dev/null +'""" +stdin, stdout, stderr = jd.exec_command(restore_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() diff --git a/revert_nginx.py b/revert_nginx.py new file mode 100644 index 0000000..678fd45 --- /dev/null +++ b/revert_nginx.py @@ -0,0 +1,42 @@ +"""REVERT: Remove broken robots.txt block from gfil-lab.com Nginx config""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Remove ALL robots.txt related lines from the Nginx config +revert_cmd = """sshpass -p 'Kt9V72Tx2c48ChikKU' ssh -o StrictHostKeyChecking=no root@216.144.233.14 ' +# Remove the broken "n" line +sed -i "/^n location/d" /etc/nginx/sites-enabled/gfil +# Remove the robots.txt location block (3 lines: open, alias, close) +sed -i "/location = \/robots\.txt/,/}/d" /etc/nginx/sites-enabled/gfil +# Remove any leftover empty lines +sed -i "/^$/N;/^\\n$/d" /etc/nginx/sites-enabled/gfil + +# Verify config is clean +echo "=== Cleaned config ===" +cat /etc/nginx/sites-enabled/gfil + +# Test +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "Nginx reloaded OK - config reverted" +else + echo "STILL FAILING - showing full config" + cat -n /etc/nginx/sites-enabled/gfil +fi +'""" +stdin, stdout, stderr = jd.exec_command(revert_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() diff --git a/revert_v2.py b/revert_v2.py new file mode 100644 index 0000000..91ddaeb --- /dev/null +++ b/revert_v2.py @@ -0,0 +1,38 @@ +"""REVERT v2: Remove leftover lines 13-15 from gfil Nginx config""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +# Precisely remove the 3 leftover lines +revert_cmd = r"""sshpass -p 'Kt9V72Tx2c48ChikKU' ssh -o StrictHostKeyChecking=no root@216.144.233.14 ' +# Remove the 3 leftover lines from robots.txt block +sed -i "/alias \/var\/www\/gfil-lab\/robots\.txt/d" /etc/nginx/sites-enabled/gfil +sed -i "/default_type text\/plain/d" /etc/nginx/sites-enabled/gfil +# Remove the orphan closing brace on its own line after the comment line +sed -i "/^ }$/d" /etc/nginx/sites-enabled/gfil + +echo "=== Final config ===" +cat -n /etc/nginx/sites-enabled/gfil + +nginx -t 2>&1 +if [ $? -eq 0 ]; then + systemctl reload nginx + echo "SUCCESS: Nginx reloaded, config clean" +else + echo "STILL FAILING" +fi +'""" +stdin, stdout, stderr = jd.exec_command(revert_cmd, timeout=30) +print(stdout.read().decode()) +jd.close() diff --git a/submit_indexnow.py b/submit_indexnow.py new file mode 100644 index 0000000..b851c26 --- /dev/null +++ b/submit_indexnow.py @@ -0,0 +1,112 @@ +"""Submit new blog URLs to IndexNow (Bing, Yandex, Seznam)""" +import urllib.request +import json +import os +import sys + +SITE = "https://blog.quant-view.xyz" +KEY = "72aa77b68704abcfada4020ba81f7c5a" + +# New pages from the 54 variant generator +new_pages = [ + # Formula pages (10) + "tools/position-size-formula.html", + "tools/pip-value-formula.html", + "tools/kelly-criterion-formula.html", + "tools/atr-formula.html", + "tools/fibonacci-retracement-formula.html", + "tools/drawdown-formula.html", + "tools/margin-formula.html", + "tools/risk-reward-formula.html", + "tools/compound-interest-formula.html", + "tools/profit-factor-formula.html", + # Gold pages (6) + "tools/gold-atr-calculator.html", + "tools/gold-margin-calculator.html", + "tools/gold-drawdown-calculator.html", + "tools/xauusd-trading-guide.html", + "tools/gold-lot-size-calculator.html", + "tools/gold-spread-calculator.html", + # Forex pages (5) + "tools/forex-position-size-calculator.html", + "tools/forex-pip-calculator.html", + "tools/forex-margin-calculator.html", + "tools/forex-risk-calculator.html", + "tools/forex-lot-size-calculator.html", + # Forex pair guides (3) + "tools/eurusd-trading-guide.html", + "tools/gbpusd-trading-guide.html", + "tools/usdjpy-trading-guide.html", + # Crypto pages (5) + "tools/btc-position-size-calculator.html", + "tools/crypto-pip-calculator.html", + "tools/btc-margin-calculator.html", + "tools/eth-position-size-calculator.html", + "tools/crypto-risk-calculator.html", + # Pip variants (9) + "tools/pip-calculator-eurgbp.html", + "tools/pip-calculator-usdchf.html", + "tools/pip-calculator-solusd.html", + "tools/pip-calculator-dogeusd.html", + "tools/pip-calculator-adausd.html", + "tools/pip-calculator-bnbusd.html", + "tools/pip-calculator-dax40.html", + "tools/pip-calculator-sp500.html", + "tools/pip-calculator-ukoil.html", + # Index pages (4) + "tools/sp500-position-size-calculator.html", + "tools/nas100-position-size-calculator.html", + "tools/dax40-position-size-calculator.html", + "tools/index-margin-calculator.html", + # Educational (6) + "tools/risk-management-guide.html", + "tools/how-to-calculate-pip-value.html", + "tools/how-to-use-atr-for-stop-loss.html", + "tools/kelly-criterion-explained.html", + "tools/drawdown-recovery-guide.html", + "tools/margin-call-prevention.html", + # Comparison (3) + "tools/tradingview-vs-mt5.html", + "tools/ctrader-vs-mt5.html", + "tools/best-free-trading-tools.html", + # Account size variants (3) + "tools/position-size-calculator-100000-dollar-account.html", + "tools/position-size-calculator-20000-dollar-account.html", + "tools/position-size-calculator-3000-dollar-account.html", + # New articles + "position-size-calculator-guide.html", + "gold-trading-2026-guide.html", + "ssh-tunnel-deployment-china.html", + "github-seo-trading-tools.html", + "gold-pip-value-calculator-wrong.html", + # Sitemap + "sitemap.xml", +] + +urls = [f"{SITE}/{p}" for p in new_pages] +print(f"Submitting {len(urls)} URLs to IndexNow...") + +payload = json.dumps({ + "host": "blog.quant-view.xyz", + "key": KEY, + "keyLocation": f"{SITE}/{KEY}.txt", + "urlList": urls +}).encode("utf-8") + +engines = [ + ("Bing", "https://www.bing.com/indexnow"), + ("Yandex", "https://yandex.com/indexnow"), + ("Seznam", "https://search.seznam.cz/indexnow"), +] + +for name, endpoint in engines: + try: + req = urllib.request.Request(endpoint, data=payload, headers={"Content-Type": "application/json"}) + resp = urllib.request.urlopen(req, timeout=30) + print(f" {name}: HTTP {resp.status}") + except urllib.error.HTTPError as e: + print(f" {name}: HTTP {e.code} ({e.reason})") + except Exception as e: + print(f" {name}: Error - {e}") + +print("Done!") diff --git a/verify_nginx.py b/verify_nginx.py new file mode 100644 index 0000000..87a199f --- /dev/null +++ b/verify_nginx.py @@ -0,0 +1,46 @@ +"""Verify Nginx config: compare sites-enabled/gfil vs sites-available/gfil""" +import paramiko + +JD_HOST = "111.228.37.165" +JD_USER = "root" +JD_PASS = "Liudecai110" + +LAB_HOST = "216.144.233.14" +LAB_USER = "root" +LAB_PASS = "Kt9V72Tx2c48ChikKU" + +jd = paramiko.SSHClient() +jd.set_missing_host_key_policy(paramiko.AutoAddPolicy()) +jd.connect(JD_HOST, port=22, username=JD_USER, password=JD_PASS, + timeout=20, banner_timeout=60, allow_agent=False, look_for_keys=False) + +cmd = f"""sshpass -p '{LAB_PASS}' ssh -o StrictHostKeyChecking=no {LAB_USER}@{LAB_HOST} ' +echo "=== Diff: sites-enabled vs sites-available ===" +diff /etc/nginx/sites-enabled/gfil /etc/nginx/sites-available/gfil && echo "IDENTICAL" || echo "DIFFERENT" + +echo "" +echo "=== sites-available/gfil (original, date) ===" +ls -la /etc/nginx/sites-available/gfil +stat /etc/nginx/sites-available/gfil | grep -i modify + +echo "" +echo "=== sites-enabled/gfil (current, date) ===" +ls -la /etc/nginx/sites-enabled/gfil +stat /etc/nginx/sites-enabled/gfil | grep -i modify + +echo "" +echo "=== Full current config ===" +cat /etc/nginx/sites-enabled/gfil + +echo "" +echo "=== Nginx status ===" +nginx -t 2>&1 +systemctl status nginx --no-pager | head -5 + +echo "" +echo "=== Site response ===" +curl -s -o /dev/null -w "HTTP_code:%{{http_code}} Size:%{{size_download}}" http://localhost/ 2>/dev/null +'""" +stdin, stdout, stderr = jd.exec_command(cmd, timeout=30) +print(stdout.read().decode()) +jd.close()