{{ t.text }}

OSINT Platform

{{ loginError }}
OSINT Tool
Monitoring
Manual Scrape
Analysis & Reports
System
{{ username }}

{{ currentTabName }}

Interactive Dashboard
Processing Dataset...

Select a database and click Generate Dashboard.

Total Posts

{{ statsData.total_posts.toLocaleString() }}

Total Views

{{ statsData.total_views.toLocaleString() }}

Channels

{{ statsData.channels.length }}

Posting Timeline

AI Sentiment

No sentiment data.

Regex Categories

No Regex categories found.

AI Categories

No AI categories found.

Channel Performance & Sentiment

Channel Name Posts Views Pos Neg Neu
{{ c.label }} {{ c.posts.toLocaleString() }} {{ c.views.toLocaleString() }} {{ c.Positive || 0 }} {{ c.Negative || 0 }} {{ c.Neutral || 0 }}

Comparison: {{ compareResult.db_a }} vs {{ compareResult.db_b }}

DB A Rows

{{ compareResult.rows_a.toLocaleString() }}

DB B Rows

{{ compareResult.rows_b.toLocaleString() }}

New in B

+{{ compareResult.new_in_b.toLocaleString() }}

Sentiment Shift

{{ s.label }}

{{ s.delta > 0 ? '+' : '' }}{{ s.delta }}%

New Channels in B

{{ compareResult.new_channels.join(', ') }}

Generated Charts ({{ chartBuilderResults.length }}) Download All (ZIP)
Generating charts...
{{ c.title }}
Select a database file and click Generate.
{{ chartPreview.title }}
Vector Preview
Rendering Graphic...
Select settings and click Render.
Report
{{ insightStatus || 'Transmitting Context to LLM...' }}
Waiting for directive.
Database Viewer
Showing {{ dbViewerPage * dbViewerPageSize + 1 }}-{{ Math.min((dbViewerPage+1) * dbViewerPageSize, dbViewerFilteredRows.length) }} of {{ dbViewerFilteredRows.length }} rows ({{ dbViewerData.rows.length }} total)
{{ dbViewerPage + 1 }} / {{ dbViewerTotalPages || 1 }}
# {{ col }}
{{ dbViewerPage * dbViewerPageSize + rIdx + 1 }}
{{ row[cIdx] }}

Select a database and click Load to view data.

Double-click any cell to edit it.

API Keys & Provider Settings

General

All schedule times (scrape, enrich, digest, etc.) use this timezone.

API keys are stored in your user profile on the server. They are never shared between accounts.

Google Gemini

Get a key from Google AI Studio

OpenRouter

FREE MODELS

Some models have mandatory reasoning. This hides it from the response to save tokens.

Get a key from openrouter.ai/keys. Free models available!

OpenAI

Get a key from platform.openai.com

Telegram Sessions for Monitoring

Add multiple Telegram sessions to distribute channel scraping and avoid rate limits. Channels are split evenly across active sessions.

Session {{ sidx + 1 }}

Monitoring Schedule

All times in UTC

When to scrape all channels. E.g.: 02:00,06:00,10:00,14:00,18:00,22:00

When to AI-enrich new posts (sentiment, categories). Set ~30 min after scrape.

When to generate AI summaries for all topics. Set before digest times.

When to scan for coordinated information attacks.

Restart the app for schedule changes to take effect.

Global AI Models

Default models for all topics (can be overridden per-topic)

Enrichment model is used for sentiment analysis and categorization. Summary model is used for AI summaries and deep analysis.

Data Retention

Older posts will be automatically deleted during scheduled cleanup.

Change My Password

Users

Admin cannot view other users' API keys or files — only manage accounts. Passwords are hashed; API keys are encrypted at rest.

Create new user

A new user gets a clone of admin's non-secret settings and a copy of admin's uploaded CSV files. API keys are NOT copied.

{{ u.role }} {{ u.username }} {{ u.created_at?.slice(0, 10) }}
No users yet — click Reload.
Cloud Storage
Filename Size Date Actions
{{ file.name }} {{ formatBytes(file.size) }} {{ new Date(file.date * 1000).toLocaleString() }}
Your secure folder is empty.
Documentation & Guides

