Revert "Finshots Plugin commit"

This reverts commit 78b83c4960.
This commit is contained in:
Nitish Khurana 2025-08-24 16:07:50 +05:30
parent 78b83c4960
commit 86d0d5dc92
17 changed files with 370 additions and 1487 deletions

View file

@ -1,10 +1,10 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = tab
indent_size = 4
tab_width = 4

View file

@ -1,3 +1,3 @@
node_modules/
main.js
node_modules/
main.js

View file

@ -1,23 +1,23 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

52
.gitignore vendored
View file

@ -1,26 +1,26 @@
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
main.js
# Exclude sourcemaps
*.map
# obsidian
data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
node_modules
package.json
package-lock.json
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
main.js
# Exclude sourcemaps
*.map
# obsidian
data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
node_modules
package.json
package-lock.json

BIN
Demo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 KiB

View file

@ -1,445 +0,0 @@
import FinshotsDailyPlugin, { FinshotsArticle, FINSHOTS_VIEW_TYPE } from 'main';
import { ItemView, WorkspaceLeaf, Notice } from 'obsidian';
export class FinshotsView extends ItemView {
plugin: FinshotsDailyPlugin;
private article: FinshotsArticle | null = null;
private isLoading = false;
constructor(leaf: WorkspaceLeaf, plugin: FinshotsDailyPlugin) {
super(leaf);
this.plugin = plugin;
}
getViewType(): string {
return FINSHOTS_VIEW_TYPE;
}
getDisplayText(): string {
return "Finshots Daily";
}
getIcon(): string {
return "newspaper";
}
async onOpen() {
await this.render();
await this.loadArticle();
}
async onClose() {
}
private async render() {
const container = this.containerEl.children[1];
container.empty();
const headerEl = container.createEl("div", { cls: "finshots-header" });
headerEl.createEl("h2", { text: "Finshots Daily" });
const refreshBtn = headerEl.createEl("button", {
text: "Refresh",
cls: "finshots-refresh-btn"
});
refreshBtn.addEventListener("click", () => this.loadArticle());
const contentEl = container.createEl("div", { cls: "finshots-content" });
this.renderContent(contentEl);
this.addStyles();
}
private renderContent(contentEl: HTMLElement) {
contentEl.empty();
if (this.isLoading) {
contentEl.createEl("div", {
text: "Loading today's article...",
cls: "finshots-loading"
});
return;
}
if (!this.article) {
const errorEl = contentEl.createEl("div", { cls: "finshots-error" });
errorEl.createEl("p", { text: "No article found for today." });
errorEl.createEl("p", { text: "Click refresh to try again." });
return;
}
// Article container
const articleEl = contentEl.createEl("div", { cls: "finshots-article" });
// Date
articleEl.createEl("div", {
text: this.article.date,
cls: "finshots-date"
});
// Image
if (this.article.imageUrl) {
const imageEl = articleEl.createEl("img", {
cls: "finshots-image"
});
imageEl.src = this.article.imageUrl;
imageEl.alt = this.article.title;
}
// Title
const titleEl = articleEl.createEl("h3", {
text: this.article.title,
cls: "finshots-title"
});
// Summary if available
if (this.article.summary) {
articleEl.createEl("p", {
text: this.article.summary,
cls: "finshots-summary"
});
}
// Read more button
const readMoreEl = articleEl.createEl("a", {
text: "Read Full Article",
cls: "finshots-read-more"
});
readMoreEl.href = this.article.articleUrl;
readMoreEl.target = "_blank";
}
private addStyles() {
const styleEl = document.createElement("style");
styleEl.textContent = `
.finshots-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--background-modifier-border);
margin-bottom: 10px;
}
.finshots-refresh-btn {
padding: 5px 10px;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 3px;
cursor: pointer;
}
.finshots-content {
padding: 10px;
}
.finshots-loading, .finshots-error {
text-align: center;
padding: 20px;
color: var(--text-muted);
}
.finshots-article {
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 15px;
background: var(--background-secondary);
}
.finshots-date {
font-size: 0.8em;
color: var(--text-muted);
margin-bottom: 10px;
}
.finshots-image {
width: 100%;
max-height: 200px;
object-fit: cover;
border-radius: 3px;
margin-bottom: 10px;
}
.finshots-title {
margin: 0 0 10px 0;
color: var(--text-normal);
line-height: 1.3;
}
.finshots-summary {
color: var(--text-muted);
line-height: 1.4;
margin-bottom: 15px;
}
.finshots-read-more {
display: inline-block;
padding: 8px 15px;
background: var(--interactive-accent);
color: var(--text-on-accent);
text-decoration: none;
border-radius: 3px;
font-size: 0.9em;
}
.finshots-read-more:hover {
background: var(--interactive-accent-hover);
}
`;
document.head.appendChild(styleEl);
}
async loadArticle() {
this.isLoading = true;
const contentEl = this.containerEl.querySelector('.finshots-content') as HTMLElement;
if (contentEl) {
this.renderContent(contentEl);
}
try {
this.article = await this.ParseAndGetArticle();
} catch (error) {
console.error('Failed to load Finshots article:', error);
new Notice('Failed to load Finshots article');
this.article = null;
} finally {
this.isLoading = false;
if (contentEl) {
this.renderContent(contentEl);
}
}
}
private async ParseAndGetArticle(): Promise<FinshotsArticle | null> {
try {
const proxyUrl = 'https://api.allorigins.win/get?url=';
const targetUrl = encodeURIComponent('https://finshots.in/archive/');
const response = await fetch(proxyUrl + targetUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Check if we have the expected structure
if (!data || !data.contents) {
throw new Error('Invalid response structure from proxy');
}
const html = data.contents;
// Parse HTML using DOMParser
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Try multiple selectors to find articles
const selectors = [
'.post-feed article.post-card',
'article.post-card',
'.post-card',
'article',
'.post'
];
let firstArticle = null;
let usedSelector = '';
for (const selector of selectors) {
firstArticle = doc.querySelector(selector);
if (firstArticle) {
usedSelector = selector;
console.log(`Found article using selector: ${selector}`);
break;
}
}
if (!firstArticle) {
const articleElements = doc.querySelectorAll('[class*="post"], [class*="article"], [class*="card"]');
articleElements.forEach((el, index) => {
});
throw new Error('Could not find any article in the post feed');
}
const titleSelectors = [
'.post-card-title',
'.post-title',
'h2',
'h3',
'[class*="title"]'
];
let titleElement = null;
let title = '';
for (const selector of titleSelectors) {
titleElement = firstArticle.querySelector(selector);
if (titleElement) {
title = titleElement.textContent?.trim() || '';
if (title) {
console.log(`Found title using selector: ${selector}`);
break;
}
}
}
if (!title) {
title = 'Latest Finshots Article';
console.log('Using fallback title');
}
const imageSelectors = [
'.post-card-image',
'img',
'[class*="image"]'
];
let imageElement = null;
let imageUrl = '';
for (const selector of imageSelectors) {
imageElement = firstArticle.querySelector(selector);
if (imageElement) {
imageUrl = imageElement.getAttribute('src') || '';
if (!imageUrl) {
const srcset = imageElement.getAttribute('srcset');
if (srcset) {
const srcsetUrls = srcset.split(',');
const lastUrl = srcsetUrls[srcsetUrls.length - 1];
imageUrl = lastUrl.split(' ')[0].trim();
}
}
if (imageUrl) {
console.log(`Found image using selector: ${selector}`);
break;
}
}
}
const linkSelectors = [
'.post-card-content-link',
'a[href*="/archive/"]',
'a',
'[href]'
];
let linkElement = null;
let articleUrl = '';
for (const selector of linkSelectors) {
linkElement = firstArticle.querySelector(selector);
if (linkElement) {
articleUrl = linkElement.getAttribute('href') || '';
if (articleUrl && articleUrl !== '#') {
console.log(`Found link using selector: ${selector}`);
break;
}
}
}
if (articleUrl && !articleUrl.startsWith('http')) {
articleUrl = `https://finshots.in${articleUrl}`;
}
const dateSelectors = [
'.post-card-meta-date',
'time',
'[datetime]',
'[class*="date"]'
];
let dateElement = null;
let dateString = '';
let formattedDate = new Date().toLocaleDateString();
for (const selector of dateSelectors) {
dateElement = firstArticle.querySelector(selector);
if (dateElement) {
dateString = dateElement.getAttribute('datetime') || dateElement.textContent?.trim() || '';
if (dateString) {
console.log(`Found date using selector: ${selector}`);
break;
}
}
}
if (dateString) {
try {
const articleDate = new Date(dateString);
if (!isNaN(articleDate.getTime())) {
formattedDate = articleDate.toLocaleDateString();
}
} catch (error) {
console.warn('Failed to parse date:', dateString);
}
}
const excerptSelectors = [
'.post-card-excerpt',
'.excerpt',
'p',
'[class*="summary"]'
];
let excerptElement = null;
let summary = '';
for (const selector of excerptSelectors) {
excerptElement = firstArticle.querySelector(selector);
if (excerptElement) {
summary = excerptElement.textContent?.trim() || '';
if (summary && summary.length > 20) { // Ensure it's not just a short text
console.log(`Found summary using selector: ${selector}`);
break;
}
}
}
const tagSelectors = [
'.post-card-primary-tag',
'.tag',
'[class*="tag"]',
'[class*="category"]'
];
let tagElement = null;
let category = '';
for (const selector of tagSelectors) {
tagElement = firstArticle.querySelector(selector);
if (tagElement) {
category = tagElement.textContent?.trim() || '';
if (category) {
console.log(`Found category using selector: ${selector}`);
break;
}
}
}
if (imageUrl && !imageUrl.startsWith('http')) {
imageUrl = `https://finshots.in${imageUrl}`;
}
return {
title,
imageUrl,
articleUrl,
date: formattedDate,
summary: summary ? `${category ? `[${category}] ` : ''}${summary}` : undefined
};
} catch (error) {
return {
title: 'Failed to fetch the latest article from Finshots Daily',
imageUrl: '',
articleUrl: 'https://finshots.in/',
date: new Date().toLocaleDateString(),
summary: 'Please check the Finshots website directly or try refreshing.'
};
}
}
}

42
LICENSE
View file

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2025 nitishkhurana
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2025 nitishkhurana
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

BIN
MovedFilesDemo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 530 KiB

View file

@ -1,19 +1,39 @@
# Finshots Daily Plugin
An Obsidian plugin that fetches and displays the latest articles from Finshots Daily in a dedicated sidebar view.
## Features
- **Daily Article Display**: Shows the latest Finshots article with title, image, and summary
- **Auto-refresh**: Automatically refreshes daily at 9 AM
- **Manual Refresh**: Click the refresh button to update the content anytime
## Usage
### Opening the Finshots View
1. **Ribbon Icon**: Click the newspaper icon in the left ribbon
2. **Command Palette**: Use Ctrl/Cmd+P and search for "Open Finshots Daily"
3. The view will open in the right sidebar by default
**Note**: This plugin is not officially affiliated with Finshots. It's a community-created tool for Obsidian users who want convenient access to Finshots content.
# Move Files Plugin
When editing a note, open command pallete and run
**"Move Files Plugin: Move linked files and update links"**.
This will create a folder with Name:
"{filename} files" and move all the linked files for e.g. png,pdf,jpg...etc to this folder. It will also udpate the links in the note.
### Updates
Version 1.1.0 : Added support for moving the open note/md files also to the folder.
## Settings
Select the below setting to move the open note/md file as well to the folder created.
![Move Files Setting](Settings.png)
## Example
![Demo](Demo.png)
Run the command and all the files are moved to the folder as shown below.
![FilesMoved](MovedFilesDemo.png)
<br>
## Tip
Use the https://github.com/reorx/obsidian-paste-image-rename
plugin to automatically rename the copied images/file's as per you custom settings.
## Acknowledgement
Developed it to keep the note and their linked files organised so as to keep the vault clean and organized.
Inspired from the Image Collector Plugin
https://github.com/tdaykin/obsidian_image_collector

BIN
Settings.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,49 +1,49 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === "production");
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
minify: prod,
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === "production");
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
minify: prod,
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

