mirror of
https://github.com/vishalxl/nostr_console.git
synced 2025-03-27 02:01:51 +01:00
no functional change
This commit is contained in:
parent
d55c5b8896
commit
43f2fab1b4
@ -265,7 +265,6 @@ bool sendDeleteEvent(Store node, String eventIdToDelete) {
|
||||
String toSendMessage = '["EVENT",{"id":"$id","pubkey":"$userPublicKey","created_at":$createdAt,"kind":$replyKind,"tags":[$strTags],"content":"$content","sig":"$sig"}]';
|
||||
sendRequest( gListRelayUrls1, toSendMessage);
|
||||
print("sent event delete request with id = $id");
|
||||
//print(toSendMessage);
|
||||
} else {
|
||||
print("${gWarningColor}The given id was not found and/or is not a valid id, or is not your event. Not deleted.$gColorEndMarker");
|
||||
}
|
||||
|
@ -1353,7 +1353,6 @@ String getDepthSpaces(int d) {
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
// make a paragraph of s that starts at numSpaces ( from screen left), and does not extend beyond gTextWidth+gNumLeftMarginSpaces. break it, or add
|
||||
// a newline if it goes beyond gTextWidth + gNumLeftMarginSpaces
|
||||
String makeParagraphAtDepth(String s, int depthInSpaces) {
|
||||
@ -1371,8 +1370,6 @@ String makeParagraphAtDepth(String s, int depthInSpaces) {
|
||||
String line = listCulledLine[0];
|
||||
int lenReturned = listCulledLine[1] as int;
|
||||
|
||||
//print("In makeParagraphAtDepth: line len = ${line.length} lenReturned = $lenReturned");
|
||||
|
||||
if( line.length == 0 || lenReturned == 0) break;
|
||||
|
||||
newString += line;
|
||||
|
@ -127,20 +127,17 @@ class Relays {
|
||||
* received events in the given List<Event>
|
||||
*/
|
||||
void getMultiUserEvents(String relayUrl, List<String> publicKeys, int limit, int sinceWhen, [Set<int>? kind = null]) {
|
||||
//print("In relays: getmulti events kind = $kind len ${publicKeys.length}");
|
||||
|
||||
Set<String> setPublicKeys = publicKeys.toSet();
|
||||
|
||||
if( relays.containsKey(relayUrl)) {
|
||||
if( relays.containsKey(relayUrl)) {
|
||||
Set<String>? users = relays[relayUrl]?.users;
|
||||
if( users != null) {
|
||||
relays[relayUrl]?.users = users.union(setPublicKeys);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String subscriptionId = "multiple_user" + (relays[relayUrl]?.numRequestsSent??"").toString() + "_" + relayUrl.substring(6);
|
||||
|
||||
String request = getMultiUserRequest( subscriptionId, setPublicKeys, limit, sinceWhen, kind);
|
||||
sendRequest(relayUrl, request);
|
||||
}
|
||||
@ -194,7 +191,6 @@ class Relays {
|
||||
} on FormatException {
|
||||
return;
|
||||
} catch(err) {
|
||||
//dynamic json = jsonDecode(d);
|
||||
return;
|
||||
}
|
||||
},
|
||||
@ -239,20 +235,17 @@ Relays relays = Relays({}, {}, {});
|
||||
void getContactFeed(Set<String> relayUrls, Set<String> setContacts, int numEventsToGet, int sinceWhen) {
|
||||
|
||||
List<String> contacts = setContacts.toList();
|
||||
//print("in getContactFeed: numEventsToGet = $numEventsToGet numContacts = ${setContacts.length} num contacts list = ${contacts.length}");
|
||||
for( int i = 0; i < contacts.length; i += gMaxAuthorsInOneRequest) {
|
||||
|
||||
// for last iteration change upper limit
|
||||
int upperLimit = (i + gMaxAuthorsInOneRequest) > contacts.length?
|
||||
(contacts.length - i): gMaxAuthorsInOneRequest;
|
||||
|
||||
//print("upperLimit = $upperLimit i = $i");
|
||||
List<String> groupContacts = [];
|
||||
for( int j = 0; j < upperLimit; j++) {
|
||||
groupContacts.add(contacts[i + j]);
|
||||
}
|
||||
|
||||
//print( "in getcontact feed . i = $i upperLimit = $upperLimit") ;
|
||||
relayUrls.forEach((relayUrl) {
|
||||
relays.getMultiUserEvents(relayUrl, groupContacts, numEventsToGet, sinceWhen);
|
||||
});
|
||||
@ -298,7 +291,6 @@ void getMultiUserEvents(Set<String> serverUrls, Set<String> setPublicKeys, int n
|
||||
if( publicKeys.length - i <= gMaxAuthorsInOneRequest) {
|
||||
getUserRequests = publicKeys.length - i;
|
||||
}
|
||||
//print("In getMultiuserevents: sending request form $i to ${i + getUserRequests} ");
|
||||
List<String> partialList = publicKeys.sublist(i, i + getUserRequests);
|
||||
relays.getMultiUserEvents(serverUrl, partialList, numUserEvents, sinceWhen, kind);
|
||||
}
|
||||
@ -314,7 +306,6 @@ void sendEventsRequest(Set<String> serverUrls, Set<String> eventIds) {
|
||||
|
||||
String getEventRequest = '["REQ","event_${eventIds.length}",{"ids":[$eventIdsStr]}]';
|
||||
if( gDebug > 0) log.info("sending $getEventRequest");
|
||||
//print("send event req: $getEventRequest\n");
|
||||
|
||||
serverUrls.forEach((url) {
|
||||
relays.sendRequest(url, getEventRequest);
|
||||
|
@ -3,7 +3,7 @@ import 'package:logging/logging.dart';
|
||||
|
||||
// name of executable
|
||||
const String exename = "nostr_console";
|
||||
const String version = "0.3.3-beta";
|
||||
const String version = "0.3.3-beta-a";
|
||||
|
||||
int gDebug = 0;
|
||||
int gSpecificDebug = 0;
|
||||
@ -13,7 +13,6 @@ final log = Logger('ExampleLogger');
|
||||
// for debugging
|
||||
String gCheckEventId = "xb9e1824fe65b10f7d06bd5f6dfe1ab3eda876d7243df5878ca0b9686d80c0840f";
|
||||
|
||||
|
||||
int gMaxEventLenthAccepted = 80000; // max event size. events larger than this are rejected.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////////////////// encrypted Group settings
|
||||
@ -44,7 +43,6 @@ int gDefaultNumWaitSeconds = 12000; // is used in main()
|
||||
const int gMaxAuthorsInOneRequest = 300; // number of author requests to send in one request
|
||||
const int gMaxPtagsToGet = 100; // maximum number of p tags that are taken from the comments of feed ( the top most, most frequent)
|
||||
|
||||
|
||||
const int gSecsLatestLive = 2 * 3600; // the lastst seconds for which to get the latest event in main
|
||||
int gHoursDefaultPrint = 6; // print latest given hours only
|
||||
|
||||
@ -54,7 +52,6 @@ int numFileEvents = 0, numFilePosts = 0, numUserPosts = 0, numFeedPosts = 0, num
|
||||
String defaultServerUrl = "wss://relay.damus.io";
|
||||
const String relayNostrInfo = 'wss://relay.nostr.info';
|
||||
|
||||
|
||||
Set<String> gListRelayUrls1 = { defaultServerUrl,
|
||||
relayNostrInfo,
|
||||
"wss://nostr-2.zebedee.cloud",
|
||||
@ -64,7 +61,6 @@ Set<String> gListRelayUrls1 = { defaultServerUrl,
|
||||
"wss://nostr.drss.io",
|
||||
"wss://nostr.radixrat.com",
|
||||
"wss://relay.nostr.ch"
|
||||
|
||||
};
|
||||
|
||||
Set<String> gListRelayUrls2 = {
|
||||
@ -81,7 +77,6 @@ Set<String> gListRelayUrls3 = {
|
||||
"wss://nostr.openchain.fr"
|
||||
};
|
||||
|
||||
|
||||
// well known disposable test private key
|
||||
const String gDefaultPublicKey = "";
|
||||
String userPrivateKey = "";
|
||||
@ -245,15 +240,12 @@ a & b are orange
|
||||
2 & 3 are blue
|
||||
0 & 1 are purple
|
||||
|
||||
|
||||
List<String> nameColorPalette = [brightGreenColor, brightCyanColor, brightYellowColor, brightMagentaColor,
|
||||
brightBlueColor, brightRedColor, brightBlackColor, brightWhiteColor,
|
||||
yellowColor, magentaColor, redColor ];
|
||||
|
||||
List<String> nameColorPalette = [brightMagentaColor, brightBlueColor, brightCyanColor, brightGreenColor,
|
||||
brightYellowColor, brightRedColor, yellowColor, redColor ];
|
||||
|
||||
|
||||
*/
|
||||
|
||||
Map<String, String> pubkeyColor = { '0': magentaColor, '1': brightMagentaColor,
|
||||
@ -266,7 +258,6 @@ Map<String, String> pubkeyColor = { '0': magentaColor, '1': brightMagentaColor,
|
||||
'e': redColor, 'f': redColor
|
||||
};
|
||||
|
||||
|
||||
String getNameColor( String pubkey) {
|
||||
if( pubkey.length == 0)
|
||||
return brightMagentaColor;
|
||||
@ -275,7 +266,6 @@ String getNameColor( String pubkey) {
|
||||
return pubkeyColor[firstChar]??brightMagentaColor;
|
||||
}
|
||||
|
||||
|
||||
// By default the threads that were started in last one day are shown
|
||||
// this can be changed with 'days' command line argument
|
||||
const int gDefaultNumLastDays = 1;
|
||||
|
@ -28,37 +28,6 @@ bool selectorTrees_selfPosts(Tree t) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
// returns true of the user has received a like or response to this post
|
||||
bool userHasNotification(String pubkey, Event e) {
|
||||
if( e.eventData.pubkey == pubkey && gReactions.containsKey(e.eventData.id) ) {
|
||||
List<List<String>>? temp = gReactions[e.eventData.id];
|
||||
if( temp != null) {
|
||||
if( temp.length > 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// only show in which user is involved
|
||||
bool selectorTrees_userNotifications(Tree t) {
|
||||
|
||||
if( userHasNotification(userPublicKey, t.event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for( Tree child in t.children) {
|
||||
if( selectorTrees_userNotifications(child)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
*/
|
||||
|
||||
// returns true of user has made this comment, or liked it
|
||||
bool userInvolved(String pubkey, Event e) {
|
||||
|
||||
@ -95,7 +64,6 @@ bool selectorTrees_userRepliesLikes(Tree t) {
|
||||
if( userInvolved(userPublicKey, t.event)) {
|
||||
usersEvent = true;
|
||||
}
|
||||
|
||||
|
||||
for( Tree child in t.children) {
|
||||
if( selectorTrees_userRepliesLikes(child)) {
|
||||
@ -327,7 +295,6 @@ class Channel extends ScrollableMessages {
|
||||
String picture;
|
||||
int lastUpdated; // used for encryptedChannels
|
||||
|
||||
|
||||
Set<String> participants; // pubkey of all participants - only for encrypted channels
|
||||
String creatorPubkey; // creator of the channel, if event is known
|
||||
|
||||
@ -417,7 +384,6 @@ class Tree {
|
||||
void setStore(Store s) {
|
||||
store = s;
|
||||
}
|
||||
|
||||
|
||||
/***********************************************************************************************************************************/
|
||||
/* The main print tree function. Calls the reeSelector() for every node and prints it( and its children), only if it returns true.
|
||||
@ -589,7 +555,6 @@ class Tree {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// returns true if the tree (or its children, depending on flag) has a post or like by user; and notification flags are set for such events
|
||||
bool treeSelectorUserPostAndLike(Set<String> pubkeys, { bool enableNotifications = true, bool checkChildrenToo = true}) {
|
||||
bool hasReacted = false;
|
||||
@ -720,7 +685,6 @@ class Tree {
|
||||
|
||||
// clears all notifications; returns true always
|
||||
int treeSelector_clearNotifications() {
|
||||
|
||||
int count = 0;
|
||||
|
||||
if( event.eventData.isNotification) {
|
||||
@ -731,11 +695,8 @@ class Tree {
|
||||
if( event.eventData.newLikes.length > 0) {
|
||||
event.eventData.newLikes = {};
|
||||
count = 1;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
for( int i = 0; i < children.length; i++ ) {
|
||||
count += children[i].treeSelector_clearNotifications();
|
||||
}
|
||||
@ -743,7 +704,6 @@ class Tree {
|
||||
return count;
|
||||
} // end treeSelector_clearNotifications()
|
||||
|
||||
|
||||
// counts all valid events in the tree: ignores the dummy nodes that are added for events which aren't yet known
|
||||
int count() {
|
||||
int totalCount = 0;
|
||||
@ -900,7 +860,6 @@ class Store {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// events with tag 'location' are added to their own public channel depending on value of tag.
|
||||
static void addLocationTagEventInChannel(EventData eventData, List<Channel> rooms, Map<String, Tree> tempChildEventsMap) {
|
||||
|
||||
@ -1020,14 +979,11 @@ class Store {
|
||||
channel.participants = participants;
|
||||
channel.chatRoomName = roomName;
|
||||
channel.about = roomAbout;
|
||||
|
||||
channel.lastUpdated = event14x.eventData.createdAt;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case 141:
|
||||
|
||||
Set<String> participants = {};
|
||||
event14x.eventData.pTags.forEach((element) { participants.add(element);});
|
||||
|
||||
@ -1068,7 +1024,6 @@ class Store {
|
||||
break;
|
||||
|
||||
case 142:
|
||||
//if( gSpecificDebug > 0 && eId == gCheckEventId) printWarning("Got ${eId}");
|
||||
if( gSpecificDebug > 0) print("got kind 142 message. total number of encrypted channels: ${encryptedChannels.length}. event e tags ${event14x.eventData.eTags}");
|
||||
String channelId = event14x.eventData.getChannelIdForKind4x();
|
||||
|
||||
@ -1103,7 +1058,6 @@ class Store {
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int handleDirectMessage( List<DirectMessageRoom> directRooms, Map<String, Tree> tempChildEventsMap, Event ce) {
|
||||
String eId = ce.eventData.id;
|
||||
int eKind = ce.eventData.kind;
|
||||
@ -1147,7 +1101,6 @@ class Store {
|
||||
directRooms.add( newDirectRoom);
|
||||
if( ce.eventData.id == gCheckEventId && gDebug >= 0) print("Adding new message ${ce.eventData.id} to NEW direct room $directRoomId. sender pubkey = ${ce.eventData.pubkey}.");
|
||||
}
|
||||
//ce.eventData.translateAndExpandMentions(directRooms, tempChildEventsMap);
|
||||
if( ce.eventData.evaluatedContent.length > 0) numMessagesDecrypted++;
|
||||
} else {
|
||||
if( gDebug > 0) print("Could not get chat room id for event ${ce.eventData.id} sender pubkey = ${ce.eventData.pubkey}.");
|
||||
@ -1230,7 +1183,6 @@ class Store {
|
||||
eventIdsToFetch.add(parentId);
|
||||
topLevelTrees.add(dummyTopNode);
|
||||
}
|
||||
//printWarning("Added unknown event as top : ${parentId}");
|
||||
}
|
||||
else {
|
||||
if( gDebug > 0) {
|
||||
@ -1238,7 +1190,6 @@ class Store {
|
||||
print("original json of event:\n${tree.event.originalJson}");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
// is not a parent, has no parent tag. then make it its own top tree, which will be done later in the calling function
|
||||
@ -1295,7 +1246,6 @@ class Store {
|
||||
// same as above but no return cause these are processed as kind 1 too
|
||||
}
|
||||
|
||||
|
||||
if( eKind == 4) {
|
||||
handleDirectMessage(tempDirectRooms, tempChildEventsMap, tree.event);
|
||||
return;
|
||||
@ -1399,9 +1349,9 @@ class Store {
|
||||
// add the event to the main event store thats allChildEventsMap
|
||||
newEventsToProcess.forEach((newEvent) {
|
||||
|
||||
/*if( newEvent.eventData.kind == 1 && newEvent.eventData.content.compareTo("Hello Nostr! :)") == 0 && newEvent.eventData.id.substring(0,2).compareTo("00") == 0) {
|
||||
if( newEvent.eventData.kind == 1 && newEvent.eventData.content.compareTo("Hello Nostr! :)") == 0 && newEvent.eventData.id.substring(0,2).compareTo("000") == 0) {
|
||||
return; // spam prevention
|
||||
}*/
|
||||
}
|
||||
|
||||
if( allChildEventsMap.containsKey(newEvent.eventData.id)) {// don't process if the event is already present in the map
|
||||
return;
|
||||
|
@ -1,9 +1,7 @@
|
||||
|
||||
import 'package:nostr_console/event_ds.dart';
|
||||
import 'package:nostr_console/settings.dart';
|
||||
import 'package:nostr_console/utils.dart';
|
||||
|
||||
|
||||
// is set intermittently by functions. and used as required. Should be kept in sync as the kind 3 for user are received.
|
||||
Set<String> gFollowList = {};
|
||||
|
||||
@ -32,7 +30,6 @@ Set<String> getFollows(String pubkey) {
|
||||
return followPubkeys;
|
||||
}
|
||||
|
||||
|
||||
Set<String> getUserChannels(Set<Event> userEvents, String userPublicKey) {
|
||||
Set<String> userChannels = {};
|
||||
|
||||
|
@ -19,7 +19,6 @@ String getPostKindFrom(enumRoomType eType) {
|
||||
case enumRoomType.RoomTTag:
|
||||
return "1";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Set<String>? getTagsFromContent(String content) {
|
||||
@ -204,11 +203,8 @@ String addEscapeChars(String str) {
|
||||
}
|
||||
|
||||
String unEscapeChars(String str) {
|
||||
//print("in unEscape: |$str|");
|
||||
String temp = str.replaceAll("\"", "\\\"");
|
||||
//temp = temp.replaceAll("\\\\", "\\");
|
||||
temp = temp.replaceAll("\n", "\\n");
|
||||
//print("returning |$temp|\n");
|
||||
return temp;
|
||||
}
|
||||
|
||||
@ -261,28 +257,22 @@ List<int> qrModules = [21, 25, 29, 33, 37, 41, 45, 49, 53, 57
|
||||
// return type and module as entries in a list
|
||||
List<int>? getTypeAndModule(String str) {
|
||||
if( qrMaxDataBits.length != qrModules.length) {
|
||||
//print("ret null 1");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 5 for padding which it seems to need, otherwise it gives error like 'QrInputTooLongException: Input too long. 2212 > 2192' for a str which is exactly 2192
|
||||
int strLen = str.length + 5;
|
||||
for( int i = 0; i < qrModules.length; i++) {
|
||||
//print("checking $strLen, ${strLen * 8} and ${qrMaxDataBits[i]}");
|
||||
if( strLen * 8 <= qrMaxDataBits[i]) {
|
||||
return [i+1, qrModules[i]];
|
||||
}
|
||||
}
|
||||
|
||||
//print("ret null 2 strLen = $strLen");
|
||||
return null;
|
||||
}
|
||||
|
||||
bool sanityChecked(String lnInvoice) {
|
||||
|
||||
//for( int i = 0 ; i < lnInvoice.length; i++) {
|
||||
//}
|
||||
|
||||
if( lnInvoice.length < gMinLnInvoiceLength)
|
||||
return false;
|
||||
|
||||
@ -308,19 +298,14 @@ String expandLNInvoices(String content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//print(lnInvoice);
|
||||
String qrStr = "";
|
||||
//print("\nqr code len: ${lnInvoice.length}");
|
||||
|
||||
List<int>? typeAndModule = getTypeAndModule(lnInvoice);
|
||||
if( typeAndModule == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//print("\nqr code len: ${lnInvoice.length}"); print(typeAndModule); print("--");
|
||||
qrStr = getPubkeyAsQrString(lnInvoice, typeAndModule[0], typeAndModule[1], "");
|
||||
//print(lnInvoice); print(qrStr);
|
||||
|
||||
content = content.substring(0, match.start) + ":-\n\n" + qrStr + "\n\n" + content.substring(match.end);
|
||||
}
|
||||
|
||||
@ -338,7 +323,6 @@ String getPubkeyAsQrString(String str, [int typeNumber = 4, moduleCount = 33, St
|
||||
final qrImage = QrImage(qrCode);
|
||||
|
||||
assert( qrImage.moduleCount == moduleCount);
|
||||
//print("qrimage modulecount = ${qrImage.moduleCount}");
|
||||
var x = 0;
|
||||
for (x = 0; x < qrImage.moduleCount -1 ; x += 2) {
|
||||
output += leftPadding;
|
||||
@ -435,7 +419,6 @@ String getCommaSeparatedInts(Set<int>? kind) {
|
||||
}
|
||||
|
||||
String getKindRequest(String subscriptionId, List<int> kind, int limit, int sinceWhen) {
|
||||
//print("in getkindrequest: kind = $kind");
|
||||
String strTime = "";
|
||||
if( sinceWhen != 0) {
|
||||
strTime = ', "since":${sinceWhen.toString()}';
|
||||
@ -446,7 +429,6 @@ String getKindRequest(String subscriptionId, List<int> kind, int limit, int sinc
|
||||
String strKind = getCommaSeparatedInts(kind.toSet());
|
||||
|
||||
String strRequest = strSubscription1 + strKind + strSubscription2;
|
||||
//print("returning $strRequest");
|
||||
return strRequest;
|
||||
}
|
||||
|
||||
@ -470,7 +452,6 @@ String getUserRequest(String subscriptionId, String publicKey, int numUserEvents
|
||||
var strSubscription1 = '["REQ","$subscriptionId",{ "authors": ["';
|
||||
var strSubscription2 ='"],$strKindSection"limit": $numUserEvents $strTime } ]';
|
||||
String request = strSubscription1 + publicKey.toLowerCase() + strSubscription2;
|
||||
//print("In getUserRequest: $request");
|
||||
return request;
|
||||
}
|
||||
|
||||
@ -497,7 +478,6 @@ String getIdAndMentionRequest(String subscriptionId, Set<String> ids, int numUse
|
||||
var strSubscription1 = '["REQ","$subscriptionId",{ "$tagToGet": [';
|
||||
var strSubscription2 ='], "limit": $numUserEvents $idStrTime } ]';
|
||||
String req = '["REQ","$subscriptionId",{ "$tagToGet": [' + getCommaSeparatedQuotedStrs(ids) + '], "limit": $numUserEvents $mentionStrTime},{"$idString":[' + getCommaSeparatedQuotedStrs(ids) + ']$idStrTime}]';
|
||||
//print("Created id and mention request: $req");
|
||||
return req;
|
||||
}
|
||||
|
||||
@ -520,7 +500,6 @@ String getMultiUserRequest(String subscriptionId, Set<String> publicKeys, int nu
|
||||
String s = "";
|
||||
s = getCommaSeparatedQuotedStrs(publicKeys);
|
||||
String request = strSubscription1 + s + strSubscription2;
|
||||
//print("In getMultiUserRequest kind = $kind strKindSection = $strKindSection: request = $request");
|
||||
return request;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: nostr_console
|
||||
description: A multi-platform nostr client built for terminal/console
|
||||
version: 0.3.3-beta
|
||||
version: 0.3.3-beta-a
|
||||
homepage: https://github.com/vishalxl/nostr_console
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user