HOW TO GET TELEGRAM CREDENTIALS

  1. Go to my.telegram.org and log in with your phone number.
  2. Click on 'API development tools'.
  3. Create a new application (you can enter any random App Title and Short Name).
  4. Copy the App api_id and App api_hash into the Setup tab.
  5. First time you run the scraper, a popup will ask for the Telegram code sent to your app.

SERVER BLOCKED? (STRING SESSION BYPASS)

If you never receive a code, your VPS IP is blocked by Telegram's anti-bot system. Bypass it using a local QR code login:

  1. Install Python on your local Windows/Mac computer.
  2. Run pip install telethon qrcode in your terminal.
  3. Run the Python script below on your PC. It will print a QR code in your terminal.
  4. Open Telegram on your phone → SettingsDevicesLink Desktop Device, and scan the QR.
  5. Click the code box below (it will auto-select all), copy the generated 1BJWap... string, and paste it into the Setup tab.
import asyncio
import qrcode
from telethon import TelegramClient
from telethon.sessions import StringSession
from telethon.errors import SessionPasswordNeededError

api_id = YOUR_API_ID # CHANGE THIS
api_hash = 'YOUR_API_HASH' # CHANGE THIS

async def main():
    client = TelegramClient(StringSession(), api_id, api_hash)
    await client.connect()
    if not await client.is_user_authorized():
        qr_login = await client.qr_login()
        qr = qrcode.QRCode(border=1)
        qr.add_data(qr_login.url)
        qr.make(fit=True)
        qr.print_ascii(invert=True)
        try:
            await qr_login.wait()
        except SessionPasswordNeededError:
            password = input("Please type your Telegram 2FA Password: ")
            await client.sign_in(password=password)
    print("\n--- YOUR SESSION STRING (COPY THIS) ---")
    print(client.session.save())

if __name__ == "__main__":
    asyncio.run(main())

REGEX (REGULAR EXPRESSIONS) TUTORIAL

Regex is a powerful way to search for specific words or patterns.

  • Exact word match: \bapple\b (Matches 'apple' but not 'apples')
  • Ignore Capitalization: (?i)apple (Matches 'Apple', 'aPpLe', 'apple')
  • OR operator: apple|banana|orange (Matches any of those words)
  • Find a hashtag: #ukraine
  • Contains numbers: \d+

Examples for the Categorizer box:

Politics | (?i)biden|trump|putin|zelensky
Military | (?i)\b(tank|missile|drone)\b
Economy  | (?i)inflation|taxes|\$

AI ANALYSIS (GEMINI)

  1. Get a free API key from Google AI Studio: https://aistudio.google.com/app/apikey
  2. Because AI reads the text conceptually, you don't need exact words. Just describe what 'Positive' means to you, or describe your desired categories in plain English!
  3. Note: Free tier strictly limits processing to ~15 messages per minute. The scraper automatically throttles itself to respect this limit.
Monitoring Dashboard
Collection Log Running...
[{{ log.time }}] {{ log.text }}

{{ t.name }}

{{ t.active_alerts }} alerts
{{ dashPeriodLabel }}
{{ t.period_posts || 0 }}
Total
{{ t.total_posts || 0 }}
Channels
{{ t.channel_count || 0 }}
Sentiment
{{ t.period_sentiment != null ? (t.period_sentiment > 0 ? '+' : '') + Number(t.period_sentiment).toFixed(2) : 'N/A' }}

No topics configured yet

Go to the Topics tab to create your first monitoring topic.

Period:

Posts by Topic ({{ dashPeriodLabel }})

Sentiment Overview ({{ dashPeriodLabel }})

Background Activity

{{ monDashboard.timezone }}
Scrapes
{{ formatTzTime(log.started_at) }} {{ log.channels_scraped }} ch / {{ log.posts_new }} matched
AI Summaries
{{ formatTzTime(r.created_at) }} {{ r.title }}
No background activity yet

