pushing stock info plugin

This commit is contained in:
Mke Jongbloet 2023-04-02 21:46:46 +01:00
parent 1f7054aeb6
commit 15f8a68db9
5 changed files with 2751 additions and 160 deletions

340
main.ts
View file

@ -1,137 +1,223 @@
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; import { Editor, Plugin, Notice } from "obsidian";
import { InsertLinkModal } from "./modal";
// Remember to rename these classes and interfaces!
interface MyPluginSettings {
mySetting: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
mySetting: 'default'
}
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
const yahooStockAPI = require("yahoo-stock-api").default;
const yahoo = new yahooStockAPI();
export default class StockInfoPlugin extends Plugin {
async onload() { async onload() {
await this.loadSettings(); console.log("reloaded");
// This creates an icon in the left ribbon. // Add command to Obsidian quick tasks
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
// Called when the user clicks the icon.
new Notice('This is a notice!');
});
// Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class');
// This adds a status bar item to the bottom of the app. Does not work on mobile apps. this.addCommand({
const statusBarItemEl = this.addStatusBarItem(); id: "insert-stock-info",
statusBarItemEl.setText('Status Bar Text'); name: "Insert stock info",
editorCallback: (editor: Editor) => {
// get selected text
const selectedText = editor.getSelection();
// This adds a simple command that can be triggered anywhere // onSubmit of the form
this.addCommand({ const onSubmit = async (ticker: string) => {
id: 'open-sample-modal-simple', // helper function: format a long number into millions or billions
name: 'Open sample modal (simple)', const formatLongNumber = (n: number) => {
callback: () => { if (n < 1e9) return +(n / 1e6).toFixed(3) + "M";
new SampleModal(this.app).open(); if (n >= 1e9 && n < 1e12)
} return +(n / 1e9).toFixed(3) + "B";
}); if (n >= 1e12) return +(n / 1e12).toFixed(1) + "T";
// This adds an editor command that can perform some operation on the current editor instance };
this.addCommand({
id: 'sample-editor-command', // helper function: check if a given date = today
name: 'Sample editor command', const isToday = (someDate: Date) => {
editorCallback: (editor: Editor, view: MarkdownView) => { const today = new Date();
console.log(editor.getSelection()); return (
editor.replaceSelection('Sample Editor Command'); someDate.getDate() == today.getDate() &&
} someDate.getMonth() == today.getMonth() &&
}); someDate.getFullYear() == today.getFullYear()
// This adds a complex command that can check whether the current state of the app allows execution of the command );
this.addCommand({ };
id: 'open-sample-modal-complex',
name: 'Open sample modal (complex)', // create object to store stock return values
checkCallback: (checking: boolean) => { let stock: { [key: string]: any } = {};
// Conditions to check
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView); // async function callHistoricalPrices(ticker: string) {
if (markdownView) { // try {
// If checking is true, we're simply "checking" if the command can be run. // const startDate: Date = new Date();
// If checking is false, then we want to actually perform the operation. // const endDate: Date = new Date();
if (!checking) {
new SampleModal(this.app).open(); // // begin async call for historical prices
// return yahoo.getHistoricalPrices({
// startDate,
// endDate,
// symbol: ticker,
// frequency: "1d",
// });
// } catch (e) {
// console.error("An error occurred:", e);
// new Notice(
// "Error: couldn't retrieve stock information",
// 15000
// );
// }
// }
async function callCurrentPrices(ticker: string) {
try {
return yahoo.getSymbol({
symbol: ticker,
});
} catch (e) {
console.error("An error occurred:", e);
new Notice(
"Error: couldn't retrieve stock information",
15000
);
}
} }
// This command will only show up in Command Palette when the check function returns true // await the results of both async calls
return true; const currentStockInfo = await callCurrentPrices(ticker);
}
} //console.log(historicStockInfo);
console.log(currentStockInfo);
if (currentStockInfo.error) {
console.error(
"Error occurred:",
currentStockInfo.message
);
new Notice(
"Error: couldn't retrieve stock information",
15000
);
} else {
// Name of the stock
stock["name"] = currentStockInfo.name
? currentStockInfo.name
: null;
// Currency of the stock
stock["currency"] = currentStockInfo.currency
? currentStockInfo.currency
: null;
// Bid price of the stock
if (currentStockInfo.response.bid)
stock["bid"] = currentStockInfo.response.bid.value
? currentStockInfo.response.bid.value
: null;
// Ask price of the stock
if (currentStockInfo.response.ask)
stock["ask"] = currentStockInfo.response.ask.value
? currentStockInfo.response.ask.value
: null;
// Market cap of the stock
stock["marketCap"] = currentStockInfo.response.marketCap
? currentStockInfo.response.marketCap
: null;
// Previous close price of the stock
stock["previousClose"] = currentStockInfo.response
.previousClose
? currentStockInfo.response.previousClose
: null;
// Volume of shares for the stock
stock["volume"] = currentStockInfo.response.volume
? currentStockInfo.response.volume
: null;
// 52 week high
stock["fiftytwo_high"] = currentStockInfo.response
.fiftyTwoWeekRange.high
? currentStockInfo.response.fiftyTwoWeekRange.high
: null;
// 52 week low
stock["fiftytwo_low"] = currentStockInfo.response
.fiftyTwoWeekRange.low
? currentStockInfo.response.fiftyTwoWeekRange.low
: null;
// Day range high
stock["dayRange_high"] = currentStockInfo.response
.dayRange.high
? currentStockInfo.response.dayRange.high
: null;
// Day range low
stock["dayRange_low"] = currentStockInfo.response
.dayRange.low
? currentStockInfo.response.dayRange.low
: null;
// Date and time of provided information
stock["updated"] = currentStockInfo.response.updated
? currentStockInfo.response.updated
: null;
// BUILD OUTPUT
var output = "> [!info]- " + ticker + " ";
if (stock.bid && stock.ask)
output +=
"(Bid: " +
stock.bid +
", Ask: " +
stock.ask +
", Spread: " +
(
((stock.ask - stock.bid) / stock.ask) *
100
).toFixed(3) +
"%)";
if (stock.name) output += "\n> **Name:** " + stock.name;
// if (stock.dayChange)
// output +=
// " (" +
// stock.dayChange +
// " / " +
// stock.dayChangePercent +
// "%)";
if (stock.currency)
output += "\n> **Currency:** " + stock.currency;
if (stock.volume)
output +=
"\n> **Volume:** " +
stock.volume.toLocaleString("en-US");
if (stock.currency && stock.marketCap)
output +=
"\n> **Market cap:** " +
formatLongNumber(stock.marketCap);
if (stock.dayRange_low && stock.dayRange_high)
output +=
"\n> **Day range:** " +
stock.dayRange_low +
" " +
stock.dayRange_high;
if (stock.fiftytwo_low && stock.fiftytwo_high)
output +=
"\n> **52W range:** " +
stock.fiftytwo_low +
" " +
stock.fiftytwo_high;
if (stock.updated)
output +=
"\n>\n><small>*" +
new Date(stock.updated) +
"*</small>";
editor.replaceSelection(`${output}` + "\n\n");
for (var r in stock) delete stock[r];
for (var r in currentStockInfo)
delete currentStockInfo[r];
}
};
new InsertLinkModal(this.app, selectedText, onSubmit).open();
},
}); });
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new SampleSettingTab(this.app, this));
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
console.log('click', evt);
});
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
const {contentEl} = this;
contentEl.setText('Woah!');
}
onClose() {
const {contentEl} = this;
contentEl.empty();
}
}
class SampleSettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const {containerEl} = this;
containerEl.empty();
containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'});
new Setting(containerEl)
.setName('Setting #1')
.setDesc('It\'s a secret')
.addText(text => text
.setPlaceholder('Enter your secret')
.setValue(this.plugin.settings.mySetting)
.onChange(async (value) => {
console.log('Secret: ' + value);
this.plugin.settings.mySetting = value;
await this.plugin.saveSettings();
}));
} }
} }

