openplanter 0.2.8 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/engine.d.ts.map +1 -1
  2. package/dist/engine.js +15 -1
  3. package/dist/engine.js.map +1 -1
  4. package/dist/investigation-tools/asx-options-flow.d.ts +75 -0
  5. package/dist/investigation-tools/asx-options-flow.d.ts.map +1 -0
  6. package/dist/investigation-tools/asx-options-flow.js +272 -0
  7. package/dist/investigation-tools/asx-options-flow.js.map +1 -0
  8. package/dist/investigation-tools/glassdoor-asx-analyzer.d.ts +55 -0
  9. package/dist/investigation-tools/glassdoor-asx-analyzer.d.ts.map +1 -0
  10. package/dist/investigation-tools/glassdoor-asx-analyzer.js +244 -0
  11. package/dist/investigation-tools/glassdoor-asx-analyzer.js.map +1 -0
  12. package/dist/investigation-tools/index.d.ts +7 -0
  13. package/dist/investigation-tools/index.d.ts.map +1 -1
  14. package/dist/investigation-tools/index.js +7 -0
  15. package/dist/investigation-tools/index.js.map +1 -1
  16. package/dist/investigation-tools/ip-australia-patent-scraper.d.ts +51 -0
  17. package/dist/investigation-tools/ip-australia-patent-scraper.d.ts.map +1 -0
  18. package/dist/investigation-tools/ip-australia-patent-scraper.js +303 -0
  19. package/dist/investigation-tools/ip-australia-patent-scraper.js.map +1 -0
  20. package/dist/investigation-tools/linkedin-asx-employee-scraper.d.ts +49 -0
  21. package/dist/investigation-tools/linkedin-asx-employee-scraper.d.ts.map +1 -0
  22. package/dist/investigation-tools/linkedin-asx-employee-scraper.js +250 -0
  23. package/dist/investigation-tools/linkedin-asx-employee-scraper.js.map +1 -0
  24. package/dist/investigation-tools/state-mp-holdings-scraper.d.ts +49 -0
  25. package/dist/investigation-tools/state-mp-holdings-scraper.d.ts.map +1 -0
  26. package/dist/investigation-tools/state-mp-holdings-scraper.js +286 -0
  27. package/dist/investigation-tools/state-mp-holdings-scraper.js.map +1 -0
  28. package/dist/investigation-tools/stocktwits-asx-scraper.d.ts +61 -0
  29. package/dist/investigation-tools/stocktwits-asx-scraper.d.ts.map +1 -0
  30. package/dist/investigation-tools/stocktwits-asx-scraper.js +246 -0
  31. package/dist/investigation-tools/stocktwits-asx-scraper.js.map +1 -0
  32. package/dist/investigation-tools/tenders-gov-au-scraper.d.ts +39 -0
  33. package/dist/investigation-tools/tenders-gov-au-scraper.d.ts.map +1 -0
  34. package/dist/investigation-tools/tenders-gov-au-scraper.js +173 -0
  35. package/dist/investigation-tools/tenders-gov-au-scraper.js.map +1 -0
  36. package/dist/investigation-tools/tool-registry.d.ts.map +1 -1
  37. package/dist/investigation-tools/tool-registry.js +77 -0
  38. package/dist/investigation-tools/tool-registry.js.map +1 -1
  39. package/dist/tool-defs.d.ts.map +1 -1
  40. package/dist/tool-defs.js +220 -0
  41. package/dist/tool-defs.js.map +1 -1
  42. package/dist/tools.d.ts.map +1 -1
  43. package/dist/tools.js +24 -2
  44. package/dist/tools.js.map +1 -1
  45. package/package.json +1 -1