View file

@ -1,733 +0,0 @@
import { App, Notice, Plugin, PluginSettingTab, Setting, WorkspaceLeaf, ItemView } from 'obsidian';
interface FinshotsPluginSettings {
refreshTime: string;
autoRefresh: boolean;
useAPI: boolean;
apiEndpoint: string;
}
const DEFAULT_SETTINGS: FinshotsPluginSettings = {
refreshTime: '09:00',
autoRefresh: true,
useAPI: false,
apiEndpoint: ''
};
interface FinshotsArticle {
title: string;
imageUrl: string;
articleUrl: string;
date: string;
summary?: string;
}
export const FINSHOTS_VIEW_TYPE = "finshots-daily-view";
export class FinshotsView extends ItemView {
plugin: FinshotsDailyPlugin;
private article: FinshotsArticle | null = null;
private isLoading = false;
constructor(leaf: WorkspaceLeaf, plugin: FinshotsDailyPlugin) {
super(leaf);
this.plugin = plugin;
}
getViewType(): string {
return FINSHOTS_VIEW_TYPE;
}
getDisplayText(): string {
return "Finshots Daily";
}
getIcon(): string {
return "newspaper";
}
async onOpen() {
await this.render();
await this.loadArticle();
}
async onClose() {
}
private async render() {
const container = this.containerEl.children[1];
container.empty();
const headerEl = container.createEl("div", { cls: "finshots-header" });
headerEl.createEl("h2", { text: "Finshots Daily" });
const refreshBtn = headerEl.createEl("button", {
text: "Refresh",
cls: "finshots-refresh-btn"
});
refreshBtn.addEventListener("click", () => this.loadArticle());
const contentEl = container.createEl("div", { cls: "finshots-content" });
this.renderContent(contentEl);
this.addStyles();
}
private renderContent(contentEl: HTMLElement) {
contentEl.empty();
if (this.isLoading) {
contentEl.createEl("div", {
text: "Loading today's article...",
cls: "finshots-loading"
});
return;
}
if (!this.article) {
const errorEl = contentEl.createEl("div", { cls: "finshots-error" });
errorEl.createEl("p", { text: "No article found for today." });
errorEl.createEl("p", { text: "Click refresh to try again." });
return;
}
// Article container
const articleEl = contentEl.createEl("div", { cls: "finshots-article" });
// Date
articleEl.createEl("div", {
text: this.article.date,
cls: "finshots-date"
});
// Image
if (this.article.imageUrl) {
const imageEl = articleEl.createEl("img", {
cls: "finshots-image"
});
imageEl.src = this.article.imageUrl;
imageEl.alt = this.article.title;
}
// Title
const titleEl = articleEl.createEl("h3", {
text: this.article.title,
cls: "finshots-title"
});
// Summary if available
if (this.article.summary) {
articleEl.createEl("p", {
text: this.article.summary,
cls: "finshots-summary"
});
}
// Read more button
const readMoreEl = articleEl.createEl("a", {
text: "Read Full Article",
cls: "finshots-read-more"
});
readMoreEl.href = this.article.articleUrl;
readMoreEl.target = "_blank";
}
private addStyles() {
const styleEl = document.createElement("style");
styleEl.textContent = `
.finshots-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid var(--background-modifier-border);
margin-bottom: 10px;
}
.finshots-refresh-btn {
padding: 5px 10px;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 3px;
cursor: pointer;
}
.finshots-content {
padding: 10px;
}
.finshots-loading, .finshots-error {
text-align: center;
padding: 20px;
color: var(--text-muted);
}
.finshots-article {
border: 1px solid var(--background-modifier-border);
border-radius: 5px;
padding: 15px;
background: var(--background-secondary);
}
.finshots-date {
font-size: 0.8em;
color: var(--text-muted);
margin-bottom: 10px;
}
.finshots-image {
width: 100%;
max-height: 200px;
object-fit: cover;
border-radius: 3px;
margin-bottom: 10px;
}
.finshots-title {
margin: 0 0 10px 0;
color: var(--text-normal);
line-height: 1.3;
}
.finshots-summary {
color: var(--text-muted);
line-height: 1.4;
margin-bottom: 15px;
}
.finshots-read-more {
display: inline-block;
padding: 8px 15px;
background: var(--interactive-accent);
color: var(--text-on-accent);
text-decoration: none;
border-radius: 3px;
font-size: 0.9em;
}
.finshots-read-more:hover {
background: var(--interactive-accent-hover);
}
`;
document.head.appendChild(styleEl);
}
async loadArticle() {
this.isLoading = true;
const contentEl = this.containerEl.querySelector('.finshots-content') as HTMLElement;
if (contentEl) {
this.renderContent(contentEl);
}
try {
this.article = await this.scrapeWebsite();
} catch (error) {
console.error('Failed to load Finshots article:', error);
new Notice('Failed to load Finshots article');
this.article = null;
} finally {
this.isLoading = false;
if (contentEl) {
this.renderContent(contentEl);
}
}
}
private async scrapeWebsite(): Promise<FinshotsArticle | null> {
try {
const proxyUrl = 'https://api.allorigins.win/get?url=';
const targetUrl = encodeURIComponent('https://finshots.in/archive/');
const response = await fetch(proxyUrl + targetUrl);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
// Debug: Check what we're getting from the proxy
console.log('Proxy response data:', data);
// Check if we have the expected structure
if (!data || !data.contents) {
throw new Error('Invalid response structure from proxy');
}
const html = data.contents;
// Debug: Check HTML length and first few characters
console.log('HTML length:', html.length);
console.log('HTML preview:', html.substring(0, 500));
// Write HTML to file for debugging
//await this.writeHtmlToVault(html);
// Parse HTML using DOMParser
const parser = new DOMParser();
const doc = parser.parseFromString(html, 'text/html');
// Debug: Check if parsing worked
console.log('Parsed document:', doc);
console.log('Document title:', doc.title);
// Try multiple selectors to find articles
const selectors = [
'.post-feed article.post-card',
'article.post-card',
'.post-card',
'article',
'.post'
];
let firstArticle = null;
let usedSelector = '';
for (const selector of selectors) {
firstArticle = doc.querySelector(selector);
if (firstArticle) {
usedSelector = selector;
console.log(`Found article using selector: ${selector}`);
break;
}
}
if (!firstArticle) {
// Debug: Log available elements
const allElements = doc.querySelectorAll('*');
console.log('Total elements found:', allElements.length);
const articleElements = doc.querySelectorAll('[class*="post"], [class*="article"], [class*="card"]');
console.log('Potential article elements:', articleElements.length);
articleElements.forEach((el, index) => {
console.log(`Element ${index}:`, el.className, el.tagName);
});
throw new Error('Could not find any article in the post feed');
}
console.log('Article element:', firstArticle);
console.log('Article HTML:', firstArticle.outerHTML.substring(0, 500));
// Extract title with multiple fallbacks
const titleSelectors = [
'.post-card-title',
'.post-title',
'h2',
'h3',
'[class*="title"]'
];
let titleElement = null;
let title = '';
for (const selector of titleSelectors) {
titleElement = firstArticle.querySelector(selector);
if (titleElement) {
title = titleElement.textContent?.trim() || '';
if (title) {
console.log(`Found title using selector: ${selector}`);
break;
}
}
}
if (!title) {
title = 'Latest Finshots Article';
console.log('Using fallback title');
}
// Extract image URL with multiple fallbacks
const imageSelectors = [
'.post-card-image',
'img',
'[class*="image"]'
];
let imageElement = null;
let imageUrl = '';
for (const selector of imageSelectors) {
imageElement = firstArticle.querySelector(selector);
if (imageElement) {
imageUrl = imageElement.getAttribute('src') || '';
if (!imageUrl) {
const srcset = imageElement.getAttribute('srcset');
if (srcset) {
const srcsetUrls = srcset.split(',');
const lastUrl = srcsetUrls[srcsetUrls.length - 1];
imageUrl = lastUrl.split(' ')[0].trim();
}
}
if (imageUrl) {
console.log(`Found image using selector: ${selector}`);
break;
}
}
}
// Extract article URL with multiple fallbacks
const linkSelectors = [
'.post-card-content-link',
'a[href*="/archive/"]',
'a',
'[href]'
];
let linkElement = null;
let articleUrl = '';
for (const selector of linkSelectors) {
linkElement = firstArticle.querySelector(selector);
if (linkElement) {
articleUrl = linkElement.getAttribute('href') || '';
if (articleUrl && articleUrl !== '#') {
console.log(`Found link using selector: ${selector}`);
break;
}
}
}
// Convert relative URL to absolute URL
if (articleUrl && !articleUrl.startsWith('http')) {
articleUrl = `https://finshots.in${articleUrl}`;
}
// Extract date with multiple fallbacks
const dateSelectors = [
'.post-card-meta-date',
'time',
'[datetime]',
'[class*="date"]'
];
let dateElement = null;
let dateString = '';
let formattedDate = new Date().toLocaleDateString();
for (const selector of dateSelectors) {
dateElement = firstArticle.querySelector(selector);
if (dateElement) {
dateString = dateElement.getAttribute('datetime') || dateElement.textContent?.trim() || '';
if (dateString) {
console.log(`Found date using selector: ${selector}`);
break;
}
}
}
if (dateString) {
try {
const articleDate = new Date(dateString);
if (!isNaN(articleDate.getTime())) {
formattedDate = articleDate.toLocaleDateString();
}
} catch (error) {
console.warn('Failed to parse date:', dateString);
}
}
// Extract excerpt/summary with multiple fallbacks
const excerptSelectors = [
'.post-card-excerpt',
'.excerpt',
'p',
'[class*="summary"]'
];
let excerptElement = null;
let summary = '';
for (const selector of excerptSelectors) {
excerptElement = firstArticle.querySelector(selector);
if (excerptElement) {
summary = excerptElement.textContent?.trim() || '';
if (summary && summary.length > 20) { // Ensure it's not just a short text
console.log(`Found summary using selector: ${selector}`);
break;
}
}
}
// Extract category/tag with multiple fallbacks
const tagSelectors = [
'.post-card-primary-tag',
'.tag',
'[class*="tag"]',
'[class*="category"]'
];
let tagElement = null;
let category = '';
for (const selector of tagSelectors) {
tagElement = firstArticle.querySelector(selector);
if (tagElement) {
category = tagElement.textContent?.trim() || '';
if (category) {
console.log(`Found category using selector: ${selector}`);
break;
}
}
}
// Ensure image URL is absolute
if (imageUrl && !imageUrl.startsWith('http')) {
imageUrl = `https://finshots.in${imageUrl}`;
}
// Log extracted data for debugging
const extractedData = {
title,
imageUrl,
articleUrl,
date: formattedDate,
summary,
category,
usedSelector
};
console.log('Extracted article data:', extractedData);
new Notice(`Found article: ${title}`);
return {
title,
imageUrl,
articleUrl,
date: formattedDate,
summary: summary ? `${category ? `[${category}] ` : ''}${summary}` : undefined
};
} catch (error) {
console.error('Web scraping failed:', error);
new Notice('Failed to scrape Finshots website: ' + error.message);
// Return a fallback article
return {
title: 'Failed to fetch the latest article from Finshots Daily',
imageUrl: '',
articleUrl: 'https://finshots.in/',
date: new Date().toLocaleDateString(),
summary: 'Please check the Finshots website directly or try refreshing.'
};
}
}
// Add this helper method to write HTML to vault for debugging
// private async writeHtmlToVault(html: string): Promise<void> {
// try {
// const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
// const filename = `finshots-debug-${timestamp}.txt`;
// // Write to vault root
// await this.app.vault.create(filename, html);
// console.log(`HTML content saved to vault file: ${filename}`);
// new Notice(`Debug HTML saved to: ${filename}`);
// } catch (error) {
// console.error('Failed to write HTML to vault:', error);
// // Fallback: try to create in a debug folder
// try {
// const debugFolder = 'Debug';
// if (!await this.app.vault.adapter.exists(debugFolder)) {
// await this.app.vault.createFolder(debugFolder);
// }
// const filepath = `${debugFolder}/${filename}`;
// await this.app.vault.create(filepath, html);
// console.log(`HTML saved to: ${filepath}`);
// new Notice(`Debug HTML saved to: ${filepath}`);
// } catch (fallbackError) {
// console.error('Fallback write also failed:', fallbackError);
// }
// }
// }
}
export default class FinshotsDailyPlugin extends Plugin {
settings: FinshotsPluginSettings;
private refreshInterval: number | null = null;
async onload() {
await this.loadSettings();
// Register the view
this.registerView(
FINSHOTS_VIEW_TYPE,
(leaf) => new FinshotsView(leaf, this)
);
// Add ribbon icon
this.addRibbonIcon('newspaper', 'Open Finshots Daily', () => {
this.activateView();
});
// Add command
this.addCommand({
id: 'open-finshots-daily',
name: 'Open Finshots Daily',
callback: () => {
this.activateView();
},
});
// Add settings tab
this.addSettingTab(new FinshotsSettingTab(this.app, this));
// Set up auto-refresh
this.setupAutoRefresh();
}
onunload() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
async activateView() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE);
if (leaves.length > 0) {
// A leaf with our view already exists, use that
leaf = leaves[0];
} else {
// Our view could not be found in the workspace, create a new leaf
// in the right sidebar for it
leaf = workspace.getRightLeaf(false);
if (leaf) {
if(leaf.getViewState().active) {
await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: false });
}
await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: true });
}
}
// "Reveal" the leaf in case it is in a collapsed sidebar
if (leaf) {
workspace.revealLeaf(leaf);
}
}
setupAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
if (!this.settings.autoRefresh) {
return;
}
// Calculate time until next refresh
const now = new Date();
const [hours, minutes] = this.settings.refreshTime.split(':').map(Number);
const nextRefresh = new Date();
nextRefresh.setHours(hours, minutes, 0, 0);
// If the time has already passed today, schedule for tomorrow
if (nextRefresh <= now) {
nextRefresh.setDate(nextRefresh.getDate() + 1);
}
const timeUntilRefresh = nextRefresh.getTime() - now.getTime();
// Set initial timeout
setTimeout(() => {
this.refreshArticle();
// Then set up daily interval
this.refreshInterval = window.setInterval(() => {
this.refreshArticle();
}, 24 * 60 * 60 * 1000); // 24 hours
}, timeUntilRefresh);
}
async refreshArticle() {
const leaves = this.app.workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE);
for (const leaf of leaves) {
if (leaf.view instanceof FinshotsView) {
await leaf.view.loadArticle();
}
}
new Notice('Finshots Daily article refreshed');
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
this.setupAutoRefresh(); // Restart auto-refresh with new settings
}
}
class FinshotsSettingTab extends PluginSettingTab {
plugin: FinshotsDailyPlugin;
constructor(app: App, plugin: FinshotsDailyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Finshots Daily Settings' });
new Setting(containerEl)
.setName('Auto-refresh')
.setDesc('Automatically refresh the article daily')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.autoRefresh)
.onChange(async (value) => {
this.plugin.settings.autoRefresh = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('Refresh time')
.setDesc('Time to refresh the article (24-hour format, e.g., 09:00)')
.addText(text => text
.setPlaceholder('09:00')
.setValue(this.plugin.settings.refreshTime)
.onChange(async (value) => {
// Validate time format
if (/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/.test(value)) {
this.plugin.settings.refreshTime = value;
await this.plugin.saveSettings();
}
}));
new Setting(containerEl)
.setName('Use API')
.setDesc('Use API endpoint instead of web scraping (when available)')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.useAPI)
.onChange(async (value) => {
this.plugin.settings.useAPI = value;
await this.plugin.saveSettings();
}));
new Setting(containerEl)
.setName('API Endpoint')
.setDesc('API endpoint URL for fetching articles')
.addText(text => text
.setPlaceholder('https://api.finshots.com/daily')
.setValue(this.plugin.settings.apiEndpoint)
.onChange(async (value) => {
this.plugin.settings.apiEndpoint = value;
await this.plugin.saveSettings();
}));
}
}