View file

@ -1,11 +1,11 @@
{ {
"id": "obsidian-sample-plugin", "id": "get-stock-information",
"name": "Sample Plugin", "name": "Get Stock Information",
"version": "1.0.0", "version": "1.0.0",
"minAppVersion": "0.15.0", "minAppVersion": "0.0.0",
"description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.", "description": "This plugin takes a stock symbol and returns a callout block with the latest stock information.",
"author": "Obsidian", "author": "mikejlj",
"authorUrl": "https://obsidian.md", "authorUrl": "https://mikej.design",
"fundingUrl": "https://obsidian.md/pricing", "fundingUrl": "https://obsidian.md/pricing",
"isDesktopOnly": false "isDesktopOnly": false
} }

44
modal.ts Normal file
View file

@ -0,0 +1,44 @@
import { App, Modal, Setting } from "obsidian";
export class InsertLinkModal extends Modal {
ticker: string;
onSubmit: (ticker: string) => void;
constructor(
app: App,
defaultTicker: string,
onSubmit: (ticker: string) => void
) {
super(app);
this.ticker = defaultTicker;
this.onSubmit = onSubmit;
}
onOpen() {
const { contentEl } = this;
contentEl.createEl("h1", { text: "Insert stock information" });
new Setting(contentEl).setName("Stock ticker").addText((text) =>
text.setValue(this.ticker).onChange((value) => {
this.ticker = value;
})
);
new Setting(contentEl).addButton((btn) =>
btn
.setButtonText("Get stock information")
.setCta()
.onClick(() => {
this.close();
this.onSubmit(this.ticker);
})
);
}
onClose() {
let { contentEl } = this;
contentEl.empty();
}
}

2458
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,24 +1,27 @@
{ {
"name": "obsidian-sample-plugin", "name": "get-stock-information",
"version": "1.0.0", "version": "1.0.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)", "description": "This plugin takes a stock symbol and returns a callout block with the latest stock information.",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {
"dev": "node esbuild.config.mjs", "dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json" "version": "node version-bump.mjs && git add manifest.json versions.json"
}, },
"keywords": [], "keywords": [],
"author": "", "author": "mikejlj",
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^16.11.6", "@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0", "@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0", "@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0", "builtin-modules": "3.3.0",
"esbuild": "0.17.3", "esbuild": "0.17.3",
"obsidian": "latest", "obsidian": "latest",
"tslib": "2.4.0", "tslib": "2.4.0",
"typescript": "4.7.4" "typescript": "4.7.4"
} },
"dependencies": {
"yahoo-stock-api": "^2.1.3"
}
} }