Channel Analytics

{{ ch.username.slice(0,14) }} {{ (ch.total_views||0).toLocaleString() }}

Size = views. Green = positive, Red = negative, Gray = neutral. Top 40 channels.

Channel Category Posts Views + ~ -
@{{ ch.username }}{{ ch.title?.slice(0,25) }} {{ ch.category || '' }} {{ ch.post_count }} {{ (ch.total_views||0).toLocaleString() }} {{ ch.positive || 0 }} {{ ch.neutral || 0 }} {{ ch.negative || 0 }}
Monitoring Topics
{{ newTopic._regexError }}
Valid pattern
Captures ALL posts from channels matching the category/type filters below. Ideal for monitoring partners or specific channel groups.

Leave all unchecked = process all channels

{{ t.name }} ALL
{{ t.stats?.total_posts || 0 }} posts • {{ (t.channels||[]).length }} channels
No topics yet

{{ selectedTopicDash.topic.name }} All Posts Keyword

{{ selectedTopicDash.topic.description }}

{{ selectedTopicDash.topic.regex_display || selectedTopicDash.topic.regex_pattern }}

Total Posts
{{ selectedTopicDash.stats.total_posts || 0 }}
Channels
{{ selectedTopicDash.stats.unique_channels || 0 }}
Avg Views
{{ Math.round(selectedTopicDash.stats.avg_views || 0).toLocaleString() }}
Positive / Negative
{{ selectedTopicDash.stats.positive || 0 }} / {{ selectedTopicDash.stats.negative || 0 }}
Coordinated
{{ selectedTopicDash.stats.coordinated || 0 }}

AI Summary

to
{{ formatTzTime(topicSummaryMeta.created_at) }} {{ topicSummaryMeta.title }}
No summary yet. Select a period and click "Generate".

Post Volume Over Time

Sentiment Over Time

AI Categories

Regex Categories

Topic Seismograph

SVG

Posts ({{ filteredSortedPosts.length }} / {{ selectedTopicDash.recent_posts.length }})

Date Channel Sent. AI Cat. Regex Cat. Text Views AI
Clear filters
{{ p.posted_at?.slice(5,16) }} {{ p.channel_username }}
{{ p.text?.slice(0, 150) }}
{{ (p.views||0).toLocaleString() }}
Page {{ postPage + 1 }} of {{ postTotalPages }} ({{ filteredSortedPosts.length }} posts)

Channel Analytics

{{ ch.username.slice(0,12) }} {{ (ch.total_views||0).toLocaleString() }}
Channel Posts Views + -
@{{ ch.username }} {{ ch.post_count }} {{ (ch.total_views||0).toLocaleString() }} {{ ch.positive || 0 }} {{ ch.negative || 0 }}

Coordinated Post Groups

Group #{{ g.id }}: {{ g.post_count }} posts {{ g.detected_at }}
Similarity: {{ (g.avg_similarity * 100).toFixed(0) }}% • Span: {{ Math.round(g.time_span_minutes) }} min

Sentiment Spikes

{{ a.severity }} {{ a.attack_type }} • {{ a.post_count }} posts from {{ a.channel_count }} channels

Saved Reports

{{ r.created_at?.slice(0,10) }} {{ r.report_type }} {{ r.title }}

Select a topic from the left panel

Monitored Channels {{ monChannels.length }} total

Manage Categories & Types

{{ c }}
{{ t }}
{{ filteredChannels.length }} shown
Channel Category Type Subs Posts Sent. Active Actions
@{{ ch.username }}
{{ ch.title }}
{{ (ch.subscriber_count||0).toLocaleString() }} {{ ch.total_posts || 0 }} {{ ch.positive||0 }}/{{ ch.negative||0 }}

No channels added yet

Click Load Defaults to import 279 Ukrainian media channels, or add manually.

Alerts & Detections
Coordinated Post Detection

Posts with ≥ {{ detectionRules.coord_similarity }}% text similarity across ≥ {{ detectionRules.coord_min_channels }} channels within {{ detectionRules.coord_time_window }}h are flagged as coordinated.

