Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
2e5313f651 | |||
6b557ba71c | |||
93e8ffcc95 | |||
46c235b82b | |||
2499578bb1 | |||
fd0dc50d3b | |||
05349ba947 | |||
d5ac787d1a |
@@ -120,7 +120,7 @@ pages:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci node install lts
|
||||
- npmci command npm install -g @gitzone/tsdoc
|
||||
- npmci command npm install -g @git.zone/tsdoc
|
||||
- npmci npm prepare
|
||||
- npmci npm install
|
||||
- npmci command tsdoc
|
||||
|
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
@@ -8,7 +8,7 @@
|
||||
"args": [
|
||||
"${relativeFile}"
|
||||
],
|
||||
"runtimeArgs": ["-r", "@gitzone/tsrun"],
|
||||
"runtimeArgs": ["-r", "@git.zone/tsrun"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
@@ -20,7 +20,7 @@
|
||||
"args": [
|
||||
"test/test.ts"
|
||||
],
|
||||
"runtimeArgs": ["-r", "@gitzone/tsrun"],
|
||||
"runtimeArgs": ["-r", "@git.zone/tsrun"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"protocol": "inspector",
|
||||
"internalConsoleOptions": "openOnSessionStart"
|
||||
|
27
changelog.md
Normal file
27
changelog.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
## 2024-07-01 - 1.0.6 - fix(medium)
|
||||
Fix various bugs and improve async handling.
|
||||
|
||||
- Update @push.rocks/qenv dependency to ^6.0.5
|
||||
- Fix bug in test: await getEnvVarOnDemand
|
||||
- Improve error handling in getAccountInfo
|
||||
- Simplify fetching publications by refactoring methods in MediumPublication and MediumAccount classes
|
||||
|
||||
## 2024-07-01 - 1.0.5 - fix(core)
|
||||
Fixed module name inconsistencies and documentation links
|
||||
|
||||
- Corrected module names in package.json and VSCode launch configuration.
|
||||
- Updated npm package name from '@pushrocks' to '@push.rocks' in readme.md and package.json.
|
||||
- Fixed test imports and improved test scripts.
|
||||
- Added updated TypeScript configuration file tsconfig.json.
|
||||
|
||||
## 2020-11-17 - 1.0.4 - No significant changes
|
||||
|
||||
No significant changes made in this version update.
|
||||
|
||||
## 2020-11-16 - 1.0.3 to 1.0.1 - Core Updates
|
||||
Routine maintenance and updates in the core functionality.
|
||||
|
||||
- fix(core): update
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"gitscope": "mojoio",
|
||||
"gitrepo": "medium",
|
||||
"shortDescription": "an unofficial medium.com API package",
|
||||
"npmPackagename": "@pushrocks/medium",
|
||||
"npmPackagename": "@push.rocks/medium",
|
||||
"license": "MIT",
|
||||
"projectDomain": "mojo.io"
|
||||
}
|
||||
|
510
package-lock.json
generated
510
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
24
package.json
24
package.json
@@ -1,26 +1,30 @@
|
||||
{
|
||||
"name": "@mojoio/medium",
|
||||
"version": "1.0.2",
|
||||
"version": "1.0.6",
|
||||
"private": false,
|
||||
"description": "an unofficial medium.com API package",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"author": "Lossless GmbH",
|
||||
"type": "module",
|
||||
"author": "Task Venture Capital GmbH",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --web)",
|
||||
"build": "(tsbuild --web)"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@gitzone/tsbuild": "^2.1.25",
|
||||
"@gitzone/tsbundle": "^1.0.78",
|
||||
"@gitzone/tstest": "^1.0.44",
|
||||
"@pushrocks/tapbundle": "^3.2.9",
|
||||
"@types/node": "^14.11.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.15.0"
|
||||
"@git.zone/tsbuild": "^2.1.25",
|
||||
"@git.zone/tsbundle": "^2.0.8",
|
||||
"@git.zone/tsrun": "^1.2.49",
|
||||
"@git.zone/tstest": "^1.0.44",
|
||||
"@push.rocks/tapbundle": "^5.0.8",
|
||||
"@types/node": "^20.14.9"
|
||||
},
|
||||
"dependencies": {
|
||||
"@push.rocks/qenv": "^6.0.5",
|
||||
"@push.rocks/smartpromise": "^4.0.2",
|
||||
"@push.rocks/smartrequest": "^2.0.15"
|
||||
},
|
||||
"dependencies": {},
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
|
6711
pnpm-lock.yaml
generated
Normal file
6711
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
readme.hints.md
Normal file
0
readme.hints.md
Normal file
10
readme.md
10
readme.md
@@ -2,7 +2,7 @@
|
||||
an unofficial medium.com API package
|
||||
|
||||
## Availabililty and Links
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/medium)
|
||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@push.rocks/medium)
|
||||
* [gitlab.com (source)](https://gitlab.com/mojoio/medium)
|
||||
* [github.com (source mirror)](https://github.com/mojoio/medium)
|
||||
* [docs (typedoc)](https://mojoio.gitlab.io/medium/)
|
||||
@@ -13,14 +13,14 @@ Status Category | Status Badge
|
||||
-- | --
|
||||
GitLab Pipelines | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
TypeScript Support | [](https://lossless.cloud)
|
||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
Code Style | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
Platform support | [](https://lossless.cloud) [](https://lossless.cloud)
|
||||
|
||||
## Usage
|
||||
|
45
test/test.ts
45
test/test.ts
@@ -1,8 +1,47 @@
|
||||
import { expect, tap } from '@pushrocks/tapbundle';
|
||||
import * as medium from '../ts/index';
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
import * as medium from '../ts/index.js';
|
||||
|
||||
import {Qenv} from '@push.rocks/qenv';
|
||||
|
||||
const testQenv = new Qenv('./', './.nogit/');
|
||||
let testMediumAccount: medium.MediumAccount;
|
||||
|
||||
tap.test('first test', async () => {
|
||||
console.log(medium.standardExport);
|
||||
testMediumAccount = new medium.MediumAccount(await testQenv.getEnvVarOnDemand('MEDIUM_API_TOKEN'));
|
||||
expect(testMediumAccount).toBeInstanceOf(medium.MediumAccount);
|
||||
});
|
||||
|
||||
tap.test('should get me info', async () => {
|
||||
const result = await testMediumAccount.getAccountInfo();
|
||||
console.log(result);
|
||||
});
|
||||
|
||||
tap.test('should get publications', async () => {
|
||||
const result = await testMediumAccount.getAllPublications();
|
||||
// console.log(result);
|
||||
});
|
||||
|
||||
tap.test('should get own publications', async () => {
|
||||
const result = await testMediumAccount.getOwnPublications();
|
||||
// console.log(result);
|
||||
});
|
||||
|
||||
tap.test('should get a publication by name', async () => {
|
||||
const result = await testMediumAccount.getPublicationByName('mojoio-test');
|
||||
console.log(result);
|
||||
});
|
||||
|
||||
tap.test('should create a post', async () => {
|
||||
const mojoioTestPublication = await testMediumAccount.getPublicationByName('mojoio-test');
|
||||
const newPost = await mojoioTestPublication.createPost({
|
||||
title: 'a test title',
|
||||
contentFormat: 'html',
|
||||
canonicalUrl: 'https://mojo.io/testarticle',
|
||||
content: '<h1>hello</1> So awesome',
|
||||
publishStatus: 'draft',
|
||||
tags: []
|
||||
});
|
||||
console.log(newPost);
|
||||
})
|
||||
|
||||
tap.start();
|
||||
|
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* autocreated commitinfo by @push.rocks/commitinfo
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@mojoio/medium',
|
||||
version: '1.0.6',
|
||||
description: 'an unofficial medium.com API package'
|
||||
}
|
@@ -1,3 +1,3 @@
|
||||
import * as plugins from './medium.plugins';
|
||||
|
||||
export let standardExport = 'Hi there! :) This is an exported string';
|
||||
export * from './medium.classes.account.js';
|
||||
export * from './medium.classes.publication.js';
|
||||
export * from './medium.classes.post.js';
|
||||
|
125
ts/medium.classes.account.ts
Normal file
125
ts/medium.classes.account.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
import { MediumPublication } from './medium.classes.publication.js';
|
||||
import * as plugins from './medium.plugins.js';
|
||||
|
||||
export interface IMediumAccountData {
|
||||
id: string;
|
||||
username: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a Medium account with various functionalities to interact with Medium's API.
|
||||
*/
|
||||
export class MediumAccount implements IMediumAccountData {
|
||||
// INSTANCE
|
||||
private accessToken: string;
|
||||
public readyDeferred = plugins.smartpromise.defer();
|
||||
public baseApiDomain = 'https://api.medium.com/v1';
|
||||
|
||||
id: string;
|
||||
username: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the MediumAccount class.
|
||||
* @param accessTokenArg - The access token for the Medium API.
|
||||
*/
|
||||
constructor(accessTokenArg: string) {
|
||||
this.accessToken = accessTokenArg;
|
||||
this.getAccountInfo().then((dataArg) => {
|
||||
if (dataArg) {
|
||||
Object.assign(this, dataArg);
|
||||
this.readyDeferred.resolve();
|
||||
} else {
|
||||
this.readyDeferred.reject('Failed to fetch account info.');
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('Error fetching account info:', error);
|
||||
this.readyDeferred.reject(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the account information from Medium.
|
||||
* @returns A promise that resolves to the account data.
|
||||
*/
|
||||
public async getAccountInfo(): Promise<IMediumAccountData | undefined> {
|
||||
try {
|
||||
const result = await this.request('/me', 'GET');
|
||||
console.log(result.statusCode);
|
||||
const accountData: IMediumAccountData = result.body.data;
|
||||
return accountData;
|
||||
} catch (error) {
|
||||
console.error('Error in getAccountInfo:', error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all publications associated with this account.
|
||||
* @returns A promise that resolves to an array of MediumPublication objects.
|
||||
*/
|
||||
public async getAllPublications(): Promise<MediumPublication[]> {
|
||||
const result = await this.request(`/users/${this.id}/publications`, 'GET');
|
||||
return result.data.map((pub: any) => new MediumPublication(this, pub));
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches all publications authored by this account.
|
||||
* @returns A promise that resolves to an array of MediumPublication objects.
|
||||
*/
|
||||
public async getOwnPublications(): Promise<MediumPublication[]> {
|
||||
const allPublications = await this.getAllPublications();
|
||||
const ownPublications: MediumPublication[] = [];
|
||||
|
||||
for (const publication of allPublications) {
|
||||
const response = await this.request(`/publications/${publication.id}/contributors`, 'GET');
|
||||
const contributors: { publicationId: string; userId: string; role: string; }[] = response.data;
|
||||
|
||||
if (contributors.some(contributor => contributor.userId === this.id)) {
|
||||
ownPublications.push(publication);
|
||||
}
|
||||
}
|
||||
|
||||
return ownPublications;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a publication by its name.
|
||||
* @param nameArg - The name of the publication.
|
||||
* @returns A promise that resolves to the MediumPublication object.
|
||||
*/
|
||||
public async getPublicationByName(nameArg: string): Promise<MediumPublication | undefined> {
|
||||
const publications = await this.getAllPublications();
|
||||
return publications.find(publication => publication.name === nameArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes an authenticated request to the Medium API.
|
||||
* @param routeArg - The API route to request.
|
||||
* @param methodArg - The HTTP method to use for the request.
|
||||
* @param payloadArg - Optional payload for POST requests.
|
||||
* @returns A promise that resolves to the API response.
|
||||
*/
|
||||
public async request(routeArg: string, methodArg: 'POST' | 'GET', payloadArg?: any): Promise<any> {
|
||||
try {
|
||||
const response = await plugins.smartrequest.request(`${this.baseApiDomain}${routeArg}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
Accept: 'application/json',
|
||||
'Accept-Charset': 'utf-8',
|
||||
},
|
||||
method: methodArg,
|
||||
keepAlive: false,
|
||||
requestBody: payloadArg ? JSON.stringify(payloadArg) : null
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error('Error in request:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
35
ts/medium.classes.post.ts
Normal file
35
ts/medium.classes.post.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { MediumPublication } from './medium.classes.publication.js';
|
||||
import * as plugins from './medium.plugins.js';
|
||||
|
||||
export interface IPostData {
|
||||
title: string;
|
||||
contentFormat: 'html' | 'markdown';
|
||||
content: string;
|
||||
canonicalUrl: string;
|
||||
tags: string[];
|
||||
publishStatus: 'public' | 'draft' | 'unlisted';
|
||||
}
|
||||
|
||||
export class MediumPost implements IPostData {
|
||||
// STATIC
|
||||
public static async createPost(mediumPublication: MediumPublication, data: IPostData): Promise<MediumPost> {
|
||||
const response = await mediumPublication.mediumAccountRef.request(`/publications/${mediumPublication.id}/posts`, 'POST', data);
|
||||
const post = new MediumPost(mediumPublication, response.data);
|
||||
return post;
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
mediumPublicationRef: MediumPublication;
|
||||
|
||||
title: string;
|
||||
contentFormat: 'html' | 'markdown';
|
||||
content: string;
|
||||
canonicalUrl: string;
|
||||
tags: string[];
|
||||
publishStatus: 'public' | 'draft' | 'unlisted';
|
||||
|
||||
constructor(mediumPublication: MediumPublication, data: IPostData) {
|
||||
this.mediumPublicationRef = mediumPublication;
|
||||
Object.assign(this, data);
|
||||
}
|
||||
}
|
62
ts/medium.classes.publication.ts
Normal file
62
ts/medium.classes.publication.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import { MediumAccount } from './medium.classes.account.js';
|
||||
import { type IPostData, MediumPost } from './medium.classes.post.js';
|
||||
import * as plugins from './medium.plugins.js';
|
||||
|
||||
export interface IMediumPublication {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
}
|
||||
|
||||
export class MediumPublication implements IMediumPublication {
|
||||
// STATIC
|
||||
public static async getAllPublications(mediumAccount: MediumAccount): Promise<MediumPublication[]> {
|
||||
await mediumAccount.readyDeferred.promise;
|
||||
const response = await mediumAccount.request(`/users/${mediumAccount.id}/publications`, 'GET');
|
||||
const publicationsDataArray: IMediumPublication[] = response.data;
|
||||
return publicationsDataArray.map(publicationData => new MediumPublication(mediumAccount, publicationData));
|
||||
}
|
||||
|
||||
public static async getOwnPublications(mediumAccount: MediumAccount): Promise<MediumPublication[]> {
|
||||
await mediumAccount.readyDeferred.promise;
|
||||
const allPublications = await this.getAllPublications(mediumAccount);
|
||||
const ownPublications: MediumPublication[] = [];
|
||||
|
||||
for (const publication of allPublications) {
|
||||
const response = await mediumAccount.request(`/publications/${publication.id}/contributors`, 'GET');
|
||||
const contributors: { publicationId: string; userId: string; role: string; }[] = response.data;
|
||||
|
||||
if (contributors.some(contributor => contributor.userId === mediumAccount.id)) {
|
||||
ownPublications.push(publication);
|
||||
}
|
||||
}
|
||||
|
||||
return ownPublications;
|
||||
}
|
||||
|
||||
public static async getPublicationByName(mediumAccount: MediumAccount, publicationName: string): Promise<MediumPublication | undefined> {
|
||||
const publications = await this.getAllPublications(mediumAccount);
|
||||
return publications.find(publication => publication.name === publicationName);
|
||||
}
|
||||
|
||||
// INSTANCE
|
||||
public mediumAccountRef: MediumAccount;
|
||||
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
url: string;
|
||||
imageUrl: string;
|
||||
|
||||
constructor(mediumAccount: MediumAccount, data: IMediumPublication) {
|
||||
this.mediumAccountRef = mediumAccount;
|
||||
Object.assign(this, data);
|
||||
}
|
||||
|
||||
public async createPost(data: IPostData): Promise<MediumPost> {
|
||||
const result = await MediumPost.createPost(this, data);
|
||||
return result;
|
||||
}
|
||||
}
|
@@ -1,2 +1,7 @@
|
||||
const removeme = {};
|
||||
export { removeme };
|
||||
import * as smartpromise from '@push.rocks/smartpromise';
|
||||
import * as smartrequest from '@push.rocks/smartrequest';
|
||||
|
||||
export {
|
||||
smartpromise,
|
||||
smartrequest
|
||||
};
|
||||
|
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
17
tslint.json
17
tslint.json
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||
"rules": {
|
||||
"semicolon": [true, "always"],
|
||||
"no-console": false,
|
||||
"ordered-imports": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"member-ordering": {
|
||||
"options":{
|
||||
"order": [
|
||||
"static-method"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultSeverity": "warning"
|
||||
}
|
Reference in New Issue
Block a user