How to login to wikipedia using mediawiki API through? - javascript

I already have two working bots in Wikipedia, but they use different method.
Currently, I am trying to create a bot using C#. On the API documentation page, they have provided some sample codes in java, PHP, and python. But unfortunately, they haven't provided any sample code in C#. It is the only language I am familiar with.
This is the Wikimedia API:Edit documentation page. Would someone kindly convert the few words from java (or any other language) to C#?
If I could get only this code converted to C#, I can build the rest of the bot by myself.
I asked help on the relevant noticeboard(s) on the wikimedia site(s), but nobody there is familiar with C#. That's why I am now asking this outside of wikipedia.
Thanks a lot in advance
This is the java source-code:
```
/*
edit.js
MediaWiki API Demos
Demo of `Login` module: Sending post request to login
MIT license
*/
var request = require( 'request' ).defaults( { jar: true } ),
url = 'https://test.wikipedia.org/w/api.php';
// Step 1: GET request to fetch login token
function getLoginToken() {
var params = {
action: 'query',
meta: 'tokens',
type: 'login',
format: 'json'
};
request.get( { url: url, qs: params }, function ( error, res, body ) {
var data;
if ( error ) {
return;
}
data = JSON.parse( body );
loginRequest( data.query.tokens.logintoken );
} );
}
// Step 2: POST request to log in.
// Use of main account for login is not
// supported. Obtain credentials via Special:BotPasswords
// (https://www.mediawiki.org/wiki/Special:BotPasswords) for lgname & lgpassword
function loginRequest( loginToken ) {
var params = {
action: 'login',
lgname: 'bot_username',
lgpassword: 'bot_password',
lgtoken: loginToken,
format: 'json'
};
request.post( { url: url, form: params }, function ( error, res, body ) {
if ( error ) {
return;
}
console.log( body );
} );
}
// Start From Step 1
getLoginToken();

I can't go so deeply but I hope you can take a look at this page and find out how to do it
https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpclient?view=net-6.0

You can try something like this
private static async Task LoginRequest(string loginToken)
{
var data = new
{
action = "login",
lgname = "bot_username",
lgpassword = "bot_password",
lgtoken = loginToken,
format = "json"
};
using (var client = new HttpClient())
using (var response = await client.PostAsJsonAsync(LOGIN_URL, data))
{
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject<JObject>(responseString);
Console.WriteLine(responseObject);
}
}
private static async Task<string?> GetLoginToken()
{
var url = $"{LOGIN_URL}?action=query&meta=tokens&type=login&format=json";
using (var client = new HttpClient())
using (var response = await client.GetAsync(url))
{
var responseString = await response.Content.ReadAsStringAsync();
var responseObject = JsonConvert.DeserializeObject<JObject>(responseString);
return responseObject?["query"]?["tokens"]?["logintoken"]?.ToString();
}
}
For converting the response you need
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
You can call these methods from your Main method like this:
const string LOGIN_URL = "https://test.wikipedia.org/w/api.php";
public static async Task Main()
{
var loginToken = await GetLoginToken();
await LoginRequest(loginToken ?? throw new Exception("No login token."));
}

Related

Design pattern for server-side (Flask) rendered variabled in javascript module files

I run a Flask web app where I have a domain model (this example Thing) which properties (e.g. rating) could be updated via a HTTP PATCH request. In my development setup I hard coded the API URL where the request must be made to, but this becomes an open source self hosted app so the URL must be generated & set by Flask.
<script type="module">
import {Thing} from './things.js';
// This string must be available inside the Thing class
// The {{ }} syntax with url_for is Flask/Jinja template style (server side)
const API_URL = '{{ url_for('thing_update', id='{0}' }}';
let stars = document.querySelectorAll('.rating .star')
stars.addEventListener('click', function(el){
// To create an object with object properties, like its id
let thing = new Thing({{ thing.id }});
thing.rating = el.getAttribute('value');
})
</script>
My module looks like:
class Entity {
_API_URL = '';
async update (data) {
let response = await fetch(this._API_URL, {
method: 'PATCH',
headers: {'Content-Type': 'application/json;charset=utf-8'},
body: JSON.stringify(data)
})
if (!response.ok) {
throw new Error(`Server response ${response.status}: `)
} else if (response.status == 204) {
console.debug('Response success, no content')
return true
} else {
let json = await response.json()
console.debug('Response success, body content: ', json)
return json
}
}
}
class Thing extends Entity {
_id = null;
_rating = null;
constructor(id) {
super()
this._id = id
// How to get the proper API URL here???
this._API_URL = super._API_URL.format(id)
}
set rating(value) {
this._rating = value
this.update()
}
async update() {
console.debug('Update rating to ' + this._rating)
super.update({rating: this._rating})
}
}
export {Thing}
I have experience in server side coding (php/python etc) and it just feels "wrong" to set the API URL via the constructor or setter method on the object instance. This should be something as constant on the class level.
However, because I use Flask and don't use javascript for a SPA or something, I struggle how to get this "right". Is there any best practice for this case?

How to send JSON data correctly using Axios to a rails server, to match the required rails params hash correctly?

I am making a GET request to a rails server, and the parameter should look like:
{"where"=>{"producer_id"=>["7"]}
I am making the request from the frontend application which is in Vue, and using Axios for making the request. I am making the request like this:
const data = await this.axios.get('http://localhost:3000/data.json', {
headers: {
'X-User-Token': this.$store.getters.authToken,
'X-User-Username': this.$store.getters.user.username
},
params: {
where: {
producer_id: data.producers
}
}
})
However, in the rails server output it shows that the params were sent like this:
{"where"=>"{\"producer_id\":[\"7\"]}"}
And I don't get the correct data back because of it.
How can I solve this? Why is the second level in params (the where object) being sent as a string?
Turns out that in this case the params have to be serialized https://github.com/axios/axios/issues/738
I used the paramsSerializer function as well to get over this
const data = await this.axios.get('http://localhost:3000/data.json', {
headers: {
'X-User-Token': this.$store.getters.authToken,
'X-User-Username': this.$store.getters.user.username
},
params: {
where: {
producer_id: data.producers
}
},
paramsSerializer: function (params) {
return jQuery.param(params)
}
})
EDIT:
I am now using qs instead of jQuery:
axios.defaults.paramsSerializer = (params) => {
return qs.stringify(params, {arrayFormat: 'brackets'})
}

Getting x from remote sources and mirroring on to a list

Currently I have this, if with the full app it will create a post with my chosen parameters, however I am very new with vue.js, My aim is to be able to have a text file of such (or other way of storing (json etc)) the values, and then having the js script iterate through the file and display as cards, so for example in the file I would have
"Mark", "http://google.com", "5556", "image"
Or of course using json or similar, I'm up to what ever but my problem is, I don't know how to get values from a remote source and mirror it on to the document, can anyone help?, for clarity here's the snippet of code that I'm using
var app = new Vue({
el: '#app',
data: {
keyword: '',
postList: [
new Post(
'Name',
'Link',
'UID',
'Image'),
]
},
});
-- EDIT --
I'd like to thank the user Justin MacArthur for his quick answer, if you or anyone else doesn't mind answering another one of my painfully incompetent questions. This is the function that adds the cards in a nutshell
var Post = function Post(title, link, author, img) {
_classCallCheck(this, Post);
this.title = title;
this.link = link;
this.author = author;
this.img = img;
};
I can now get the data from the text file, meaning I could do, and assuming I have response defined (that being the http request) it'll output the contents of the file, how would I do this for multiple cards- as, as one would guess having a new URL for each variable in each set of four in each card is not just tedious but very inefficient.
new Post(
response.data,
)
The solution you're looking for is any of the AJAX libraries available. Vue used to promote vue-resource though it recently retired that support in favor of Axios
You can follow the instructions on the github page to install it in your app and the usage is very simple.
// Perform a Get on a file/route
axios.get(
'url.to.resource/path',
{
params: {
ID: 12345
}
}
).then(
// Successful response received
function (response) {
console.log(response);
}
).catch(
// Error returned by the server
function (error) {
console.log(error);
}
);
// Perform a Post on a file/route
// Posts don't need the 'params' object as the second argument is sent as the request body
axios.post(
'url.to.resource/path',
{
ID: 12345
}
).then(
// Successful response received
function (response) {
console.log(response);
}
).catch(
// Error returned by the server
function (error) {
console.log(error);
}
);
Obviously in the catch handler you'd have your error handing code, either an alert or message appearing on the page. In the success you could have something along the lines of this.postList.push(new Post(response.data.name, response.data.link, response.data.uid, response.data.image));
To make it even easier you can assign axios to the vue prototype like this:
Vue.prototype.$http = axios
and make use of it using the local vm instance
this.$http.post("url", { data }).then(...);
EDIT:
For your multi-signature function edit it's best to use the arguments keyword. In Javascript the engine defines an arguments array containing the parameters passed to the function.
var Post = function Post(title, link, author, img) {
_classCallCheck(this, Post);
if(arguments.length == 1) {
this.title = title.title;
this.link = title.link;
this.author = title.author;
this.img = title.img;
} else {
this.title = title;
this.link = link;
this.author = author;
this.img = img;
}
};
Be careful not to mutate the arguments list as it's a reference list to the parameters themselves so you can overwrite your variables easily without knowing it.

How should I extract value from Url in Node Js

I recently started programming on nodeJs.
I am using Angular JS, resource to call API's as
demoApp.factory('class', function ($resource) {
return $resource('/class/:classId', { classId: '#_classId' }, {
update: { method: 'PUT' }
});
});
And in Controller, I have delete method as;
// The class object, e {classId: 1, className: "Pro"}
$scope.deleteClass = function (class) {
var deleteObj = new Class();
deleteObj.classId = class.classId;
deleteObj.$delete({classId : deleteObj.classId}, function() {
growl.success("Class deleted successfully.");
$location.path('/');
},function () {
growl.error("Error while deleting Class.");
}
);
};
Using browser, I verified call goes to :
http://localhost:3000/class/1
Now in node Js, How should I extract value from Url,
In server.js
app.use('/class', classController.getApi);
In classController.js
exports.getApi = function(req, resp){
switch(req.method) {
case 'DELETE':
if (req) {
// how to extract 1 from url.
}
else {
httpMsgs.show404(req, resp);
}
break;
I have tried ,
console.log(req.params);
console.log(req.query);
But no luck.
I am seeing
console.log(req._parsedUrl);
query: null,
pathname: '/class/1',
path: '/class/1',
Any help appreciated.
This should be a get call right ? You can use angular $http service, with method as get. Replace your app.use('/class') with app.get('/class', function). Then you can use req.param('classId') to retrieve data. I think it should work.
Try updating your app.use to app.use('/class/:classId'), then try req.params.classId
Try using req.hostname as in:
`http://host/path'
Check this answer.
Tldr;
var url = require('url');
var url_parts = url.parse(request.url, true);
var query = url_parts.query;
Also read the docs on node url.

