Not sure why facebook refered me here but anyhow, let me ask the question. I have a group on facebook with over 4000 members. I want to delete old members that are not active on the group anymore. Is there a way to select multiple users for deletion?
How to get a list of ID's of your facebook group to avoid removal of active users, it's used to reduce as well a group from 10.000 to 5000 members as well as removal of not active members or old members "You will risk removing some few viewers of the group" "remember to open all comments while you browse down the page":
You will need to have Notepad++ for this process:
After you save the HTML. Remove all information before of document:
"div id=contentArea" to
"div id=bottomContent"
to avoid using messenger ID's,
somehow script will run problems if you have ID's by blocked users.
As well as a different example of how to parse as well as text and code out of HTML. And a range of numbers if they are with 2 digits up to 30.
You can try this to purge the list of member_id= and with them along with numbers from 2 to up to 30 digits long. Making sure only numbers and whole "member_id=12456" or "member_id=12" is written to file. Later you can replace out the member_id= with blanking it out. Then copy the whole list to a duplicate scanner or remove duplicates. And have all unique ID's. And then use it in the Java code below.
"This is used to purge all Facebook user ID's by a group out of a single HTML file after you saved it scrolling down the group"
Find: (member_id=\d{2,30})|.
Replace: $1
You should use the "Regular Expression" and ". matches newline" on above code.
Second use the Extended Mode on this mode:
Find: member_id=
Replace: \n
That will make new lines and with an easy way to remove all Fx0 in all lines to manually remove all the extra characters that come in buggy Notepad++
Then you can easily as well then remove all duplicates. Connect all lines into one single space between. The option was to use this tool which aligns the whole text with one space between each ID: https://www.tracemyip.org/tools/remove-duplicate-words-in-text/
As well then again "use Normal option in Notepad++":
Find: "ONE SPACE"
Replace ','
Remember to add ' to beginning and end
Then you can copy the whole line into your java edit and then remove all members who are not active. If you though use a whole scrolled down HTML of a page. ['21','234','124234'] <-- remember right characters from beginning. Extra secure would be to add your ID's to the beginning.
You put your code into this line:
var excludedFbIds = ['1234','11223344']; // make sure each id is a string!
The facebook group removal java code is on the user that as well posted to this solution.
var deleteAllGroupMembers = (function () {
var deleteAllGroupMembers = {};
// the facebook ids of the users that will not be removed.
// IMPORTANT: bobby.leopold.5,LukeBryannNuttTx!
var excludedFbIds = ['1234','11223344']; // make sure each id is a string!
var usersToDeleteQueue = [];
var scriptEnabled = false;
var processing = false;
deleteAllGroupMembers.start = function() {
scriptEnabled = true;
deleteAll();
};
deleteAllGroupMembers.stop = function() {
scriptEnabled = false;
};
function deleteAll() {
if (scriptEnabled) {
queueMembersToDelete();
processQueue();
}
}
function queueMembersToDelete() {
var adminActions = document.getElementsByClassName('adminActions');
console.log(excludedFbIds);
for(var i=0; i<adminActions.length; i++) {
var gearWheelIconDiv = adminActions[i];
var hyperlinksInAdminDialog = gearWheelIconDiv.getElementsByTagName('a');
var fbMemberId = gearWheelIconDiv.parentNode.parentNode.id.replace('member_','');
var fbMemberName = getTextFromElement(gearWheelIconDiv.parentNode.parentNode.getElementsByClassName('fcb')[0]);
if (excludedFbIds.indexOf(fbMemberId) != -1) {
console.log("SKIPPING "+fbMemberName+' ('+fbMemberId+')');
continue;
} else {
usersToDeleteQueue.push({'memberId': fbMemberId, 'gearWheelIconDiv': gearWheelIconDiv});
}
}
}
function processQueue() {
if (!scriptEnabled) {
return;
}
if (usersToDeleteQueue.length > 0) {
removeNext();
setTimeout(function(){
processQueue();
},1000);
} else {
getMore();
}
}
function removeNext() {
if (!scriptEnabled) {
return;
}
if (usersToDeleteQueue.length > 0) {
var nextElement = usersToDeleteQueue.pop();
removeMember(nextElement.memberId, nextElement.gearWheelIconDiv);
}
}
function removeMember(memberId, gearWheelIconDiv) {
if (processing) {
return;
}
var gearWheelHref = gearWheelIconDiv.getElementsByTagName('a')[0];
gearWheelHref.click();
processing = true;
setTimeout(function(){
var popupRef = gearWheelHref.id;
var popupDiv = getElementByAttribute('data-ownerid',popupRef);
var popupLinks = popupDiv.getElementsByTagName('a');
for(var j=0; j<popupLinks.length; j++) {
if (popupLinks[j].getAttribute('href').indexOf('remove.php') !== -1) {
// this is the remove link
popupLinks[j].click();
setTimeout(function(){
var confirmButton = document.getElementsByClassName('layerConfirm uiOverlayButton selected')[0];
var errorDialog = getElementByAttribute('data-reactid','.4.0');
if (confirmButton != null) {
if (canClick(confirmButton)) {
confirmButton.click();
} else {
console.log('This should not happen memberid: '+memberId);
5/0;
console.log(gearWheelIconDiv);
}
}
if (errorDialog != null) {
console.log("Error while removing member "+memberId);
errorDialog.getElementsByClassName('selected layerCancel autofocus')[0].click();
}
processing = false;
},700);
continue;
}
}
},500);
}
function canClick(el) {
return (typeof el != 'undefined') && (typeof el.click != 'undefined');
}
function getMore() {
processing = true;
more = document.getElementsByClassName("pam uiBoxLightblue uiMorePagerPrimary");
if (typeof more != 'undefined' && canClick(more[0])) {
more[0].click();
setTimeout(function(){
deleteAll();
processing = false;
}, 2000);
} else {
deleteAllGroupMembers.stop();
}
}
function getTextFromElement(element) {
var text = element.textContent;
return text;
}
function getElementByAttribute(attr, value, root) {
root = root || document.body;
if(root.hasAttribute(attr) && root.getAttribute(attr) == value) {
return root;
}
var children = root.children,
element;
for(var i = children.length; i--; ) {
element = getElementByAttribute(attr, value, children[i]);
if(element) {
return element;
}
}
return null;
}
return deleteAllGroupMembers;
})();
deleteAllGroupMembers.start();
// stop the script by entering this in the console: deleteAllGroupMembers.stop();
Use this in Chrome or Firefox Javascript control panel.
Related
Hi i am trying to add two new fields to the Barcode mobile view. I went through the default js code of odoo, but didn't find a way to add my custome fields to it.
Here is the default code
stock_barcode/static/src/js/client_action/lines_widget.js
init: function (parent, page, pageIndex, nbPages) {
this._super.apply(this, arguments);
this.page = page; #don't know where this argument page coming from in argument list.
this.pageIndex = pageIndex;
this.nbPages = nbPages;
this.mode = parent.mode;
this.groups = parent.groups;
this.model = parent.actionParams.model;
this.show_entire_packs = parent.show_entire_packs;
this.displayControlButtons = this.nbPages > 0 && parent._isControlButtonsEnabled();
this.displayOptionalButtons = parent._isOptionalButtonsEnabled();
this.isPickingRelated = parent._isPickingRelated();
this.isImmediatePicking = parent.isImmediatePicking ? true : false;
this.sourceLocations = parent.sourceLocations;
this.destinationLocations = parent.destinationLocations;
// detect if touchscreen (more complicated than one would expect due to browser differences...)
this.istouchSupported = 'ontouchend' in document ||
'ontouchstart' in document ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
},
In _renderLines function,
_renderLines: function () {
//Skipped some codes here
// Render and append the lines, if any.
var $body = this.$el.filter('.o_barcode_lines');
console.log('this model',this.model);
if (this.page.lines.length) {
var $lines = $(QWeb.render('stock_barcode_lines_template', {
lines: this.getProductLines(this.page.lines),
packageLines: this.getPackageLines(this.page.lines),
model: this.model,
groups: this.groups,
isPickingRelated: this.isPickingRelated,
istouchSupported: this.istouchSupported,
}));
$body.prepend($lines);
for (const line of $lines) {
if (line.dataset) {
this._updateIncrementButtons($(line));
}
}
$lines.on('click', '.o_edit', this._onClickEditLine.bind(this));
$lines.on('click', '.o_package_content', this._onClickTruckLine.bind(this));
}
In the above code, you can see this.page.lines field, i need to add my custom two more fields.
Actually it's dead-end for me.
Any solution?
I'm trying to get information (true/false) from AsyncStorage in a function and create a string which is importent to fetch data in the next step. My problem is, the function is not finished until the string is required.
I tried many solutions from the internet like async function and await getItem or .done() or .then(), but none worked out for me.
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return this.state.channels;
_fetchData() {
var channel = this._getFetchData();
console.log("channel required: " + channel);
}
The current behaviour is that the console displays first "channel required: " than "channel: channel_id0".
Aspects in your question are unclear:
You don't say when this.state.firstValue is set, and how that relates to what you are trying to accomplish.
You have a for-loop where you could be setting the same value multiple times.
You mutate the state rather than set it. This is not good, see this SO question for more on that.
There are somethings we can do to make your code easier to understand. Below I will show a possible refactor. Explaining what I am doing at each step. I am using async/await because it can lead to much tidier and easier to read code, rather than using promises where you can get lost in callbacks.
Get all the keys from AsyncStorage
Make sure that there is a value for all the keys.
Filter the keys so that we only include the ones that do not contain the string 'not'.
Use a Promise.all, this part is important as it basically gets all the values for each of the keys that we just found and puts them into an array called items
Each object in the items array has a key and a value property.
We then filter the items so that only the ones with a item.value === 'true' remain.
We then filter the items so that only the ones with a item.value !== 'true' remain. (this may be optional it is really dependent on what you want to do)
What do we return? You need to add that part.
Here is the refactor:
_getFetchData = async () => {
let allKeys = await AsyncStorage.getAllKeys(); // 1
if (allKeys.length) { // 2
let filteredKeys = allKeys.filter(key => !key.includes('not')); // 3
let items = await Promise.all(filteredKeys.map(async key => { // 4
let value = await AsyncStorage.getItem(key);
return { key, value }; // 5
}))
let filteredTrueItems = items.filter(item => items.value === 'true'); // 6
let filteredFalseItems = items.filter(item => items.value !== 'true'); // 7
// now you have two arrays one with the items that have the true values
// and one with the items that have the false values
// at this points you can decide what to return as it is not
// that clear from your question
// return the value that your want // 8
} else {
// return your default value if there are no keys // 8
}
}
You would call this function as follows:
_fetchData = async () => {
let channel = await this._getFetchData();
console.log("channel required: " + channel);
}
Although the above will work, it will not currently return a value as you haven't made it clear which value you wish to return. I would suggest you build upon the code that I have written here and update it so that it returns the values that you want.
Further reading
For further reading I would suggest these awesome articles by Michael Chan that discuss state
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6
I would also suggest taking some time to read up about async/await and promises
https://medium.com/#bluepnume/learn-about-promises-before-you-start-using-async-await-eb148164a9c8
And finally this article and SO question on Promise.all are quite good
https://www.taniarascia.com/promise-all-with-async-await/
Using async/await with a forEach loop
Try this instead. Async functions and Promises can be tricky to get right and can be difficult to debug but you're on the right track.
async _getFetchData() {
let channels = "";
let results = await AsyncStorage.getAllKeys();
results.forEach((result) => {
if (result.includes("not") === false) {
let item = await AsyncStorage.getItem(result);
if (item === 'true') {
console.log(`channel: ${result}`)
channels = `channel_id ${result}`;
}
}
});
return channels;
}
_fetchData() {
this._getFetchData().then((channels) => {
console.log(`channel required: ${channel}`);
});
}
what if you wrap the _getFetchData() in a Promise? This would enable you to use
var channel = this._getFetchData().then(console.log("channel required: " + channel));
Otherwise the console.log won't wait for the execution of the _getFetchData().
This is what the console.log is telling you. it just logs the string. the variable is added after the async operation is done.
UPDATE
I would try this:
//_getFetchData()
AsyncStorage.getAllKeys().then((result) => { //get all stored Keys
valuelength = result.length;
if (valuelength !== 0) {
for (let i = 0; i < valuelength; i++) {
if (result[i].includes("not") == false) { //get Keys without not
AsyncStorage.getItem(result[i]).then((resultvalue) => {
if (resultvalue === 'true') {
if (this.state.firstValue) {
this.state.channels = this.state.channels + "channel_id" + result[i];
console.log("channel: " + this.state.channels);
}
else {
this.state.channels = this.state.channels + "channel" + result[i];
}
}
});
}
return new Promise((resolve, reject) => {
this.state.channels !=== undefined ? resolve(this.state.channels) : reject(Error('error '));
}
_fetchData() {
var channel = this._getFetchData().then(console.log("channel required: " + channel));
}
maybe you must change the this.state.channels !=== undefined to an expression that's matches the default value of this.state.channels.
I'm trying to print qz tray from javascript.
I have barcode with number in ascending order 1,2,3,4, 5 and so on.
I looping the seq correctly . but when printed out, it was not in order.
setTimeout("directPrint2()",1000);
function sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
function directPrint2(){
var data;
var xhttp;
var v_carton = "' || x_str_carton ||'";
var carton_arr = v_carton.split('','');
var v1 = "' ||
replace(x_zebra_printer_id, '\', '|') ||
'".replace(/\|/g,"\\");
if(v1 == ""){
alert("Please setup ZPL Printer");
}
else{
xhttp=new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
data = [ toNative(this.responseText) ];
printZPL(data, v1);
}
};
for (var j = 0; j < carton_arr.length; j++){
var url = "' || x_wms_url ||
'WWW_URL.direct_print_label?in_carton_no="+toValidStr(carton_arr[j]);
xhttp.open("GET", url, false);
xhttp.send();
sleep(5000);
}
}
};
',
'javascript'
What's missing from your example:
I do not see any looping logic in the example calling the printZPL function,
printZPL isn't a QZ Tray function and you're missing the code snippet which it calls. Usually this would be qz.print(config, data);.
Regardless of the missing information, the qz.print(...) API is ES6/Promise/A+ based meaning if you want to call qz.print multiple times in a row you need to use a Promise-compatible technique. (e.g. .then(...) syntax) between your print calls as explained in the Chaining Requests guide.
To avoid this, you can concatenate all ZPL data into one large data array. Be careful not to spool too much data at once.
If you know exactly how many jobs you'll be appending, you can hard-code the promise chain:
qz.websocket.connect()
.then(function() {
return qz.printers.find("zebra"); // Pass the printer name into the next Promise
})
.then(function(printer) {
var config = qz.configs.create(printer); // Create a default config for the found printer
var data = ['^XA^FO50,50^ADN,36,20^FDRAW ZPL EXAMPLE^FS^XZ']; // Raw ZPL
return qz.print(config, data);
})
.catch(function(e) { console.error(e); });
Finally, if you do NOT know in advanced how many calls to qz.print(...) you can use a Promise loop as explained in the Promise Loop guide.
function promiseLoop() {
var data = [
"^XA\n^FO50,50^ADN,36,20^FDPRINT 1 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 2 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 3 ^FS\n^XZ\n",
"^XA\n^FO50,50^ADN,36,20^FDPRINT 4 ^FS\n^XZ\n"
];
var configs = [
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" },
{ "printer": "ZDesigner LP2844-Z" }
];
var chain = [];
for(var i = 0; i < data.length; i++) {
(function(i_) {
//setup this chain link
var link = function() {
return qz.printers.find(configs[i_].printer).then(function(found) {
return qz.print(qz.configs.create(found), [data[i_]]);
});
};
chain.push(link);
})(i);
//closure ensures this promise's concept of `i` doesn't change
}
//can be .connect or `Promise.resolve()`, etc
var firstLink = new RSVP.Promise(function(r, e) { r(); });
var lastLink = null;
chain.reduce(function(sequence, link) {
lastLink = sequence.then(link);
return lastLink;
}, firstLink);
//this will be the very last link in the chain
lastLink.catch(function(err) {
console.error(err);
});
}
Note: The Promise Loop is no longer needed in QZ Tray 2.1. Instead, since 2.1, an array of config objects and data arrays can be provided instead.
I'm absolutely newbie in the Dojo, so my question may be too evident. Sorry.
I've programmatically created the complex menu, including MenuBar, based on the rows, selected from the DB.
All problems were solved besides one: the alignment of the final items and submenu items differ.How it looks like All submenus primarily were rendered in the same line. Only by adding the MenuSeparator I was able to divide them.
I'm lost I've found the example in the Internet, that shows exactly what I need (including the right-hand arrow for submenus) Example . I've used exactly the same algorithm to create menu. But I cannot get the same result.
Please, help.
I've noted that the image is not accessible.
In pure text it looks like:
Final 1
Final 2
Final 3
DropDown 1
DropDown 2
Indent depends on the submenu width.
Think, now I know what happened (don't know though, how to work around it).
The problem is the widget rendering.
The final menu option (leaf) is rendered as table row (tr and td tags).
The PopupMenuItem is rendered as div between rows.
Once more, I have no clue, how to avoid it.
Here is the code. A couple of notes:
1.The rows is the two dimensional array
2.The rows with ParentID=0 are the MenuBarItems
3.pM is the MenuBar widget
createMenu: function (rows, pM) {
var me = this; // for references from the event handlers, where 'this' means event origin (instead of lang.hitch)
// First define the indexes of the DB fields
var xMenu_Id;
var xMenu_Title;
var xParent;
var xURL;
var xUser_Roles;
var i;
for (i = 0; i < rows[0].length; i++) {
switch (rows[0][i]) {
case 'Menu_Id':
xMenu_Id = i;
break;
case 'Menu_Title':
xMenu_Title = i;
break;
case 'Parent':
xParent = i;
break;
case 'URL':
xURL = i;
break;
case 'User_Roles':
xUser_Roles = i;
break;
}
}
// Define the function to filter the menu rows
// Parameters: r - two-dimentional rows array
// p - criterion (the parent menu ID)
// idx - index of needed field
// f - returned filtered array (no need to use in calling statement)
var filterArray = function (r, p, idx, f) {
f = dojo.filter(r, function (item) {
return item[idx] == p;
});
return f;
}
// Define the recurcive function to create the sub menu tree for Menu bar item
// Parameters: parentMenu - the menu to add childs
// parentID - the ID of parent menu to select direct children
// role - current user role
var subMenuFactory = function (parentMenu, parentID, role) {
var i;
var fa = filterArray(rows, parentID, xParent);
var sub;
for (i = 0; i < fa.length; i++) {
if (fa[i][xUser_Roles].indexOf(role) >= 0 || fa[i][xUser_Roles] == 'all') {
if (fa[i][xURL] != '0') { // leaf
url = fa[i][xURL];
parentMenu.addChild(new MenuItem({
dir: 'ltr',
label: fa[i][xMenu_Title],
action: fa[i][xURL],
onClick: function () { me.menuAction(this.action); }
}));
}
else { // DropDown Node
sub = new DropDownMenu({ dir: 'ltr' });
subMenuFactory(sub, fa[i][xMenu_Id], role);
parentMenu.addChild(new MenuSeparator({}));
parentMenu.addChild(new PopupMenuBarItem({
dir: 'ltr',
label: fa[i][xMenu_Title],
popup: sub
}));
}
}
}
}
// Get array of Menu bar items
var filtered = filterArray(rows, 0, xParent);
var pSub;
var user_Role = this.user.Role;
for (i = 0; i < filtered.length; i++) {
if (filtered[i][xUser_Roles].indexOf(user_Role) >= 0 || filtered[i][xUser_Roles]=='all') {
if (filtered[i][xURL] != '0') // leaf
{
pM.addChild(new MenuBarItem({
dir: 'ltr',
label: filtered[i][xMenu_Title],
action: filtered[i][xURL],
onClick: function () { me.menuAction(this.action); }
}));
}
else { // DropDown Node
pSub = new DropDownMenu({ dir: 'ltr' });
subMenuFactory(pSub, filtered[i][xMenu_Id],user_Role);
pM.addChild(new PopupMenuBarItem({
dir: 'ltr',
label: filtered[i][xMenu_Title],
popup: pSub
}));
}
}
}
},
I've found what's the problem. In the required array of define I erroneously import PopupMenubarItem instead of PopupMenuItem. In the function the parameter is named right - PopupMenuItem, but evidently it couldn't help a lot...
Thanks to everyone who tried to help me.
Regards,
Gena
Validate if checkbox should be displayed in html page:
get canPerformCommand() {
let r = false;
let valids = ['Success', 'Error'];
if (this.requests.length == 0) return false;
if (valids.indexOf(this.myresult[0].requestStatus) > 0) {
r = true;
}
return r;
}
Html page:
<span if.bind="canPerformCommand" class="panel-heading-buttons">
<button click.trigger="confirm()">Confirm</button>
</span>
Search:
return this.myService.getRequests()
.then(data => {
this.requests = data
//Somehow refresh canPerformCommand here?
});
this.requests will be empty on first page load, resulting in canPerformCommand = false.
I want to refresh canPerformCommand when using the search function.
Currently canPerformCommand will not refresh itself after a search has been done.
How do I refresh canPerformCommand after search using Property Dependencies?
Solution:
The easiest and most logical solution would be to change canPerformCommand into two separate entities -- a flag variable and a function. Make the flag variable be canPerformCommand and keep the if.bind=canPerformCommand but change the name of your function to updateCanPerformCommand() so that you can easily call this function from your return this.myService.getRequests() .then function.
Details:
Set up your view-model like this:
export class App { // <-- (or whatever yours is called)
canPerformCommnad = false;
constructor() {
// ...
}
updateCanPerformCommand() {
let r = false;
let valids = ['Success', 'Error'];
if (this.requests.length == 0) {
this.canPerformCommand = false;
} else if (valids.indexOf(this.myresult[0].requestStatus) > 0) {
this.canPerformCommand = true;
}
}
}
I believe you need to change myresult to requests in the canPerformCommand getter:
get canPerformCommand() {
let r = false;
let valids = ['Success', 'Error'];
if (this.requests.length == 0) return false;
if (valids.indexOf(this.requests[0].requestStatus) > 0) {
r = true;
}
return r;
}
Since this getter will be dirty checked by Aurelia, it will be called every 200ms. To avoid that, you can add a computedFrom decorator:
import {computedFrom} from 'aurelia-framework';
export class MyClass {
....
#computedFrom('requests')
get canPerformCommand() { ....
I replaced:
if (valids.indexOf(this.myresult[0].requestStatus) > 0) {
r = true;
}
With
if (valids.includes(this.requests[0].requestStatus)) {
r = true;
}
And now it works. I was using the indexOf function on other pages aswell, and strangely enough it worked on most of them. Must have been something wrong in my model.
Also, I agree that it would be better to use computedFrom.