Spike Detection

Flags a negative spike when volume is ≥ {{ detectionRules.attack_volume_mult }}× the {{ detectionRules.attack_lookback_days }}-day baseline AND negativity exceeds baseline + {{ detectionRules.attack_std_devs }}σ with ≥ {{ detectionRules.attack_min_posts }} same-sentiment posts from ≥ {{ detectionRules.attack_min_channels }} channels. A positive spike fires on the same volume rule when positivity > 70%.

Sentiment Spikes

{{ a.severity }} {{ spikeLabel(a.attack_type) }} {{ monTopics.find(t => t.id === a.topic_id)?.name || 'Topic #' + a.topic_id }}
{{ formatTzTime(a.period_start) }} — {{ formatTzTime(a.period_end) }} • {{ a.post_count }} posts from {{ a.channel_count }} channels

Coordinated Post Groups

Group #{{ g.id }}
{{ monTopics.find(t => t.id === g.topic_id)?.name || 'Topic #' + g.topic_id }} {{ formatTzTime(g.detected_at) }}
{{ g.post_count }} similar posts • {{ (g.avg_similarity * 100).toFixed(0) }}% avg similarity • {{ Math.round(g.time_span_minutes) }} min span

No active alerts

Run detection to scan for sentiment spikes and coordinated posts.

Loading archive…

Dismissed items and everything older than 7 days. Kept for reference — won't trigger digests or auto-alerts.

Archived Spikes ({{ archivedAttacks.length }})

{{ a.severity }} {{ spikeLabel(a.attack_type) }} {{ monTopics.find(t => t.id === a.topic_id)?.name || 'Topic #' + a.topic_id }}
{{ formatTzTime(a.detected_at) }}
{{ a.post_count }} posts / {{ a.channel_count }} channels • {{ a.period_start?.slice(0,10) }} — {{ a.period_end?.slice(0,10) }}

Archived Coordinated Groups ({{ archivedGroups.length }})

Group #{{ g.id }}
{{ monTopics.find(t => t.id === g.topic_id)?.name || 'Topic #' + g.topic_id }} {{ formatTzTime(g.detected_at) }}
{{ g.post_count }} posts • {{ (g.avg_similarity * 100).toFixed(0) }}% sim • {{ Math.round(g.time_span_minutes) }} min
{{ coordGroupPreview(g.summary) }}

Archive is empty

Dismissed or auto-archived items will appear here.

Group #{{ viewingGroup.id }} {{ viewingGroup.topic_name }}
{{ viewingGroup.post_count }} posts • {{ (viewingGroup.avg_similarity * 100).toFixed(0) }}% similarity • {{ Math.round(viewingGroup.time_span_minutes) }} min span
FIRST @{{ p.channel_username }} {{ p.channel_title }} {{ (p.subscriber_count/1000).toFixed(0) }}K subs
{{ p.sentiment_label || '—' }} {{ p.posted_at?.slice(5,16) }} {{ (p.views||0).toLocaleString() }} views
{{ p.text?.slice(0, 500) }}...
{{ p.post_url }} {{ p.ai_category }} {{ p.regex_categories }}
No posts found in this group
{{ viewingAttack.severity }} {{ spikeLabel(viewingAttack.attack_type) }} {{ viewingAttack.topic_name }}
{{ viewingAttack.post_count }} posts • {{ viewingAttack.channel_count }} channels
{{ formatTzTime(viewingAttack.period_start) }} — {{ formatTzTime(viewingAttack.period_end) }}
No AI analysis available for this attack.
@{{ p.channel_username }} {{ p.channel_title }} {{ (p.subscriber_count/1000).toFixed(0) }}K subs
{{ p.sentiment_label || '—' }} {{ p.posted_at?.slice(5,16) }} {{ (p.views||0).toLocaleString() }} views
{{ p.text?.slice(0, 500) }}...
t.me/{{ p.channel_username }}/{{ p.message_id }} {{ p.ai_category }} {{ p.regex_categories }}
No posts found in this attack window
Regex Filter Builder