AngularJS: How to send auth token with $resource requests?

I want to send an auth token when requesting a resource from my API.
I did implement a service using $resource:
factory('Todo', ['$resource', function($resource) {
return $resource('http://localhost:port/todos.json', {port:":3001"} , {
query: {method: 'GET', isArray: true}
});
}])
And I have a service that stores the auth token:
factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
return tokenHandler;
});
I would like to send the token from tokenHandler.get with every request send via the Todo service. I was able to send it by putting it into the call of a specific action. For example this works:
Todo.query( {access_token : tokenHandler.get()} );
But I would prefer to define the access_token as a parameter in the Todo service, as it has to be sent with every call. And to improve DRY.
But everything in the factory is executed only once, so the access_token would have to be available before defining the factory and it cant change afterwards.
Is there a way to put a dynamically updated request parameter in the service?
Thanks to Andy Joslin. I picked his idea of wrapping the resource actions. The service for the resource looks like this now:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
return resource;
}])
As you can see the resource is defined the usual way in the first place. In my example this includes a custom action called update. Afterwards the resource is overwritten by the return of the tokenHandler.wrapAction() method which takes the resource and an array of actions as parameters.
As you would expect the latter method actually wraps the actions to include the auth token in every request and returns a modified resource. So let's have a look at the code for that:
.factory('TokenHandler', function() {
var tokenHandler = {};
var token = "none";
tokenHandler.set = function( newToken ) {
token = newToken;
};
tokenHandler.get = function() {
return token;
};
// wrap given actions of a resource to send auth token with every
// request
tokenHandler.wrapActions = function( resource, actions ) {
// copy original resource
var wrappedResource = resource;
for (var i=0; i < actions.length; i++) {
tokenWrapper( wrappedResource, actions[i] );
};
// return modified copy of resource
return wrappedResource;
};
// wraps resource action to send request with auth token
var tokenWrapper = function( resource, action ) {
// copy original action
resource['_' + action] = resource[action];
// create new action wrapping the original and sending token
resource[action] = function( data, success, error){
return resource['_' + action](
angular.extend({}, data || {}, {access_token: tokenHandler.get()}),
success,
error
);
};
};
return tokenHandler;
});
As you can see the wrapActions() method creates a copy of the resource from it's parameters and loops through the actions array to call another function tokenWrapper() for every action. In the end it returns the modified copy of the resource.
The tokenWrappermethod first of all creates a copy of preexisting resource action. This copy has a trailing underscore. So query()becomes _query(). Afterwards a new method overwrites the original query() method. This new method wraps _query(), as suggested by Andy Joslin, to provide the auth token with every request send through that action.
The good thing with this approach is, that we still can use the predefined actions which come with every angularjs resource (get, query, save, etc.), without having to redefine them. And in the rest of the code (within controllers for example) we can use the default action name.
Another way is to use an HTTP interceptor which replaces a "magic" Authorization header with the current OAuth token. The code below is OAuth specific, but remedying that is a simple exercise for the reader.
// Injects an HTTP interceptor that replaces a "Bearer" authorization header
// with the current Bearer token.
module.factory('oauthHttpInterceptor', function (OAuth) {
return {
request: function (config) {
// This is just example logic, you could check the URL (for example)
if (config.headers.Authorization === 'Bearer') {
config.headers.Authorization = 'Bearer ' + btoa(OAuth.accessToken);
}
return config;
}
};
});
module.config(function ($httpProvider) {
$httpProvider.interceptors.push('oauthHttpInterceptor');
});
I really like this approach:
http://blog.brunoscopelliti.com/authentication-to-a-restful-web-service-in-an-angularjs-web-app
where the token is always automagically sent within the request header without the need of a wrapper.
// Define a new http header
$http.defaults.headers.common['auth-token'] = 'C3PO R2D2';
You could create a wrapper function for it.
app.factory('Todo', function($resource, TokenHandler) {
var res= $resource('http://localhost:port/todos.json', {
port: ':3001',
}, {
_query: {method: 'GET', isArray: true}
});
res.query = function(data, success, error) {
//We put a {} on the first parameter of extend so it won't edit data
return res._query(
angular.extend({}, data || {}, {access_token: TokenHandler.get()}),
success,
error
);
};
return res;
})
I had to deal with this problem as well. I don't think if it is an elegant solution but it works and there are 2 lines of code :
I suppose you get your token from your server after an authentication in SessionService for instance. Then, call this kind of method :
angular.module('xxx.sessionService', ['ngResource']).
factory('SessionService', function( $http, $rootScope) {
//...
function setHttpProviderCommonHeaderToken(token){
$http.defaults.headers.common['X-AUTH-TOKEN'] = token;
}
});
After that all your requests from $resource and $http will have token in their header.
Another solution would be to use resource.bind(additionalParamDefaults), that return a new instance of the resource bound with additional parameters
var myResource = $resource(url, {id: '#_id'});
var myResourceProtectedByToken = myResource.bind({ access_token : function(){
return tokenHandler.get();
}});
return myResourceProtectedByToken;
The access_token function will be called every time any of the action on the resource is called.
I might be misunderstanding all of your question (feel free to correct me :) ) but to specifically address adding the access_token for every request, have you tried injecting the TokenHandler module into the Todo module?
// app
var app = angular.module('app', ['ngResource']);
// token handler
app.factory('TokenHandler', function() { /* ... */ });
// inject the TokenHandler
app.factory('Todo', function($resource, TokenHandler) {
// get the token
var token = TokenHandler.get();
// and add it as a default param
return $resource('http://localhost:port/todos.json', {
port: ':3001',
access_token : token
});
})
You can call Todo.query() and it will append ?token=none to your URL. Or if you prefer to add a token placeholder you can of course do that too:
http://localhost:port/todos.json/:token
Hope this helps :)
Following your accepted answer, I would propose to extend the resource in order to set the token with the Todo object:
.factory('Todo', ['$resource', 'TokenHandler', function($resource, tokenHandler) {
var resource = $resource('http://localhost:port/todos/:id', {
port:":3001",
id:'#id'
}, {
update: {method: 'PUT'}
});
resource = tokenHandler.wrapActions( resource, ["query", "update"] );
resource.prototype.setToken = function setTodoToken(newToken) {
tokenHandler.set(newToken);
};
return resource;
}]);
In that way there is no need to import the TokenHandler each time you want to use the Todo object and you can use:
todo.setToken(theNewToken);
Another change I would do is to allow default actions if they are empty in wrapActions:
if (!actions || actions.length === 0) {
actions = [];
for (i in resource) {
if (i !== 'bind') {
actions.push(i);
}
}
}

Categories

Resources