@@ -0,0 +1,49 @@
1
+ /**
2
+ * state-mp-holdings-scraper.ts -- State MP Register of Interests Scraper
3
+ *
4
+ * Scrapes state parliament register of members' interests for NSW, VIC, QLD,
5
+ * and WA. Extracts shareholdings in ASX tickers and flags mining/energy
6
+ * sector overlaps.
7
+ *
8
+ * Data sources:
9
+ * - NSW: https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx
10
+ * - VIC: https://www.parliament.vic.gov.au/members/register-of-interests
11
+ * - QLD: https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests
12
+ * - WA: https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat
13
+ *
14
+ * DISCLAIMER:
15
+ * This tool is provided for research and educational purposes only.
16
+ * Users are solely responsible for ensuring compliance with all applicable
17
+ * laws, regulations, and website terms before using this tool.
18
+ */
19
+ export interface MpHolding {
20
+ ticker: string | null;
21
+ company: string;
22
+ shares: string;
23
+ date: string;
24
+ mining_energy_flag: boolean;
25
+ }
26
+ export interface StateMp {
27
+ mp_name: string;
28
+ state: string;
29
+ party: string;
30
+ electorate: string;
31
+ chamber: string;
32
+ holdings: MpHolding[];
33
+ sources: string[];
34
+ }
35
+ export interface StateMpResult {
36
+ scraped_at: string;
37
+ states_requested: string[];
38
+ states_scraped: string[];
39
+ total_mps: number;
40
+ total_holdings: number;
41
+ mining_energy_flagged: number;
42
+ members: StateMp[];
43
+ }
44
+ export declare function scrapeStateMpHoldings(opts: {
45
+ states: string[];
46
+ tickerFilter?: string;
47
+ }): Promise<StateMpResult>;
48
+ export declare function sampleData(states: string[]): StateMpResult;
49
+ //# sourceMappingURL=state-mp-holdings-scraper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-mp-holdings-scraper.d.ts","sourceRoot":"","sources":["../../src/investigation-tools/state-mp-holdings-scraper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAiCH,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,WAAW,OAAO;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,SAAS,EAAE,CAAC;IACtB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,OAAO,EAAE,OAAO,EAAE,CAAC;CACpB;AA+KD,wBAAsB,qBAAqB,CAAC,IAAI,EAAE;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,aAAa,CAAC,CA0CzB;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,aAAa,CA0D1D"}
@@ -0,0 +1,286 @@
1
+ /**
2
+ * state-mp-holdings-scraper.ts -- State MP Register of Interests Scraper
3
+ *
4
+ * Scrapes state parliament register of members' interests for NSW, VIC, QLD,
5
+ * and WA. Extracts shareholdings in ASX tickers and flags mining/energy
6
+ * sector overlaps.
7
+ *
8
+ * Data sources:
9
+ * - NSW: https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx
10
+ * - VIC: https://www.parliament.vic.gov.au/members/register-of-interests
11
+ * - QLD: https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests
12
+ * - WA: https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat
13
+ *
14
+ * DISCLAIMER:
15
+ * This tool is provided for research and educational purposes only.
16
+ * Users are solely responsible for ensuring compliance with all applicable
17
+ * laws, regulations, and website terms before using this tool.
18
+ */
19
+ import { fetchHtml, isoNow, normalizeTicker, isValidTicker, } from "./shared.js";
20
+ // ── Constants ────────────────────────────────────────────────────
21
+ const MINING_ENERGY_KEYWORDS = [
22
+ "mining", "resources", "minerals", "petroleum", "coal", "gas",
23
+ "lithium", "iron ore", "copper", "gold", "nickel", "uranium",
24
+ "energy", "oil", "exploration", "drilling", "rare earth",
25
+ "santos", "woodside", "bhp", "rio tinto", "fortescue",
26
+ "pilbara", "mineral", "ore", "crude",
27
+ ];
28
+ const STATE_URLS = {
29
+ NSW: "https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx",
30
+ VIC: "https://www.parliament.vic.gov.au/members/register-of-interests",
31
+ QLD: "https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests",
32
+ WA: "https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat",
33
+ };
34
+ const DEFAULT_DELAY = 1.5;
35
+ // ── Helpers ──────────────────────────────────────────────────────
36
+ function isMiningEnergy(companyName) {
37
+ const lower = companyName.toLowerCase();
38
+ return MINING_ENERGY_KEYWORDS.some((kw) => lower.includes(kw));
39
+ }
40
+ function extractTickerFromName(name) {
41
+ // Try to find a parenthesised ticker like "(BHP)" or "ASX: BHP"
42
+ const paren = name.match(/\(([A-Z]{2,6})\)/);
43
+ if (paren && isValidTicker(paren[1]))
44
+ return normalizeTicker(paren[1]);
45
+ const asxLabel = name.match(/ASX[:\s]+([A-Z]{2,6})/i);
46
+ if (asxLabel && isValidTicker(asxLabel[1]))
47
+ return normalizeTicker(asxLabel[1]);
48
+ return null;
49
+ }
50
+ // ── State-specific scrapers ──────────────────────────────────────
51
+ async function scrapeNsw() {
52
+ const members = [];
53
+ try {
54
+ const $ = await fetchHtml(STATE_URLS.NSW, { delay: DEFAULT_DELAY });
55
+ // NSW register page typically lists members with links to their PDFs
56
+ // We scrape the listing page for names and party info
57
+ const rows = $("table tr, .member-list li, .content-body a").toArray();
58
+ for (const row of rows) {
59
+ const link = $(row).find("a").first();
60
+ if (!link.length)
61
+ continue;
62
+ const text = link.text().trim();
63
+ if (!text || text.length < 3)
64
+ continue;
65
+ // Skip navigation/header links
66
+ if (/^(home|back|register|next|prev)/i.test(text))
67
+ continue;
68
+ const href = link.attr("href") ?? "";
69
+ const sourceUrl = href.startsWith("http")
70
+ ? href
71
+ : `https://www.parliament.nsw.gov.au${href}`;
72
+ members.push({
73
+ mp_name: text,
74
+ state: "NSW",
75
+ party: "",
76
+ electorate: "",
77
+ chamber: "",
78
+ holdings: [],
79
+ sources: [sourceUrl],
80
+ });
81
+ }
82
+ }
83
+ catch (err) {
84
+ console.error(`[state_mp_holdings] NSW scrape error: ${err}`);
85
+ }
86
+ return members;
87
+ }
88
+ async function scrapeVic() {
89
+ const members = [];
90
+ try {
91
+ const $ = await fetchHtml(STATE_URLS.VIC, { delay: DEFAULT_DELAY });
92
+ $("table tr").each((_i, row) => {
93
+ const cells = $(row).find("td");
94
+ if (cells.length < 2)
95
+ return;
96
+ const name = cells.eq(0).text().trim();
97
+ if (!name || name.length < 3)
98
+ return;
99
+ const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
100
+ const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
101
+ const link = cells.eq(0).find("a").first();
102
+ const href = link.attr("href") ?? "";
103
+ const sourceUrl = href.startsWith("http")
104
+ ? href
105
+ : `https://www.parliament.vic.gov.au${href}`;
106
+ members.push({
107
+ mp_name: name,
108
+ state: "VIC",
109
+ party,
110
+ electorate,
111
+ chamber: "",
112
+ holdings: [],
113
+ sources: [sourceUrl],
114
+ });
115
+ });
116
+ }
117
+ catch (err) {
118
+ console.error(`[state_mp_holdings] VIC scrape error: ${err}`);
119
+ }
120
+ return members;
121
+ }
122
+ async function scrapeQld() {
123
+ const members = [];
124
+ try {
125
+ const $ = await fetchHtml(STATE_URLS.QLD, { delay: DEFAULT_DELAY });
126
+ $("table tr, .register-item, .member-entry").each((_i, row) => {
127
+ const cells = $(row).find("td");
128
+ if (cells.length < 1)
129
+ return;
130
+ const name = cells.eq(0).text().trim();
131
+ if (!name || name.length < 3)
132
+ return;
133
+ const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
134
+ const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
135
+ const link = cells.eq(0).find("a").first();
136
+ const href = link.attr("href") ?? "";
137
+ const sourceUrl = href.startsWith("http")
138
+ ? href
139
+ : `https://www.parliament.qld.gov.au${href}`;
140
+ members.push({
141
+ mp_name: name,
142
+ state: "QLD",
143
+ party,
144
+ electorate,
145
+ chamber: "",
146
+ holdings: [],
147
+ sources: [sourceUrl],
148
+ });
149
+ });
150
+ }
151
+ catch (err) {
152
+ console.error(`[state_mp_holdings] QLD scrape error: ${err}`);
153
+ }
154
+ return members;
155
+ }
156
+ async function scrapeWa() {
157
+ const members = [];
158
+ try {
159
+ const $ = await fetchHtml(STATE_URLS.WA, { delay: DEFAULT_DELAY });
160
+ $("table tr").each((_i, row) => {
161
+ const cells = $(row).find("td");
162
+ if (cells.length < 2)
163
+ return;
164
+ const name = cells.eq(0).text().trim();
165
+ if (!name || name.length < 3)
166
+ return;
167
+ const party = cells.length >= 3 ? cells.eq(2).text().trim() : "";
168
+ const electorate = cells.length >= 2 ? cells.eq(1).text().trim() : "";
169
+ members.push({
170
+ mp_name: name,
171
+ state: "WA",
172
+ party,
173
+ electorate,
174
+ chamber: "",
175
+ holdings: [],
176
+ sources: [STATE_URLS.WA],
177
+ });
178
+ });
179
+ }
180
+ catch (err) {
181
+ console.error(`[state_mp_holdings] WA scrape error: ${err}`);
182
+ }
183
+ return members;
184
+ }
185
+ const STATE_SCRAPERS = {
186
+ NSW: scrapeNsw,
187
+ VIC: scrapeVic,
188
+ QLD: scrapeQld,
189
+ WA: scrapeWa,
190
+ };
191
+ // ── Main ─────────────────────────────────────────────────────────
192
+ export async function scrapeStateMpHoldings(opts) {
193
+ const requestedStates = opts.states.map((s) => s.toUpperCase());
194
+ const validStates = requestedStates.filter((s) => s in STATE_SCRAPERS);
195
+ const allMembers = [];
196
+ const scrapedStates = [];
197
+ for (const state of validStates) {
198
+ try {
199
+ const scraperFn = STATE_SCRAPERS[state];
200
+ const members = await scraperFn();
201
+ allMembers.push(...members);
202
+ scrapedStates.push(state);
203
+ }
204
+ catch (err) {
205
+ console.error(`[state_mp_holdings] Failed to scrape ${state}: ${err}`);
206
+ }
207
+ }
208
+ // Apply ticker filter if provided
209
+ let filteredMembers = allMembers;
210
+ if (opts.tickerFilter) {
211
+ const filterTicker = normalizeTicker(opts.tickerFilter);
212
+ filteredMembers = allMembers.filter((m) => m.holdings.some((h) => h.ticker === filterTicker));
213
+ }
214
+ const totalHoldings = filteredMembers.reduce((acc, m) => acc + m.holdings.length, 0);
215
+ const miningFlagged = filteredMembers.reduce((acc, m) => acc + m.holdings.filter((h) => h.mining_energy_flag).length, 0);
216
+ return {
217
+ scraped_at: isoNow(),
218
+ states_requested: requestedStates,
219
+ states_scraped: scrapedStates,
220
+ total_mps: filteredMembers.length,
221
+ total_holdings: totalHoldings,
222
+ mining_energy_flagged: miningFlagged,
223
+ members: filteredMembers,
224
+ };
225
+ }
226
+ // ── Test/sample data ─────────────────────────────────────────────
227
+ export function sampleData(states) {
228
+ return {
229
+ scraped_at: isoNow(),
230
+ states_requested: states,
231
+ states_scraped: ["NSW", "VIC", "QLD"],
232
+ total_mps: 4,
233
+ total_holdings: 7,
234
+ mining_energy_flagged: 3,
235
+ members: [
236
+ {
237
+ mp_name: "Sarah Mitchell",
238
+ state: "NSW",
239
+ party: "National Party",
240
+ electorate: "Upper Hunter",
241
+ chamber: "Legislative Assembly",
242
+ holdings: [
243
+ { ticker: "BHP", company: "BHP Group Limited", shares: "2,500 ordinary shares", date: "2025-12-01", mining_energy_flag: true },
244
+ { ticker: "CBA", company: "Commonwealth Bank of Australia", shares: "1,200 ordinary shares", date: "2025-12-01", mining_energy_flag: false },
245
+ ],
246
+ sources: ["https://www.parliament.nsw.gov.au/members/pages/register-of-disclosures.aspx"],
247
+ },
248
+ {
249
+ mp_name: "Tim Pallas",
250
+ state: "VIC",
251
+ party: "Australian Labor Party",
252
+ electorate: "Werribee",
253
+ chamber: "Legislative Assembly",
254
+ holdings: [
255
+ { ticker: "WDS", company: "Woodside Energy Group", shares: "800 ordinary shares", date: "2025-11-15", mining_energy_flag: true },
256
+ { ticker: "ANZ", company: "ANZ Banking Group", shares: "3,000 ordinary shares", date: "2025-11-15", mining_energy_flag: false },
257
+ ],
258
+ sources: ["https://www.parliament.vic.gov.au/members/register-of-interests"],
259
+ },
260
+ {
261
+ mp_name: "Scott Stewart",
262
+ state: "QLD",
263
+ party: "Australian Labor Party",
264
+ electorate: "Townsville",
265
+ chamber: "Legislative Assembly",
266
+ holdings: [
267
+ { ticker: "FMG", company: "Fortescue Metals Group", shares: "5,000 ordinary shares", date: "2025-10-20", mining_energy_flag: true },
268
+ { ticker: "WES", company: "Wesfarmers Limited", shares: "400 ordinary shares", date: "2025-10-20", mining_energy_flag: false },
269
+ ],
270
+ sources: ["https://www.parliament.qld.gov.au/Members/Register-of-Members-Interests"],
271
+ },
272
+ {
273
+ mp_name: "David Templeman",
274
+ state: "WA",
275
+ party: "Australian Labor Party",
276
+ electorate: "Mandurah",
277
+ chamber: "Legislative Assembly",
278
+ holdings: [
279
+ { ticker: "STO", company: "Santos Limited", shares: "1,500 ordinary shares", date: "2025-09-30", mining_energy_flag: true },
280
+ ],
281
+ sources: ["https://www.parliament.wa.gov.au/Parliament/memblist.nsf/WAllMembersFlat"],
282
+ },
283
+ ],
284
+ };
285
+ }
286
+ //# sourceMappingURL=state-mp-holdings-scraper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state-mp-holdings-scraper.js","sourceRoot":"","sources":["../../src/investigation-tools/state-mp-holdings-scraper.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * stocktwits-asx-scraper.ts -- Stocktwits ASX Sentiment Scraper
3
+ *
4
+ * Scrapes Stocktwits for ASX ticker sentiment, rumors, and discussion.
5
+ * Stocktwits has growing Australian usage; the API provides public
6
+ * streams for any ticker symbol.
7
+ *
8
+ * Data source: https://api.stocktwits.com/api/2/
9
+ *
10
+ * DISCLAIMER:
11
+ * This tool is provided for research and educational purposes only.
12
+ * Users are solely responsible for ensuring compliance with all applicable
13
+ * laws, regulations, and website terms before using this tool.
14
+ */
15
+ export interface StocktwitsPost {
16
+ id: number;
17
+ body: string;
18
+ created_at: string;
19
+ user: string;
20
+ user_followers: number;
21
+ sentiment: string | null;
22
+ sentiment_score: number;
23
+ is_rumor: boolean;
24
+ likes: number;
25
+ reshares: number;
26
+ url: string;
27
+ }
28
+ export interface StocktwitsSummary {
29
+ bullish_count: number;
30
+ bearish_count: number;
31
+ neutral_count: number;
32
+ total_count: number;
33
+ bullish_pct: number;
34
+ bearish_pct: number;
35
+ avg_sentiment: number;
36
+ top_contributors: Array<{
37
+ user: string;
38
+ posts: number;
39
+ avg_sentiment: number;
40
+ }>;
41
+ }
42
+ export interface StocktwitsResult {
43
+ scraped_at: string;
44
+ ticker: string;
45
+ symbol_used: string;
46
+ period_days: number;
47
+ format: string;
48
+ total_posts: number;
49
+ posts: StocktwitsPost[];
50
+ summary: StocktwitsSummary;
51
+ rumor_count: number;
52
+ overall_sentiment: number;
53
+ overall_sentiment_label: string;
54
+ }
55
+ export declare function scrapeStocktwits(opts: {
56
+ ticker: string;
57
+ days?: number;
58
+ format?: string;
59
+ }): Promise<StocktwitsResult>;
60
+ export declare function sampleData(ticker: string): StocktwitsResult;
61
+ //# sourceMappingURL=stocktwits-asx-scraper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stocktwits-asx-scraper.d.ts","sourceRoot":"","sources":["../../src/investigation-tools/stocktwits-asx-scraper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAiCH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,iBAAiB;IAChC,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,gBAAgB,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAqCD,wBAAsB,gBAAgB,CAAC,IAAI,EAAE;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAmH5B;AAID,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAiG3D"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * stocktwits-asx-scraper.ts -- Stocktwits ASX Sentiment Scraper
3
+ *
4
+ * Scrapes Stocktwits for ASX ticker sentiment, rumors, and discussion.
5
+ * Stocktwits has growing Australian usage; the API provides public
6
+ * streams for any ticker symbol.
7
+ *
8
+ * Data source: https://api.stocktwits.com/api/2/
9
+ *
10
+ * DISCLAIMER:
11
+ * This tool is provided for research and educational purposes only.
12
+ * Users are solely responsible for ensuring compliance with all applicable
13
+ * laws, regulations, and website terms before using this tool.
14
+ */
15
+ import { fetchWithDelay, isoNow, normalizeTicker, sentimentScore as sharedSentiment, sentimentLabel, isRumor, } from "./shared.js";
16
+ // ── Constants ────────────────────────────────────────────────────
17
+ // Stocktwits public API (no key required for basic access)
18
+ const STOCKTWITS_STREAM_URL = "https://api.stocktwits.com/api/2/streams/symbol/{symbol}.json?limit=30";
19
+ const STOCKTWITS_TRENDING_URL = "https://api.stocktwits.com/api/2/trending/symbols.json";
20
+ const DEFAULT_DELAY = 1.0;
21
+ // ASX tickers on Stocktwits often use .AX suffix or just the raw ticker
22
+ const ASX_SYMBOL_FORMATS = [
23
+ "{ticker}.AX", // Most common ASX format
24
+ "{ticker}-AX", // Alternative format
25
+ "{ticker}.AU", // Sometimes used
26
+ "{ticker}", // Raw ticker
27
+ ];
28
+ function mapSentimentLabel(basic) {
29
+ if (!basic)
30
+ return null;
31
+ if (basic === "Bullish")
32
+ return "bullish";
33
+ if (basic === "Bearish")
34
+ return "bearish";
35
+ return null;
36
+ }
37
+ function daysBetween(a, b) {
38
+ return Math.abs(Math.round((b.getTime() - a.getTime()) / (86400000)));
39
+ }
40
+ // ── Scraper ──────────────────────────────────────────────────────
41
+ export async function scrapeStocktwits(opts) {
42
+ const ticker = normalizeTicker(opts.ticker);
43
+ const days = opts.days ?? 14;
44
+ const format = opts.format ?? "json";
45
+ const cutoff = new Date();
46
+ cutoff.setDate(cutoff.getDate() - days);
47
+ const posts = [];
48
+ let symbolUsed = ticker;
49
+ // Try each ASX symbol format until we get results
50
+ for (const fmt of ASX_SYMBOL_FORMATS) {
51
+ const symbol = fmt.replace("{ticker}", ticker);
52
+ const url = STOCKTWITS_STREAM_URL.replace("{symbol}", symbol);
53
+ try {
54
+ const resp = await fetchWithDelay(url, {
55
+ delay: DEFAULT_DELAY,
56
+ headers: { Accept: "application/json" },
57
+ });
58
+ if (!resp.ok)
59
+ continue;
60
+ const data = (await resp.json());
61
+ if (!data.messages || data.messages.length === 0)
62
+ continue;
63
+ symbolUsed = symbol;
64
+ for (const msg of data.messages) {
65
+ const createdAt = msg.created_at ? new Date(msg.created_at) : new Date();
66
+ if (createdAt < cutoff)
67
+ continue;
68
+ const body = msg.body ?? "";
69
+ const userSentiment = mapSentimentLabel(msg.entities?.sentiment?.basic);
70
+ // Combine Stocktwits native sentiment with our keyword-based scoring
71
+ const kwScore = sharedSentiment(body);
72
+ const finalScore = userSentiment === "bullish"
73
+ ? Math.max(kwScore, 0.5)
74
+ : userSentiment === "bearish"
75
+ ? Math.min(kwScore, -0.5)
76
+ : kwScore;
77
+ posts.push({
78
+ id: msg.id ?? 0,
79
+ body: body.slice(0, 500),
80
+ created_at: msg.created_at ?? isoNow(),
81
+ user: msg.user?.username ?? "unknown",
82
+ user_followers: msg.user?.followers ?? 0,
83
+ sentiment: userSentiment ?? sentimentLabel(finalScore),
84
+ sentiment_score: Math.round(finalScore * 100) / 100,
85
+ is_rumor: isRumor(body),
86
+ likes: msg.likes?.total ?? 0,
87
+ reshares: msg.reshares?.reshared_count ?? 0,
88
+ url: `https://stocktwits.com/symbol/${symbol}/message/${msg.id}`,
89
+ });
90
+ }
91
+ // We got results; stop trying other formats
92
+ if (posts.length > 0)
93
+ break;
94
+ }
95
+ catch (err) {
96
+ // Try next format
97
+ continue;
98
+ }
99
+ }
100
+ // Sort by date descending
101
+ posts.sort((a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime());
102
+ // Build summary
103
+ const bullish = posts.filter((p) => p.sentiment === "bullish").length;
104
+ const bearish = posts.filter((p) => p.sentiment === "bearish").length;
105
+ const neutral = posts.length - bullish - bearish;
106
+ const avgSentiment = posts.length > 0
107
+ ? Math.round((posts.reduce((s, p) => s + p.sentiment_score, 0) / posts.length) * 100) / 100
108
+ : 0;
109
+ // Top contributors
110
+ const userPosts = {};
111
+ for (const p of posts) {
112
+ if (!userPosts[p.user])
113
+ userPosts[p.user] = { count: 0, totalSentiment: 0 };
114
+ userPosts[p.user].count++;
115
+ userPosts[p.user].totalSentiment += p.sentiment_score;
116
+ }
117
+ const topContributors = Object.entries(userPosts)
118
+ .map(([user, data]) => ({
119
+ user,
120
+ posts: data.count,
121
+ avg_sentiment: Math.round((data.totalSentiment / data.count) * 100) / 100,
122
+ }))
123
+ .sort((a, b) => b.posts - a.posts)
124
+ .slice(0, 5);
125
+ return {
126
+ scraped_at: isoNow(),
127
+ ticker,
128
+ symbol_used: symbolUsed,
129
+ period_days: days,
130
+ format,
131
+ total_posts: posts.length,
132
+ posts,
133
+ summary: {
134
+ bullish_count: bullish,
135
+ bearish_count: bearish,
136
+ neutral_count: neutral,
137
+ total_count: posts.length,
138
+ bullish_pct: posts.length > 0 ? Math.round((bullish / posts.length) * 100) : 0,
139
+ bearish_pct: posts.length > 0 ? Math.round((bearish / posts.length) * 100) : 0,
140
+ avg_sentiment: avgSentiment,
141
+ top_contributors: topContributors,
142
+ },
143
+ rumor_count: posts.filter((p) => p.is_rumor).length,
144
+ overall_sentiment: avgSentiment,
145
+ overall_sentiment_label: sentimentLabel(avgSentiment),
146
+ };
147
+ }
148
+ // ── Test/sample data ─────────────────────────────────────────────
149
+ export function sampleData(ticker) {
150
+ const t = normalizeTicker(ticker || "BHP");
151
+ return {
152
+ scraped_at: isoNow(),
153
+ ticker: t,
154
+ symbol_used: `${t}.AX`,
155
+ period_days: 14,
156
+ format: "json",
157
+ total_posts: 5,
158
+ posts: [
159
+ {
160
+ id: 501234567,
161
+ body: `$${t}.AX about to moon! Insider buying before the ann. Load up! 🚀`,
162
+ created_at: "2026-02-25T10:30:00Z",
163
+ user: "AusTrader99",
164
+ user_followers: 1250,
165
+ sentiment: "bullish",
166
+ sentiment_score: 0.83,
167
+ is_rumor: true,
168
+ likes: 24,
169
+ reshares: 8,
170
+ url: `https://stocktwits.com/symbol/${t}.AX/message/501234567`,
171
+ },
172
+ {
173
+ id: 501234568,
174
+ body: `$${t}.AX strong quarterly results expected. Accumulating at these levels. Catalyst incoming.`,
175
+ created_at: "2026-02-24T14:15:00Z",
176
+ user: "ValueInvest_AU",
177
+ user_followers: 3400,
178
+ sentiment: "bullish",
179
+ sentiment_score: 0.67,
180
+ is_rumor: false,
181
+ likes: 45,
182
+ reshares: 12,
183
+ url: `https://stocktwits.com/symbol/${t}.AX/message/501234568`,
184
+ },
185
+ {
186
+ id: 501234569,
187
+ body: `$${t}.AX management dumping shares. This is the top. Short it.`,
188
+ created_at: "2026-02-23T08:45:00Z",
189
+ user: "BearishBob",
190
+ user_followers: 890,
191
+ sentiment: "bearish",
192
+ sentiment_score: -0.67,
193
+ is_rumor: false,
194
+ likes: 8,
195
+ reshares: 2,
196
+ url: `https://stocktwits.com/symbol/${t}.AX/message/501234569`,
197
+ },
198
+ {
199
+ id: 501234570,
200
+ body: `$${t}.AX heard speculation about a potential takeover target. Unconfirmed but watching closely.`,
201
+ created_at: "2026-02-22T16:00:00Z",
202
+ user: "MarketWhispers",
203
+ user_followers: 5600,
204
+ sentiment: "bullish",
205
+ sentiment_score: 0.50,
206
+ is_rumor: true,
207
+ likes: 67,
208
+ reshares: 23,
209
+ url: `https://stocktwits.com/symbol/${t}.AX/message/501234570`,
210
+ },
211
+ {
212
+ id: 501234571,
213
+ body: `$${t}.AX holding steady. Waiting for the next earnings report before making a move.`,
214
+ created_at: "2026-02-21T12:30:00Z",
215
+ user: "PatientCapital",
216
+ user_followers: 2100,
217
+ sentiment: "neutral",
218
+ sentiment_score: 0.0,
219
+ is_rumor: false,
220
+ likes: 12,
221
+ reshares: 3,
222
+ url: `https://stocktwits.com/symbol/${t}.AX/message/501234571`,
223
+ },
224
+ ],
225
+ summary: {
226
+ bullish_count: 3,
227
+ bearish_count: 1,
228
+ neutral_count: 1,
229
+ total_count: 5,
230
+ bullish_pct: 60,
231
+ bearish_pct: 20,
232
+ avg_sentiment: 0.27,
233
+ top_contributors: [
234
+ { user: "MarketWhispers", posts: 1, avg_sentiment: 0.50 },
235
+ { user: "ValueInvest_AU", posts: 1, avg_sentiment: 0.67 },
236
+ { user: "AusTrader99", posts: 1, avg_sentiment: 0.83 },
237
+ { user: "BearishBob", posts: 1, avg_sentiment: -0.67 },
238
+ { user: "PatientCapital", posts: 1, avg_sentiment: 0.0 },
239
+ ],
240
+ },
241
+ rumor_count: 2,
242
+ overall_sentiment: 0.27,
243
+ overall_sentiment_label: "bullish",
244
+ };
245
+ }
246
+ //# sourceMappingURL=stocktwits-asx-scraper.js.map