1

I'm operating a bot on Wikipedia using npm mwbot, and planning to migrate to npm mwn. This is because you need a "token" to edit pages on Wikipedia, and this can expire after a while so you need to prepare your own countermeasures against this if you use mwbot, but it seems like mwn handles this issue on its own.

When you use mwn, you first need to initialize a bot instance as documented on the turotial:

const bot = await mwn.init(myUserInfo);

Then your token is stored in the bot instance and you can for example edit a page using:

const result = await bot.edit('Page title', () => {text: 'text'});

So, basically you want to share the initialized bot instance across modules. I believe it'd be easiest to declare a global variable like so:

// bot.js (main script)
const {mwn} = require('mwn');
const {my} = require('./modules/my');

(async() => {
    global.mw = await mwn.init(my.userinfo);
    const {t} = require('./modules/test');
    t();
})();
// modules/test.js
/* global mw */

exports.t = async () => {
    const content = await mw.read('Main page');
    console.log(content);
    return content;
};

I'm currently using JavaScript, but will hopefully migrate to TypeScript (although I'm new to it) because I feel like it'd be useful in developing some of my modules. But I'm stuck with how I should use the initialized bot instance across modules in TypeScript.

-+-- dist (<= where ts files are compiled into)
 +-- src
     +-- types
         +-- global.d.ts
     +-- bot.ts
     +-- my.ts
// bot.ts
import {mwn} from 'mwn';
import {my} from './my';
(async() => {
    global.mw = await mwn.init(my.userinfo);
})();

// global.d.ts
import {mwn} from 'mwn';

declare global {
    // eslint-disable-next-line no-var
    var mw: mwn;
}

This doesn't work and returns "Element implicitly has an 'any' type because type 'typeof globalThis' has no index signature. (at mw in global.mw)".

This is probably a naive question but any help would be appreciated.

Edit:

Thanks @CertainPerformance, that's a simple and easy approach. Actually, I once tried the same kind of an approach:

export const init = async () => {
    if (typeof mw === 'undefined') {
        return Promise.resolve(mw);
    } else {
        return mwn.init(my.userinfo).then((res) => {
            mw = res;
            return mw;
        });
    }
}

But I was like "init().then() in every module?"... don't know why I didn't come up with just exporting the initialized mwn instance.

Anyway, is it like the entry point file should be a .js file? I've been trying with a .ts file and this is one thing that's been giving me a headache. I'm using ts-node or nodemon to auto-compile .ts files, but without "type: module", "Cannot use import statement outside a module" error occurs and with that included, "TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"" occurs. How do you tell a given file should be a .js or .ts file?

Edit2:

Just making a note: The error I mentioned above was caused by not having "module": "CommonJS" in my tsconfig.json, as I commented to CertainPerformance's answer below.

1
  • No, don't use a global variable! Just have one module that creates and exports the bot instance, then import that module everywhere you need the bot. Commented Dec 16, 2022 at 4:10

1 Answer 1

3

One of the main benefits of modules is the ability to drop dependencies on global variables. Rather than going back on that and assigning a global anyway, a better approach that happens to solve your problem as well would be to have a module that exports two functions:

  • One that initializes the asynchronous mwn
  • One that returns mwn when called
// mw.ts
import {mwn} from 'mwn';
import {my} from './my';

let mw: mwn;

export const init = async () => {
    mw = await mwn.init(my.userinfo);
};
export const getMw = () => mw;

Then it can be consumed by other modules quite naturally, barely requiring any typing at all:

// modules/index.ts
// Entry point
import { init } from './mw';
import { test } from './test';

init()
  .then(() => {
    test();
  })
  // .catch(handleErrors);
// modules/test.ts
import { getMw } from './bot';
export const test = () => {
  const mw = getMw();
  // anything else that depends on mw
};

The above could be a reasonable approach if mw is used in many places across your app and you don't think that passing it around everywhere would be maintainable.

If you could pass it around everywhere, that would have even less of a code smell, though.

// initMw.ts
import {mwn} from 'mwn';
import {my} from './my';
export const initMw = () => mwn.init(my.userinfo);
// modules/index.ts
// Entry point
import { initMw } from './initMw';
import { test } from './test';

initMw()
  .then((mw) => {
    test(mw);
  })
  // .catch(handleErrors);
// modules/test.ts
import { mwn } from 'mwn';
export const test = (mw: mwn) => {
  // anything that depends on mw
};

Initialize it once, then pass it down (synchronously) everywhere it's needed - that's the approach I'd prefer.

You could put the mwn type in a global d.ts file to avoid having to add import { mwn } from 'mwn'; to every module if you wanted. (Yes, it's somewhat of a global variable, but it's a type rather than a value, so it's arguably less of a problem.)

Sign up to request clarification or add additional context in comments.

4 Comments

Since you're using TypeScript, it'd make the most sense for your entire app to be written in TypeScript and .ts files. The entry point should be .ts. You must transpile TypeScript to JavaScript before it can be run. Since this is Node, my preferred approach would be to use ts-node.
Sorry I moved my comments to Op. Currently, I have "nodemon src/bot.ts" in my package.json, but an error occurs in the entry point at "import" in the very first line. I'm probably doing something wrong, but do you have any idea?
That should work if you have ts-node installed, see: blog.logrocket.com/configuring-nodemon-with-typescript Try it without Nodemon to start to take that out of the equation while debugging. ts-node index.ts
Just found out why. I had "module: ESNext" in my tsconfig.json, and after changing it to "commonjs", the error went away. I appreciate your help :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.