@anymux/imgur 0.1.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.
- package/dist/index.d.ts +146 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +199 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/ImgurClient.ts +184 -0
- package/src/ImgurMediaProvider.ts +158 -0
- package/src/index.ts +3 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { IMediaProvider, MediaItem } from "@anymux/ui-kit/media";
|
|
2
|
+
|
|
3
|
+
//#region src/ImgurClient.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Low-level Imgur API v3 client.
|
|
6
|
+
*
|
|
7
|
+
* Uses Client-ID authentication for public gallery access.
|
|
8
|
+
* OAuth2 bearer tokens can be used for user-specific operations.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Low-level Imgur API v3 client.
|
|
12
|
+
*
|
|
13
|
+
* Uses Client-ID authentication for public gallery access.
|
|
14
|
+
* OAuth2 bearer tokens can be used for user-specific operations.
|
|
15
|
+
*/
|
|
16
|
+
interface ImgurImage {
|
|
17
|
+
id: string;
|
|
18
|
+
title: string | null;
|
|
19
|
+
description: string | null;
|
|
20
|
+
datetime: number;
|
|
21
|
+
type: string;
|
|
22
|
+
animated: boolean;
|
|
23
|
+
width: number;
|
|
24
|
+
height: number;
|
|
25
|
+
size: number;
|
|
26
|
+
views: number;
|
|
27
|
+
bandwidth: number;
|
|
28
|
+
link: string;
|
|
29
|
+
mp4?: string;
|
|
30
|
+
gifv?: string;
|
|
31
|
+
mp4_size?: number;
|
|
32
|
+
nsfw: boolean | null;
|
|
33
|
+
section: string | null;
|
|
34
|
+
account_url: string | null;
|
|
35
|
+
account_id: number | null;
|
|
36
|
+
is_ad: boolean;
|
|
37
|
+
in_most_viral: boolean;
|
|
38
|
+
has_sound: boolean;
|
|
39
|
+
tags: Array<{
|
|
40
|
+
name: string;
|
|
41
|
+
}>;
|
|
42
|
+
favorite: boolean;
|
|
43
|
+
}
|
|
44
|
+
interface ImgurAlbum {
|
|
45
|
+
id: string;
|
|
46
|
+
title: string | null;
|
|
47
|
+
description: string | null;
|
|
48
|
+
datetime: number;
|
|
49
|
+
cover: string | null;
|
|
50
|
+
cover_width: number | null;
|
|
51
|
+
cover_height: number | null;
|
|
52
|
+
account_url: string | null;
|
|
53
|
+
account_id: number | null;
|
|
54
|
+
privacy: string;
|
|
55
|
+
layout: string;
|
|
56
|
+
views: number;
|
|
57
|
+
link: string;
|
|
58
|
+
images_count: number;
|
|
59
|
+
images?: ImgurImage[];
|
|
60
|
+
}
|
|
61
|
+
interface ImgurGalleryItem {
|
|
62
|
+
id: string;
|
|
63
|
+
title: string | null;
|
|
64
|
+
description: string | null;
|
|
65
|
+
datetime: number;
|
|
66
|
+
type?: string;
|
|
67
|
+
animated?: boolean;
|
|
68
|
+
width?: number;
|
|
69
|
+
height?: number;
|
|
70
|
+
size?: number;
|
|
71
|
+
views: number;
|
|
72
|
+
link: string;
|
|
73
|
+
is_album: boolean;
|
|
74
|
+
images_count?: number;
|
|
75
|
+
images?: ImgurImage[];
|
|
76
|
+
tags?: Array<{
|
|
77
|
+
name: string;
|
|
78
|
+
}>;
|
|
79
|
+
mp4?: string;
|
|
80
|
+
}
|
|
81
|
+
interface ImgurResponse<T> {
|
|
82
|
+
data: T;
|
|
83
|
+
success: boolean;
|
|
84
|
+
status: number;
|
|
85
|
+
}
|
|
86
|
+
interface ImgurClientConfig {
|
|
87
|
+
/** Imgur Client-ID for public API access */
|
|
88
|
+
clientId: string;
|
|
89
|
+
/** OAuth2 access token for user-specific operations (optional) */
|
|
90
|
+
accessToken?: string;
|
|
91
|
+
}
|
|
92
|
+
declare class ImgurClient {
|
|
93
|
+
private clientId;
|
|
94
|
+
private accessToken;
|
|
95
|
+
constructor(config: ImgurClientConfig);
|
|
96
|
+
/** Get image details by ID. */
|
|
97
|
+
getImage(imageId: string): Promise<ImgurImage>;
|
|
98
|
+
/** Get album details by ID. */
|
|
99
|
+
getAlbum(albumId: string): Promise<ImgurAlbum>;
|
|
100
|
+
/** Get images in an album. */
|
|
101
|
+
getAlbumImages(albumId: string): Promise<ImgurImage[]>;
|
|
102
|
+
/** Search the gallery. */
|
|
103
|
+
searchGallery(options: {
|
|
104
|
+
query: string;
|
|
105
|
+
sort?: 'time' | 'viral' | 'top';
|
|
106
|
+
window?: 'day' | 'week' | 'month' | 'year' | 'all';
|
|
107
|
+
page?: number;
|
|
108
|
+
}): Promise<ImgurGalleryItem[]>;
|
|
109
|
+
/** Get hot/viral gallery items. */
|
|
110
|
+
getGallery(options?: {
|
|
111
|
+
section?: 'hot' | 'top' | 'user';
|
|
112
|
+
sort?: 'viral' | 'top' | 'time' | 'rising';
|
|
113
|
+
window?: 'day' | 'week' | 'month' | 'year' | 'all';
|
|
114
|
+
page?: number;
|
|
115
|
+
}): Promise<ImgurGalleryItem[]>;
|
|
116
|
+
/** Get user's images (requires OAuth). */
|
|
117
|
+
getAccountImages(username?: string, page?: number): Promise<ImgurImage[]>;
|
|
118
|
+
/** Get user's albums (requires OAuth). */
|
|
119
|
+
getAccountAlbums(username?: string, page?: number): Promise<ImgurAlbum[]>;
|
|
120
|
+
/** Get user's favorites (requires OAuth). */
|
|
121
|
+
getAccountFavorites(username?: string, page?: number): Promise<ImgurGalleryItem[]>;
|
|
122
|
+
private get;
|
|
123
|
+
} //#endregion
|
|
124
|
+
//#region src/ImgurMediaProvider.d.ts
|
|
125
|
+
|
|
126
|
+
//# sourceMappingURL=ImgurClient.d.ts.map
|
|
127
|
+
declare class ImgurMediaProvider implements IMediaProvider {
|
|
128
|
+
private client;
|
|
129
|
+
private albumCache;
|
|
130
|
+
constructor(config: ImgurClientConfig);
|
|
131
|
+
listItems(): Promise<MediaItem[]>;
|
|
132
|
+
getItem(id: string): Promise<MediaItem | null>;
|
|
133
|
+
createItem(_item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>): Promise<MediaItem>;
|
|
134
|
+
updateItem(_id: string, _updates: Partial<MediaItem>): Promise<MediaItem>;
|
|
135
|
+
deleteItem(_id: string): Promise<void>;
|
|
136
|
+
search(query: string): Promise<MediaItem[]>;
|
|
137
|
+
getAlbums(): Promise<string[]>;
|
|
138
|
+
getByAlbum(album: string): Promise<MediaItem[]>;
|
|
139
|
+
getByDateRange(_start: Date, _end: Date): Promise<MediaItem[]>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
//#endregion
|
|
143
|
+
//# sourceMappingURL=ImgurMediaProvider.d.ts.map
|
|
144
|
+
|
|
145
|
+
export { ImgurAlbum, ImgurClient, ImgurClientConfig, ImgurGalleryItem, ImgurImage, ImgurMediaProvider, ImgurResponse };
|
|
146
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/ImgurClient.ts","../src/ImgurMediaProvider.ts"],"sourcesContent":null,"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
//#region src/ImgurClient.ts
|
|
2
|
+
/**
|
|
3
|
+
* Low-level Imgur API v3 client.
|
|
4
|
+
*
|
|
5
|
+
* Uses Client-ID authentication for public gallery access.
|
|
6
|
+
* OAuth2 bearer tokens can be used for user-specific operations.
|
|
7
|
+
*/
|
|
8
|
+
const API_URL = "https://api.imgur.com/3";
|
|
9
|
+
var ImgurClient = class {
|
|
10
|
+
constructor(config) {
|
|
11
|
+
this.clientId = config.clientId;
|
|
12
|
+
this.accessToken = config.accessToken;
|
|
13
|
+
}
|
|
14
|
+
/** Get image details by ID. */
|
|
15
|
+
async getImage(imageId) {
|
|
16
|
+
const res = await this.get(`/image/${imageId}`);
|
|
17
|
+
return res.data;
|
|
18
|
+
}
|
|
19
|
+
/** Get album details by ID. */
|
|
20
|
+
async getAlbum(albumId) {
|
|
21
|
+
const res = await this.get(`/album/${albumId}`);
|
|
22
|
+
return res.data;
|
|
23
|
+
}
|
|
24
|
+
/** Get images in an album. */
|
|
25
|
+
async getAlbumImages(albumId) {
|
|
26
|
+
const res = await this.get(`/album/${albumId}/images`);
|
|
27
|
+
return res.data;
|
|
28
|
+
}
|
|
29
|
+
/** Search the gallery. */
|
|
30
|
+
async searchGallery(options) {
|
|
31
|
+
const sort = options.sort ?? "time";
|
|
32
|
+
const window = options.window ?? "all";
|
|
33
|
+
const page = options.page ?? 0;
|
|
34
|
+
const res = await this.get(`/gallery/search/${sort}/${window}/${page}?q=${encodeURIComponent(options.query)}`);
|
|
35
|
+
return res.data;
|
|
36
|
+
}
|
|
37
|
+
/** Get hot/viral gallery items. */
|
|
38
|
+
async getGallery(options = {}) {
|
|
39
|
+
const section = options.section ?? "hot";
|
|
40
|
+
const sort = options.sort ?? "viral";
|
|
41
|
+
const window = options.window ?? "day";
|
|
42
|
+
const page = options.page ?? 0;
|
|
43
|
+
const res = await this.get(`/gallery/${section}/${sort}/${window}/${page}`);
|
|
44
|
+
return res.data;
|
|
45
|
+
}
|
|
46
|
+
/** Get user's images (requires OAuth). */
|
|
47
|
+
async getAccountImages(username = "me", page = 0) {
|
|
48
|
+
const res = await this.get(`/account/${username}/images/${page}`);
|
|
49
|
+
return res.data;
|
|
50
|
+
}
|
|
51
|
+
/** Get user's albums (requires OAuth). */
|
|
52
|
+
async getAccountAlbums(username = "me", page = 0) {
|
|
53
|
+
const res = await this.get(`/account/${username}/albums/${page}`);
|
|
54
|
+
return res.data;
|
|
55
|
+
}
|
|
56
|
+
/** Get user's favorites (requires OAuth). */
|
|
57
|
+
async getAccountFavorites(username = "me", page = 0) {
|
|
58
|
+
const res = await this.get(`/account/${username}/favorites/${page}`);
|
|
59
|
+
return res.data;
|
|
60
|
+
}
|
|
61
|
+
async get(path) {
|
|
62
|
+
const url = `${API_URL}${path}`;
|
|
63
|
+
const headers = {};
|
|
64
|
+
if (this.accessToken) headers["Authorization"] = `Bearer ${this.accessToken}`;
|
|
65
|
+
else headers["Authorization"] = `Client-ID ${this.clientId}`;
|
|
66
|
+
const res = await fetch(url, { headers });
|
|
67
|
+
if (!res.ok) {
|
|
68
|
+
const text = await res.text();
|
|
69
|
+
throw new Error(`Imgur API error ${res.status}: ${text}`);
|
|
70
|
+
}
|
|
71
|
+
return res.json();
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/ImgurMediaProvider.ts
|
|
77
|
+
function getMediaType(image) {
|
|
78
|
+
if (image.animated || image.mp4) return "video";
|
|
79
|
+
return "photo";
|
|
80
|
+
}
|
|
81
|
+
function getMimeType(image) {
|
|
82
|
+
if (image.mp4) return "video/mp4";
|
|
83
|
+
return image.type || "image/jpeg";
|
|
84
|
+
}
|
|
85
|
+
function getUrl(image) {
|
|
86
|
+
if (image.mp4) return image.mp4;
|
|
87
|
+
return image.link;
|
|
88
|
+
}
|
|
89
|
+
function getThumbnail(image) {
|
|
90
|
+
const link = image.link;
|
|
91
|
+
const lastDot = link.lastIndexOf(".");
|
|
92
|
+
if (lastDot === -1) return link;
|
|
93
|
+
return `${link.substring(0, lastDot)}m${link.substring(lastDot)}`;
|
|
94
|
+
}
|
|
95
|
+
function mapImage(image, albumName) {
|
|
96
|
+
const createdAt = new Date(image.datetime * 1e3);
|
|
97
|
+
const mediaType = getMediaType(image);
|
|
98
|
+
const item = {
|
|
99
|
+
id: image.id,
|
|
100
|
+
type: "media",
|
|
101
|
+
title: image.title || `Image ${image.id}`,
|
|
102
|
+
mediaType,
|
|
103
|
+
url: getUrl(image),
|
|
104
|
+
thumbnail: getThumbnail(image),
|
|
105
|
+
mimeType: getMimeType(image),
|
|
106
|
+
width: image.width,
|
|
107
|
+
height: image.height,
|
|
108
|
+
createdAt,
|
|
109
|
+
updatedAt: createdAt
|
|
110
|
+
};
|
|
111
|
+
if (image.description) item.description = image.description;
|
|
112
|
+
if (image.tags && image.tags.length > 0) item.tags = image.tags.map((t) => t.name);
|
|
113
|
+
if (albumName) item.album = albumName;
|
|
114
|
+
return item;
|
|
115
|
+
}
|
|
116
|
+
function mapGalleryItem(item) {
|
|
117
|
+
if (item.is_album && item.images) return item.images.map((img) => mapImage(img));
|
|
118
|
+
const createdAt = new Date(item.datetime * 1e3);
|
|
119
|
+
const mediaType = item.animated || item.mp4 ? "video" : "photo";
|
|
120
|
+
return [{
|
|
121
|
+
id: item.id,
|
|
122
|
+
type: "media",
|
|
123
|
+
title: item.title || `Image ${item.id}`,
|
|
124
|
+
mediaType,
|
|
125
|
+
url: item.mp4 || item.link,
|
|
126
|
+
thumbnail: item.link,
|
|
127
|
+
mimeType: item.mp4 ? "video/mp4" : item.type || "image/jpeg",
|
|
128
|
+
width: item.width,
|
|
129
|
+
height: item.height,
|
|
130
|
+
createdAt,
|
|
131
|
+
updatedAt: createdAt,
|
|
132
|
+
description: item.description ?? void 0,
|
|
133
|
+
tags: item.tags?.map((t) => t.name)
|
|
134
|
+
}];
|
|
135
|
+
}
|
|
136
|
+
var ImgurMediaProvider = class {
|
|
137
|
+
constructor(config) {
|
|
138
|
+
this.albumCache = new Map();
|
|
139
|
+
this.client = new ImgurClient(config);
|
|
140
|
+
}
|
|
141
|
+
async listItems() {
|
|
142
|
+
const items = await this.client.getGallery({
|
|
143
|
+
section: "hot",
|
|
144
|
+
sort: "viral",
|
|
145
|
+
page: 0
|
|
146
|
+
});
|
|
147
|
+
return items.flatMap(mapGalleryItem);
|
|
148
|
+
}
|
|
149
|
+
async getItem(id) {
|
|
150
|
+
try {
|
|
151
|
+
const image = await this.client.getImage(id);
|
|
152
|
+
return mapImage(image);
|
|
153
|
+
} catch {
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async createItem(_item) {
|
|
158
|
+
throw new Error("Imgur upload requires OAuth2 authentication");
|
|
159
|
+
}
|
|
160
|
+
async updateItem(_id, _updates) {
|
|
161
|
+
throw new Error("Imgur does not support updating images via this API");
|
|
162
|
+
}
|
|
163
|
+
async deleteItem(_id) {
|
|
164
|
+
throw new Error("Imgur delete requires OAuth2 authentication");
|
|
165
|
+
}
|
|
166
|
+
async search(query) {
|
|
167
|
+
const items = await this.client.searchGallery({
|
|
168
|
+
query,
|
|
169
|
+
sort: "time"
|
|
170
|
+
});
|
|
171
|
+
return items.flatMap(mapGalleryItem);
|
|
172
|
+
}
|
|
173
|
+
async getAlbums() {
|
|
174
|
+
return [];
|
|
175
|
+
}
|
|
176
|
+
async getByAlbum(album) {
|
|
177
|
+
if (this.albumCache.size === 0) return [];
|
|
178
|
+
let albumId;
|
|
179
|
+
for (const [id, a] of this.albumCache) if (a.title === album) {
|
|
180
|
+
albumId = id;
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
if (!albumId) return [];
|
|
184
|
+
const images = await this.client.getAlbumImages(albumId);
|
|
185
|
+
return images.map((img) => mapImage(img, album));
|
|
186
|
+
}
|
|
187
|
+
async getByDateRange(_start, _end) {
|
|
188
|
+
const items = await this.client.getGallery({
|
|
189
|
+
section: "hot",
|
|
190
|
+
sort: "time",
|
|
191
|
+
page: 0
|
|
192
|
+
});
|
|
193
|
+
return items.flatMap(mapGalleryItem);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
//#endregion
|
|
198
|
+
export { ImgurClient, ImgurMediaProvider };
|
|
199
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":["config: ImgurClientConfig","imageId: string","albumId: string","options: {\n query: string;\n sort?: 'time' | 'viral' | 'top';\n window?: 'day' | 'week' | 'month' | 'year' | 'all';\n page?: number;\n }","options: {\n section?: 'hot' | 'top' | 'user';\n sort?: 'viral' | 'top' | 'time' | 'rising';\n window?: 'day' | 'week' | 'month' | 'year' | 'all';\n page?: number;\n }","username: string","page: number","path: string","headers: Record<string, string>","image: ImgurImage","albumName?: string","item: MediaItem","item: ImgurGalleryItem","mediaType: MediaType","config: ImgurClientConfig","id: string","_item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>","_id: string","_updates: Partial<MediaItem>","query: string","album: string","albumId: string | undefined","_start: Date","_end: Date"],"sources":["../src/ImgurClient.ts","../src/ImgurMediaProvider.ts"],"sourcesContent":["/**\n * Low-level Imgur API v3 client.\n *\n * Uses Client-ID authentication for public gallery access.\n * OAuth2 bearer tokens can be used for user-specific operations.\n */\n\nconst API_URL = 'https://api.imgur.com/3';\n\n// ---------- Response shapes ----------\n\nexport interface ImgurImage {\n id: string;\n title: string | null;\n description: string | null;\n datetime: number;\n type: string;\n animated: boolean;\n width: number;\n height: number;\n size: number;\n views: number;\n bandwidth: number;\n link: string;\n mp4?: string;\n gifv?: string;\n mp4_size?: number;\n nsfw: boolean | null;\n section: string | null;\n account_url: string | null;\n account_id: number | null;\n is_ad: boolean;\n in_most_viral: boolean;\n has_sound: boolean;\n tags: Array<{ name: string }>;\n favorite: boolean;\n}\n\nexport interface ImgurAlbum {\n id: string;\n title: string | null;\n description: string | null;\n datetime: number;\n cover: string | null;\n cover_width: number | null;\n cover_height: number | null;\n account_url: string | null;\n account_id: number | null;\n privacy: string;\n layout: string;\n views: number;\n link: string;\n images_count: number;\n images?: ImgurImage[];\n}\n\nexport interface ImgurGalleryItem {\n id: string;\n title: string | null;\n description: string | null;\n datetime: number;\n type?: string;\n animated?: boolean;\n width?: number;\n height?: number;\n size?: number;\n views: number;\n link: string;\n is_album: boolean;\n images_count?: number;\n images?: ImgurImage[];\n tags?: Array<{ name: string }>;\n mp4?: string;\n}\n\nexport interface ImgurResponse<T> {\n data: T;\n success: boolean;\n status: number;\n}\n\nexport interface ImgurClientConfig {\n /** Imgur Client-ID for public API access */\n clientId: string;\n /** OAuth2 access token for user-specific operations (optional) */\n accessToken?: string;\n}\n\nexport class ImgurClient {\n private clientId: string;\n private accessToken: string | undefined;\n\n constructor(config: ImgurClientConfig) {\n this.clientId = config.clientId;\n this.accessToken = config.accessToken;\n }\n\n /** Get image details by ID. */\n async getImage(imageId: string): Promise<ImgurImage> {\n const res = await this.get<ImgurResponse<ImgurImage>>(`/image/${imageId}`);\n return res.data;\n }\n\n /** Get album details by ID. */\n async getAlbum(albumId: string): Promise<ImgurAlbum> {\n const res = await this.get<ImgurResponse<ImgurAlbum>>(`/album/${albumId}`);\n return res.data;\n }\n\n /** Get images in an album. */\n async getAlbumImages(albumId: string): Promise<ImgurImage[]> {\n const res = await this.get<ImgurResponse<ImgurImage[]>>(`/album/${albumId}/images`);\n return res.data;\n }\n\n /** Search the gallery. */\n async searchGallery(options: {\n query: string;\n sort?: 'time' | 'viral' | 'top';\n window?: 'day' | 'week' | 'month' | 'year' | 'all';\n page?: number;\n }): Promise<ImgurGalleryItem[]> {\n const sort = options.sort ?? 'time';\n const window = options.window ?? 'all';\n const page = options.page ?? 0;\n const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(\n `/gallery/search/${sort}/${window}/${page}?q=${encodeURIComponent(options.query)}`\n );\n return res.data;\n }\n\n /** Get hot/viral gallery items. */\n async getGallery(options: {\n section?: 'hot' | 'top' | 'user';\n sort?: 'viral' | 'top' | 'time' | 'rising';\n window?: 'day' | 'week' | 'month' | 'year' | 'all';\n page?: number;\n } = {}): Promise<ImgurGalleryItem[]> {\n const section = options.section ?? 'hot';\n const sort = options.sort ?? 'viral';\n const window = options.window ?? 'day';\n const page = options.page ?? 0;\n const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(\n `/gallery/${section}/${sort}/${window}/${page}`\n );\n return res.data;\n }\n\n /** Get user's images (requires OAuth). */\n async getAccountImages(username: string = 'me', page: number = 0): Promise<ImgurImage[]> {\n const res = await this.get<ImgurResponse<ImgurImage[]>>(`/account/${username}/images/${page}`);\n return res.data;\n }\n\n /** Get user's albums (requires OAuth). */\n async getAccountAlbums(username: string = 'me', page: number = 0): Promise<ImgurAlbum[]> {\n const res = await this.get<ImgurResponse<ImgurAlbum[]>>(`/account/${username}/albums/${page}`);\n return res.data;\n }\n\n /** Get user's favorites (requires OAuth). */\n async getAccountFavorites(username: string = 'me', page: number = 0): Promise<ImgurGalleryItem[]> {\n const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(`/account/${username}/favorites/${page}`);\n return res.data;\n }\n\n // ---------- Internal ----------\n\n private async get<T>(path: string): Promise<T> {\n const url = `${API_URL}${path}`;\n const headers: Record<string, string> = {};\n if (this.accessToken) {\n headers['Authorization'] = `Bearer ${this.accessToken}`;\n } else {\n headers['Authorization'] = `Client-ID ${this.clientId}`;\n }\n const res = await fetch(url, { headers });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Imgur API error ${res.status}: ${text}`);\n }\n return res.json() as Promise<T>;\n }\n}\n","import type { IMediaProvider, MediaItem, MediaType } from '@anymux/ui-kit/media';\nimport { ImgurClient } from './ImgurClient';\nimport type { ImgurImage, ImgurGalleryItem, ImgurAlbum, ImgurClientConfig } from './ImgurClient';\n\nfunction getMediaType(image: ImgurImage): MediaType {\n if (image.animated || image.mp4) return 'video';\n return 'photo';\n}\n\nfunction getMimeType(image: ImgurImage): string {\n if (image.mp4) return 'video/mp4';\n return image.type || 'image/jpeg';\n}\n\nfunction getUrl(image: ImgurImage): string {\n if (image.mp4) return image.mp4;\n return image.link;\n}\n\nfunction getThumbnail(image: ImgurImage): string {\n // Imgur thumbnail convention: insert size suffix before extension\n // l = large thumbnail (640x640), m = medium (320x320), t = small (160x160), s = small square (90x90)\n const link = image.link;\n const lastDot = link.lastIndexOf('.');\n if (lastDot === -1) return link;\n return `${link.substring(0, lastDot)}m${link.substring(lastDot)}`;\n}\n\nfunction mapImage(image: ImgurImage, albumName?: string): MediaItem {\n const createdAt = new Date(image.datetime * 1000);\n const mediaType = getMediaType(image);\n\n const item: MediaItem = {\n id: image.id,\n type: 'media',\n title: image.title || `Image ${image.id}`,\n mediaType,\n url: getUrl(image),\n thumbnail: getThumbnail(image),\n mimeType: getMimeType(image),\n width: image.width,\n height: image.height,\n createdAt,\n updatedAt: createdAt,\n };\n\n if (image.description) {\n item.description = image.description;\n }\n\n if (image.tags && image.tags.length > 0) {\n item.tags = image.tags.map(t => t.name);\n }\n\n if (albumName) {\n item.album = albumName;\n }\n\n return item;\n}\n\nfunction mapGalleryItem(item: ImgurGalleryItem): MediaItem[] {\n // Gallery items can be albums or single images\n if (item.is_album && item.images) {\n return item.images.map(img => mapImage(img));\n }\n // Single image in gallery\n const createdAt = new Date(item.datetime * 1000);\n const mediaType: MediaType = item.animated || item.mp4 ? 'video' : 'photo';\n\n return [{\n id: item.id,\n type: 'media',\n title: item.title || `Image ${item.id}`,\n mediaType,\n url: item.mp4 || item.link,\n thumbnail: item.link, // Gallery items don't have thumbnail suffix easily\n mimeType: item.mp4 ? 'video/mp4' : item.type || 'image/jpeg',\n width: item.width,\n height: item.height,\n createdAt,\n updatedAt: createdAt,\n description: item.description ?? undefined,\n tags: item.tags?.map(t => t.name),\n }];\n}\n\nexport class ImgurMediaProvider implements IMediaProvider {\n private client: ImgurClient;\n private albumCache = new Map<string, ImgurAlbum>();\n\n constructor(config: ImgurClientConfig) {\n this.client = new ImgurClient(config);\n }\n\n // ---------- IObjectProvider<MediaItem> ----------\n\n async listItems(): Promise<MediaItem[]> {\n const items = await this.client.getGallery({ section: 'hot', sort: 'viral', page: 0 });\n return items.flatMap(mapGalleryItem);\n }\n\n async getItem(id: string): Promise<MediaItem | null> {\n try {\n const image = await this.client.getImage(id);\n return mapImage(image);\n } catch {\n return null;\n }\n }\n\n async createItem(_item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>): Promise<MediaItem> {\n throw new Error('Imgur upload requires OAuth2 authentication');\n }\n\n async updateItem(_id: string, _updates: Partial<MediaItem>): Promise<MediaItem> {\n throw new Error('Imgur does not support updating images via this API');\n }\n\n async deleteItem(_id: string): Promise<void> {\n throw new Error('Imgur delete requires OAuth2 authentication');\n }\n\n async search(query: string): Promise<MediaItem[]> {\n const items = await this.client.searchGallery({ query, sort: 'time' });\n return items.flatMap(mapGalleryItem);\n }\n\n // ---------- IMediaProvider ----------\n\n async getAlbums(): Promise<string[]> {\n // With Client-ID only, we can't list user albums\n // Return empty list — user needs OAuth for personal albums\n return [];\n }\n\n async getByAlbum(album: string): Promise<MediaItem[]> {\n if (this.albumCache.size === 0) return [];\n\n let albumId: string | undefined;\n for (const [id, a] of this.albumCache) {\n if (a.title === album) {\n albumId = id;\n break;\n }\n }\n if (!albumId) return [];\n\n const images = await this.client.getAlbumImages(albumId);\n return images.map(img => mapImage(img, album));\n }\n\n async getByDateRange(_start: Date, _end: Date): Promise<MediaItem[]> {\n // Imgur doesn't support date range queries — return hot gallery\n const items = await this.client.getGallery({ section: 'hot', sort: 'time', page: 0 });\n return items.flatMap(mapGalleryItem);\n }\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@anymux/imgur",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "Imgur media provider adapter for AnyMux",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"anymux",
|
|
9
|
+
"imgur",
|
|
10
|
+
"media",
|
|
11
|
+
"photos",
|
|
12
|
+
"image-hosting"
|
|
13
|
+
],
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "https://github.com/AnyMux/AnyMuxMonorepo.git",
|
|
17
|
+
"directory": "packages/imgur"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/AnyMux/AnyMuxMonorepo/tree/main/packages/imgur#readme",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
},
|
|
23
|
+
"main": "./src/index.ts",
|
|
24
|
+
"types": "./src/index.ts",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"src",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@anymux/ui-kit": "0.2.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.10.2",
|
|
41
|
+
"tsdown": "^0.10.2",
|
|
42
|
+
"typescript": "^5.7.3",
|
|
43
|
+
"@anymux/typescript-config": "0.0.0"
|
|
44
|
+
},
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "tsdown",
|
|
47
|
+
"check": "tsc -p tsconfig.json --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Low-level Imgur API v3 client.
|
|
3
|
+
*
|
|
4
|
+
* Uses Client-ID authentication for public gallery access.
|
|
5
|
+
* OAuth2 bearer tokens can be used for user-specific operations.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const API_URL = 'https://api.imgur.com/3';
|
|
9
|
+
|
|
10
|
+
// ---------- Response shapes ----------
|
|
11
|
+
|
|
12
|
+
export interface ImgurImage {
|
|
13
|
+
id: string;
|
|
14
|
+
title: string | null;
|
|
15
|
+
description: string | null;
|
|
16
|
+
datetime: number;
|
|
17
|
+
type: string;
|
|
18
|
+
animated: boolean;
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
size: number;
|
|
22
|
+
views: number;
|
|
23
|
+
bandwidth: number;
|
|
24
|
+
link: string;
|
|
25
|
+
mp4?: string;
|
|
26
|
+
gifv?: string;
|
|
27
|
+
mp4_size?: number;
|
|
28
|
+
nsfw: boolean | null;
|
|
29
|
+
section: string | null;
|
|
30
|
+
account_url: string | null;
|
|
31
|
+
account_id: number | null;
|
|
32
|
+
is_ad: boolean;
|
|
33
|
+
in_most_viral: boolean;
|
|
34
|
+
has_sound: boolean;
|
|
35
|
+
tags: Array<{ name: string }>;
|
|
36
|
+
favorite: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ImgurAlbum {
|
|
40
|
+
id: string;
|
|
41
|
+
title: string | null;
|
|
42
|
+
description: string | null;
|
|
43
|
+
datetime: number;
|
|
44
|
+
cover: string | null;
|
|
45
|
+
cover_width: number | null;
|
|
46
|
+
cover_height: number | null;
|
|
47
|
+
account_url: string | null;
|
|
48
|
+
account_id: number | null;
|
|
49
|
+
privacy: string;
|
|
50
|
+
layout: string;
|
|
51
|
+
views: number;
|
|
52
|
+
link: string;
|
|
53
|
+
images_count: number;
|
|
54
|
+
images?: ImgurImage[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface ImgurGalleryItem {
|
|
58
|
+
id: string;
|
|
59
|
+
title: string | null;
|
|
60
|
+
description: string | null;
|
|
61
|
+
datetime: number;
|
|
62
|
+
type?: string;
|
|
63
|
+
animated?: boolean;
|
|
64
|
+
width?: number;
|
|
65
|
+
height?: number;
|
|
66
|
+
size?: number;
|
|
67
|
+
views: number;
|
|
68
|
+
link: string;
|
|
69
|
+
is_album: boolean;
|
|
70
|
+
images_count?: number;
|
|
71
|
+
images?: ImgurImage[];
|
|
72
|
+
tags?: Array<{ name: string }>;
|
|
73
|
+
mp4?: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface ImgurResponse<T> {
|
|
77
|
+
data: T;
|
|
78
|
+
success: boolean;
|
|
79
|
+
status: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ImgurClientConfig {
|
|
83
|
+
/** Imgur Client-ID for public API access */
|
|
84
|
+
clientId: string;
|
|
85
|
+
/** OAuth2 access token for user-specific operations (optional) */
|
|
86
|
+
accessToken?: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export class ImgurClient {
|
|
90
|
+
private clientId: string;
|
|
91
|
+
private accessToken: string | undefined;
|
|
92
|
+
|
|
93
|
+
constructor(config: ImgurClientConfig) {
|
|
94
|
+
this.clientId = config.clientId;
|
|
95
|
+
this.accessToken = config.accessToken;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Get image details by ID. */
|
|
99
|
+
async getImage(imageId: string): Promise<ImgurImage> {
|
|
100
|
+
const res = await this.get<ImgurResponse<ImgurImage>>(`/image/${imageId}`);
|
|
101
|
+
return res.data;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Get album details by ID. */
|
|
105
|
+
async getAlbum(albumId: string): Promise<ImgurAlbum> {
|
|
106
|
+
const res = await this.get<ImgurResponse<ImgurAlbum>>(`/album/${albumId}`);
|
|
107
|
+
return res.data;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Get images in an album. */
|
|
111
|
+
async getAlbumImages(albumId: string): Promise<ImgurImage[]> {
|
|
112
|
+
const res = await this.get<ImgurResponse<ImgurImage[]>>(`/album/${albumId}/images`);
|
|
113
|
+
return res.data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Search the gallery. */
|
|
117
|
+
async searchGallery(options: {
|
|
118
|
+
query: string;
|
|
119
|
+
sort?: 'time' | 'viral' | 'top';
|
|
120
|
+
window?: 'day' | 'week' | 'month' | 'year' | 'all';
|
|
121
|
+
page?: number;
|
|
122
|
+
}): Promise<ImgurGalleryItem[]> {
|
|
123
|
+
const sort = options.sort ?? 'time';
|
|
124
|
+
const window = options.window ?? 'all';
|
|
125
|
+
const page = options.page ?? 0;
|
|
126
|
+
const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(
|
|
127
|
+
`/gallery/search/${sort}/${window}/${page}?q=${encodeURIComponent(options.query)}`
|
|
128
|
+
);
|
|
129
|
+
return res.data;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Get hot/viral gallery items. */
|
|
133
|
+
async getGallery(options: {
|
|
134
|
+
section?: 'hot' | 'top' | 'user';
|
|
135
|
+
sort?: 'viral' | 'top' | 'time' | 'rising';
|
|
136
|
+
window?: 'day' | 'week' | 'month' | 'year' | 'all';
|
|
137
|
+
page?: number;
|
|
138
|
+
} = {}): Promise<ImgurGalleryItem[]> {
|
|
139
|
+
const section = options.section ?? 'hot';
|
|
140
|
+
const sort = options.sort ?? 'viral';
|
|
141
|
+
const window = options.window ?? 'day';
|
|
142
|
+
const page = options.page ?? 0;
|
|
143
|
+
const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(
|
|
144
|
+
`/gallery/${section}/${sort}/${window}/${page}`
|
|
145
|
+
);
|
|
146
|
+
return res.data;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Get user's images (requires OAuth). */
|
|
150
|
+
async getAccountImages(username: string = 'me', page: number = 0): Promise<ImgurImage[]> {
|
|
151
|
+
const res = await this.get<ImgurResponse<ImgurImage[]>>(`/account/${username}/images/${page}`);
|
|
152
|
+
return res.data;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Get user's albums (requires OAuth). */
|
|
156
|
+
async getAccountAlbums(username: string = 'me', page: number = 0): Promise<ImgurAlbum[]> {
|
|
157
|
+
const res = await this.get<ImgurResponse<ImgurAlbum[]>>(`/account/${username}/albums/${page}`);
|
|
158
|
+
return res.data;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Get user's favorites (requires OAuth). */
|
|
162
|
+
async getAccountFavorites(username: string = 'me', page: number = 0): Promise<ImgurGalleryItem[]> {
|
|
163
|
+
const res = await this.get<ImgurResponse<ImgurGalleryItem[]>>(`/account/${username}/favorites/${page}`);
|
|
164
|
+
return res.data;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// ---------- Internal ----------
|
|
168
|
+
|
|
169
|
+
private async get<T>(path: string): Promise<T> {
|
|
170
|
+
const url = `${API_URL}${path}`;
|
|
171
|
+
const headers: Record<string, string> = {};
|
|
172
|
+
if (this.accessToken) {
|
|
173
|
+
headers['Authorization'] = `Bearer ${this.accessToken}`;
|
|
174
|
+
} else {
|
|
175
|
+
headers['Authorization'] = `Client-ID ${this.clientId}`;
|
|
176
|
+
}
|
|
177
|
+
const res = await fetch(url, { headers });
|
|
178
|
+
if (!res.ok) {
|
|
179
|
+
const text = await res.text();
|
|
180
|
+
throw new Error(`Imgur API error ${res.status}: ${text}`);
|
|
181
|
+
}
|
|
182
|
+
return res.json() as Promise<T>;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { IMediaProvider, MediaItem, MediaType } from '@anymux/ui-kit/media';
|
|
2
|
+
import { ImgurClient } from './ImgurClient';
|
|
3
|
+
import type { ImgurImage, ImgurGalleryItem, ImgurAlbum, ImgurClientConfig } from './ImgurClient';
|
|
4
|
+
|
|
5
|
+
function getMediaType(image: ImgurImage): MediaType {
|
|
6
|
+
if (image.animated || image.mp4) return 'video';
|
|
7
|
+
return 'photo';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function getMimeType(image: ImgurImage): string {
|
|
11
|
+
if (image.mp4) return 'video/mp4';
|
|
12
|
+
return image.type || 'image/jpeg';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function getUrl(image: ImgurImage): string {
|
|
16
|
+
if (image.mp4) return image.mp4;
|
|
17
|
+
return image.link;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function getThumbnail(image: ImgurImage): string {
|
|
21
|
+
// Imgur thumbnail convention: insert size suffix before extension
|
|
22
|
+
// l = large thumbnail (640x640), m = medium (320x320), t = small (160x160), s = small square (90x90)
|
|
23
|
+
const link = image.link;
|
|
24
|
+
const lastDot = link.lastIndexOf('.');
|
|
25
|
+
if (lastDot === -1) return link;
|
|
26
|
+
return `${link.substring(0, lastDot)}m${link.substring(lastDot)}`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function mapImage(image: ImgurImage, albumName?: string): MediaItem {
|
|
30
|
+
const createdAt = new Date(image.datetime * 1000);
|
|
31
|
+
const mediaType = getMediaType(image);
|
|
32
|
+
|
|
33
|
+
const item: MediaItem = {
|
|
34
|
+
id: image.id,
|
|
35
|
+
type: 'media',
|
|
36
|
+
title: image.title || `Image ${image.id}`,
|
|
37
|
+
mediaType,
|
|
38
|
+
url: getUrl(image),
|
|
39
|
+
thumbnail: getThumbnail(image),
|
|
40
|
+
mimeType: getMimeType(image),
|
|
41
|
+
width: image.width,
|
|
42
|
+
height: image.height,
|
|
43
|
+
createdAt,
|
|
44
|
+
updatedAt: createdAt,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
if (image.description) {
|
|
48
|
+
item.description = image.description;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (image.tags && image.tags.length > 0) {
|
|
52
|
+
item.tags = image.tags.map(t => t.name);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (albumName) {
|
|
56
|
+
item.album = albumName;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return item;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function mapGalleryItem(item: ImgurGalleryItem): MediaItem[] {
|
|
63
|
+
// Gallery items can be albums or single images
|
|
64
|
+
if (item.is_album && item.images) {
|
|
65
|
+
return item.images.map(img => mapImage(img));
|
|
66
|
+
}
|
|
67
|
+
// Single image in gallery
|
|
68
|
+
const createdAt = new Date(item.datetime * 1000);
|
|
69
|
+
const mediaType: MediaType = item.animated || item.mp4 ? 'video' : 'photo';
|
|
70
|
+
|
|
71
|
+
return [{
|
|
72
|
+
id: item.id,
|
|
73
|
+
type: 'media',
|
|
74
|
+
title: item.title || `Image ${item.id}`,
|
|
75
|
+
mediaType,
|
|
76
|
+
url: item.mp4 || item.link,
|
|
77
|
+
thumbnail: item.link, // Gallery items don't have thumbnail suffix easily
|
|
78
|
+
mimeType: item.mp4 ? 'video/mp4' : item.type || 'image/jpeg',
|
|
79
|
+
width: item.width,
|
|
80
|
+
height: item.height,
|
|
81
|
+
createdAt,
|
|
82
|
+
updatedAt: createdAt,
|
|
83
|
+
description: item.description ?? undefined,
|
|
84
|
+
tags: item.tags?.map(t => t.name),
|
|
85
|
+
}];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class ImgurMediaProvider implements IMediaProvider {
|
|
89
|
+
private client: ImgurClient;
|
|
90
|
+
private albumCache = new Map<string, ImgurAlbum>();
|
|
91
|
+
|
|
92
|
+
constructor(config: ImgurClientConfig) {
|
|
93
|
+
this.client = new ImgurClient(config);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ---------- IObjectProvider<MediaItem> ----------
|
|
97
|
+
|
|
98
|
+
async listItems(): Promise<MediaItem[]> {
|
|
99
|
+
const items = await this.client.getGallery({ section: 'hot', sort: 'viral', page: 0 });
|
|
100
|
+
return items.flatMap(mapGalleryItem);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async getItem(id: string): Promise<MediaItem | null> {
|
|
104
|
+
try {
|
|
105
|
+
const image = await this.client.getImage(id);
|
|
106
|
+
return mapImage(image);
|
|
107
|
+
} catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async createItem(_item: Omit<MediaItem, 'id' | 'createdAt' | 'updatedAt'>): Promise<MediaItem> {
|
|
113
|
+
throw new Error('Imgur upload requires OAuth2 authentication');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async updateItem(_id: string, _updates: Partial<MediaItem>): Promise<MediaItem> {
|
|
117
|
+
throw new Error('Imgur does not support updating images via this API');
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async deleteItem(_id: string): Promise<void> {
|
|
121
|
+
throw new Error('Imgur delete requires OAuth2 authentication');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async search(query: string): Promise<MediaItem[]> {
|
|
125
|
+
const items = await this.client.searchGallery({ query, sort: 'time' });
|
|
126
|
+
return items.flatMap(mapGalleryItem);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ---------- IMediaProvider ----------
|
|
130
|
+
|
|
131
|
+
async getAlbums(): Promise<string[]> {
|
|
132
|
+
// With Client-ID only, we can't list user albums
|
|
133
|
+
// Return empty list — user needs OAuth for personal albums
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async getByAlbum(album: string): Promise<MediaItem[]> {
|
|
138
|
+
if (this.albumCache.size === 0) return [];
|
|
139
|
+
|
|
140
|
+
let albumId: string | undefined;
|
|
141
|
+
for (const [id, a] of this.albumCache) {
|
|
142
|
+
if (a.title === album) {
|
|
143
|
+
albumId = id;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (!albumId) return [];
|
|
148
|
+
|
|
149
|
+
const images = await this.client.getAlbumImages(albumId);
|
|
150
|
+
return images.map(img => mapImage(img, album));
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async getByDateRange(_start: Date, _end: Date): Promise<MediaItem[]> {
|
|
154
|
+
// Imgur doesn't support date range queries — return hot gallery
|
|
155
|
+
const items = await this.client.getGallery({ section: 'hot', sort: 'time', page: 0 });
|
|
156
|
+
return items.flatMap(mapGalleryItem);
|
|
157
|
+
}
|
|
158
|
+
}
|
package/src/index.ts
ADDED