257
main.ts
View file

@ -1,108 +1,149 @@
import { FinshotsView } from 'FinshotsView';
import { App, Notice, Plugin, WorkspaceLeaf } from 'obsidian';
export interface FinshotsArticle {
title: string;
imageUrl: string;
articleUrl: string;
date: string;
summary?: string;
}
export const FINSHOTS_VIEW_TYPE = "finshots-daily-view";
export default class FinshotsDailyPlugin extends Plugin {
private refreshInterval: number | null = null;
async onload() {
this.registerView(
FINSHOTS_VIEW_TYPE,
(leaf) => new FinshotsView(leaf, this)
);
// Add ribbon icon
this.addRibbonIcon('newspaper', 'Open Finshots Daily', () => {
this.activateView();
});
// Add command
this.addCommand({
id: 'open-finshots-daily',
name: 'Open Finshots Daily',
callback: () => {
this.activateView();
},
});
// Set up auto-refresh
this.setupAutoRefresh();
}
onunload() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
}
async activateView() {
const { workspace } = this.app;
let leaf: WorkspaceLeaf | null = null;
const leaves = workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE);
if (leaves.length > 0) {
leaf = leaves[0];
} else {
leaf = workspace.getRightLeaf(false);
if (leaf) {
if(leaf.getViewState().active) {
await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: false });
}
await leaf.setViewState({ type: FINSHOTS_VIEW_TYPE, active: true });
}
}
if (leaf) {
workspace.revealLeaf(leaf);
}
}
setupAutoRefresh() {
if (this.refreshInterval) {
clearInterval(this.refreshInterval);
}
// Calculate time until next refresh
const now = new Date();
const [hours, minutes] = [9, 0];
const nextRefresh = new Date();
nextRefresh.setHours(hours, minutes, 0, 0);
if (nextRefresh <= now) {
nextRefresh.setDate(nextRefresh.getDate() + 1);
}
const timeUntilRefresh = nextRefresh.getTime() - now.getTime();
setTimeout(() => {
this.refreshArticle();
this.refreshInterval = window.setInterval(() => {
this.refreshArticle();
}, 24 * 60 * 60 * 1000); // 24 hours
}, timeUntilRefresh);
}
async refreshArticle() {
const leaves = this.app.workspace.getLeavesOfType(FINSHOTS_VIEW_TYPE);
for (const leaf of leaves) {
if (leaf.view instanceof FinshotsView) {
await leaf.view.loadArticle();
}
}
new Notice('Finshots Daily article refreshed');
}
}
import { App, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting, TFile, TFolder } from 'obsidian';
interface MoveFilesPluginSettings {
moveMdFile: boolean;
}
const DEFAULT_SETTINGS: MoveFilesPluginSettings = {
moveMdFile: false,
};
export default class MoveFilesPlugin extends Plugin {
settings: MoveFilesPluginSettings = DEFAULT_SETTINGS;
async onload() {
await this.loadSettings();
this.addCommand({
id: 'move-linked-files',
name: 'Move linked files and update links',
checkCallback: (checking: boolean) => {
// Obtain the current active file
const activeFile = this.app.workspace.getActiveFile();
// Check if there's an active file and it's a markdown file
if (activeFile && activeFile instanceof TFile && activeFile.extension === 'md') {
//If the check is true then the command will be shown to the user else the command is not visible
//If it is true then the callback is called with checking as false, so execute the command
if(!checking)
{
// Call the export function with the active file
this.moveFilesToANewFolder(activeFile);
}
return true;
}
return false;
},
});
this.addSettingTab(new class extends PluginSettingTab {
plugin: MoveFilesPlugin;
constructor(app: App, plugin: MoveFilesPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl('h2', { text: 'Move Files Plugin Settings' });
new Setting(containerEl)
.setName('Move the current open markdown file itself')
.setDesc('If enabled, the markdown file will also be moved to the new folder.')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.moveMdFile)
.onChange(async (value) => {
this.plugin.settings.moveMdFile = value;
await this.plugin.saveSettings();
}));
}
}(this.app, this));
}
onunload() {
}
async moveFilesToANewFolder(file: TFile) {
const files = [];
const embeds = this.app.metadataCache.getFileCache(file)?.embeds ?? [];
const fileLinks = embeds.map(embed => this.app.metadataCache.getFirstLinkpathDest(embed.link, file.path)).filter(file => file?.extension !== 'md');
for (const file of fileLinks) {
if (file instanceof TFile) {
files.push(file.path);
}
}
if (this.settings.moveMdFile) {
// If the setting is enabled, include the markdown file itself
files.push(file.path);
}
if (files.length === 0) {
new Notice('No linked files found in the markdown file.');
return;
}
const targetFolderName = `${file.basename} files`;
const folderExists = this.app.vault.getAbstractFileByPath(targetFolderName);
if (!(folderExists instanceof TFolder) )
{
await this.app.vault.createFolder(targetFolderName).catch(() => {});
}
for (const filePath of files) {
const fileItem = this.app.metadataCache.getFirstLinkpathDest(filePath, file.path);
if (fileItem instanceof TFile) {
try {
const targetPath = `${targetFolderName}/${fileItem.name}`;
const targetFile = this.app.vault.getAbstractFileByPath(targetPath);
if(!(targetFile instanceof TFile))
{
await this.app.fileManager.renameFile(fileItem,targetPath);
//update the links in open md file
const view = this.app.workspace.getActiveViewOfType(MarkdownView);
// Make sure the user is editing a Markdown file.
if (view) {
const line = view.editor.getValue();
const updatedLine = line.replace(fileItem.name,targetPath);
view.editor.setValue(updatedLine);
}
else {
new Notice(`Failed to rename ${targetPath}: no active editor`)
return
}
new Notice(`Moved ${fileItem.name} to ${targetPath} and updated the links`);
}
else
{
new Notice(`Did not move ${fileItem.name} to ${targetPath} as file already exists`);
}
} catch (error) {
new Notice(`Failed to move file ${fileItem.name}: ${error}`);
console.error(`Failed to move file ${fileItem.name}:`, error);
}
} else {
new Notice(`File not found: ${filePath}`);
console.error(`File not found: ${filePath}`);
}
}
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}

View file

@ -1,10 +1,10 @@
{
"id": "finshots-daily",
"name": "Finshots Daily Article",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "A plugin to fetch and display the latest Finshots Daily articles",
"author": "Nitish Khurana",
"authorUrl": "https://github.com/nitishkhurana",
"isDesktopOnly": false
}
{
"id": "move-files",
"name": "Move Files",
"version": "1.1.0",
"minAppVersion": "0.15.0",
"description": "Moves all the files linked to a open md file to a folder and updates the link in md file.",
"author": "Nitish Khurana",
"authorUrl": "https://github.com/nitishkhurana",
"isDesktopOnly": false
}

View file

@ -1,24 +1,24 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}

View file

@ -1,14 +1,14 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

View file

@ -1,3 +1,3 @@
{
"1.0.0": "0.15.0"
}
{
"1.0.0": "0.15.0"
}