Comma-separated. Post matches if it contains at least one of these words.

Comma-separated. Post must contain every single word listed here.

Comma-separated. Posts with any of these words will be excluded.

Examples & Recipes

Basic: Ukraine News
Any: ukraine, zelensky, kyiv, ukrainian
(?i)(ukraine|zelensky|kyiv|ukrainian)
Plus + Minus: Military without sports
Any: missile, drone, artillery, tank
Exclude: football, basketball, game, sport, league
(?i)(?!.*football)(?!.*basketball)(?!.*game)(?!.*sport)(?!.*league)(missile|drone|artillery|tank)
All Required: Corruption + Official
All of: corruption AND official
Any: arrest, scandal, investigation, bribe
(?i)(?=.*corruption)(?=.*official)(arrest|scandal|investigation|bribe)
Complex: Energy crisis without ads
All of: energy
Any: blackout, power cut, electricity, gas price, outage
Exclude: advertisement, promo, discount, buy now, order
(?i)(?=.*energy)(?!.*advertisement)(?!.*promo)(?!.*discount)(?!.*buy\ now)(?!.*order)(blackout|power\ cut|electricity|gas\ price|outage)
Cyrillic + Latin: Mixed language tracking
Any: sanctions, war, peace, negotiate
Exclude: crypto, bitcoin, nft
Tip: Add both Latin and Cyrillic variants in "Any" field
{{ regexBuilder.result || 'Click "Build Pattern" to generate' }}
{{ regexBuilder.test_result.match_count }} match(es) found
{{ regexBuilder.test_result.error }}
{{ m }}
Notification Bots
{{ bot.label || 'Bot #' + (bot.id || (idx+1)) }}
{{ bot.telegram_chat_id ? 'Chat: ' + bot.telegram_chat_id : 'Not configured' }} • Thread: {{ bot.message_thread_id }} PAUSED • {{ bot._topicIds && bot._topicIds.length ? bot._topicIds.length + ' topic(s)' : 'All topics' }}

This bot receives notifications for all topics.

min

No bots configured

Click "Add Bot" to set up notifications.

Digest Archive

Loading…
No digests sent yet. They appear here once the scheduler or a /digest command runs.
{{ d.sent_at?.slice(0, 16) }} {{ d.label || ('Bot #' + d.bot_id) }} Chat {{ d.chat_id || '—' }} {{ d.trigger_type || 'scheduled' }}
Digest {{ viewingDigest.sent_at }} {{ viewingDigest.label }}

Setup Guide

  1. Open Telegram, search for @BotFather
  2. Send /newbot and follow the prompts
  3. Copy the bot token and paste it into a bot card above
  4. For DMs: start a chat with the bot, send /start. Get your Chat ID from @userinfobot
  5. For groups: add the bot to the group as admin. Chat ID is the group ID (negative number)
  6. For forum topics: also enter the Thread ID (from the ?thread= URL param)
  7. You can use the same bot token for multiple destinations — just create multiple cards with different Chat IDs
@{{ viewingPost.channel_username }} {{ viewingPost.posted_at?.slice(0,16) }} {{ viewingPost.sentiment_label }} {{ (viewingPost.views||0).toLocaleString() }} views
{{ viewingPost.text }}
Open in Telegram
{{ viewingReport.title }} {{ viewingReport.created_at?.slice(0,10) }} {{ viewingReport.report_type }}
@{{ viewingChannelPosts.channel.username }} — {{ viewingChannelPosts.posts.length }} posts
{{ p.posted_at?.slice(0,16) }} {{ p.sentiment_label }} {{ p.ai_category }} {{ (p.views||0).toLocaleString() }} views
{{ p.text?.slice(0, 300) }}
No posts from this channel

Edit Topic

{{ editingTopic._regexError }}
Valid pattern
Captures all posts from channels matching category/type filters below.

Edit to define what "positive" means for this topic

Edit to define what "negative" means for this topic

{{ prompt.title }}

{{ prompt.desc }}