8 Commits

Author SHA1 Message Date
2e5313f651 1.0.6 2024-07-01 23:55:47 +02:00
6b557ba71c fix(medium): Fix various bugs and improve async handling. 2024-07-01 23:55:47 +02:00
93e8ffcc95 1.0.5 2024-07-01 16:43:22 +02:00
46c235b82b fix(core): Fixed module name inconsistencies and documentation links 2024-07-01 16:43:21 +02:00
2499578bb1 1.0.4 2020-11-16 23:04:54 +00:00
fd0dc50d3b fix(core): update 2020-11-16 23:04:54 +00:00
05349ba947 1.0.3 2020-11-16 03:15:49 +00:00
d5ac787d1a fix(core): update 2020-11-16 03:15:49 +00:00
18 changed files with 7317 additions and 294 deletions

View File

@@ -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
View File

@@ -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
View 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

View File

@@ -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

File diff suppressed because it is too large Load Diff

View File

@@ -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

File diff suppressed because it is too large Load Diff

0
readme.hints.md Normal file
View File

View File

@@ -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 | [![pipeline status](https://gitlab.com/mojoio/medium/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/mojoio/medium/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/medium)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@push.rocks/medium)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/mojoio/medium)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/medium)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/medium)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/medium)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@push.rocks/medium)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@push.rocks/medium)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@push.rocks/medium)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage

View File

@@ -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
View 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'
}

View File

@@ -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';

View 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
View 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);
}
}

View 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;
}
}

View File

@@ -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
View File

@@ -0,0 +1,14 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true
},
"exclude": [
"dist_*/**/*.d.ts"
]
}

View File

@@ -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"
}