Complete location as a criteria for search
This commit is contained in:
		
							
								
								
									
										43119
									
								
								cities.sql
									
									
									
									
									
								
							
							
						
						
									
										43119
									
								
								cities.sql
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										13
									
								
								csvtosql.js
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								csvtosql.js
									
									
									
									
									
								
							@@ -25,18 +25,21 @@ const db = await getClient();
 | 
				
			|||||||
const file = fs.readFileSync("./output.csv").toString();
 | 
					const file = fs.readFileSync("./output.csv").toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const inFileStream = fs.createReadStream("./output.csv");
 | 
					const inFileStream = fs.createReadStream("./output.csv");
 | 
				
			||||||
 | 
					await db.query("BEGIN");
 | 
				
			||||||
const rl = readline.createInterface({
 | 
					const rl = readline.createInterface({
 | 
				
			||||||
    input: inFileStream,
 | 
					    input: inFileStream,
 | 
				
			||||||
    output: null,  
 | 
					    output: null,
 | 
				
			||||||
    crlfDelay: Infinity
 | 
					    crlfDelay: Infinity
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rl.on('line', async (line) => {
 | 
					rl.on('line', async (line) => {
 | 
				
			||||||
    let splitted = line.split(",")
 | 
					    let splitted = line.split(",")
 | 
				
			||||||
    await db.query(`INSERT INTO cities (name, lat, lng, country) VALUES ($1, $2, $3, $4);`, [splitted[0], splitted[1], splitted[2], splitted[3]])
 | 
					    console.log(splitted);
 | 
				
			||||||
 | 
					    await db.query(`INSERT INTO cities (name, lat, lng, country) VALUES ($1, $2, $3, $4)`, [splitted[0], splitted[1], splitted[2], splitted[3]])
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
rl.on('close', () => {
 | 
					rl.on('close', async () => {
 | 
				
			||||||
    console.log("done");
 | 
					    await db.query("COMMIT");
 | 
				
			||||||
 | 
					    process.exit(0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
							
								
								
									
										22
									
								
								scheme.psql
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								scheme.psql
									
									
									
									
									
								
							@@ -40,8 +40,24 @@ CREATE TABLE IF NOT EXISTS messages (
 | 
				
			|||||||
CREATE UNIQUE INDEX IF NOT EXISTS unique_messages ON messages(sender, recipient);
 | 
					CREATE UNIQUE INDEX IF NOT EXISTS unique_messages ON messages(sender, recipient);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE IF NOT EXISTS cities (
 | 
					CREATE TABLE IF NOT EXISTS cities (
 | 
				
			||||||
    name VARCHAR(32),
 | 
					    name VARCHAR(64),
 | 
				
			||||||
    lat REAL,
 | 
					    lat REAL,
 | 
				
			||||||
    lng REAL,
 | 
					    lng REAL,
 | 
				
			||||||
    country VARCHAR(32)
 | 
					    country VARCHAR(64)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE EXTENSION IF NOT EXISTS fuzzystrmatch SCHEMA public;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CREATE OR REPLACE FUNCTION deg2rad(double precision) RETURNS double precision AS 'SELECT $1 * 0.01745329' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT;
 | 
				
			||||||
 | 
					-- lat1, lng1, lat2, lng2, range
 | 
				
			||||||
 | 
					CREATE OR REPLACE FUNCTION check_distance(double precision, double precision, double precision, double precision, double precision) RETURNS BOOLEAN AS '
 | 
				
			||||||
 | 
					SELECT 2 * ASIN(SQRT(
 | 
				
			||||||
 | 
					            POWER(SIN( (deg2rad($3 - $1)) / 2 ), 2) 
 | 
				
			||||||
 | 
					            + COS(deg2rad($1)) 
 | 
				
			||||||
 | 
					            * COS(deg2rad($3)) 
 | 
				
			||||||
 | 
					            * POWER(SIN(deg2rad($4 - $2)/2), 2) 
 | 
				
			||||||
 | 
					            )) 
 | 
				
			||||||
 | 
					* 6371 <= $5
 | 
				
			||||||
 | 
					' LANGUAGE SQL RETURNS NULL ON NULL INPUT;
 | 
				
			||||||
							
								
								
									
										114
									
								
								src/db.js
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								src/db.js
									
									
									
									
									
								
							@@ -19,7 +19,7 @@ const getClient = async () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if ((await client.query("SELECT * FROM cities")).rowCount < 6079) {
 | 
					    if ((await client.query("SELECT * FROM cities")).rowCount < 6079) {
 | 
				
			||||||
        await client.query("DELETE FROM cities");
 | 
					        await client.query("DELETE FROM cities");
 | 
				
			||||||
        
 | 
					
 | 
				
			||||||
        //Not sure if pg has support for such kind of things, sooooooooo 
 | 
					        //Not sure if pg has support for such kind of things, sooooooooo 
 | 
				
			||||||
        exec(`psql -h ${process.env.POSTGRES_HOST} -p ${process.env.POSTGRES_PORT} -d ${process.env.POSTGRES_DB} -U ${process.env.POSTGRES_USER} -f ./cities.sql`, (error) => {
 | 
					        exec(`psql -h ${process.env.POSTGRES_HOST} -p ${process.env.POSTGRES_PORT} -d ${process.env.POSTGRES_DB} -U ${process.env.POSTGRES_USER} -f ./cities.sql`, (error) => {
 | 
				
			||||||
            if (error) logError(error);
 | 
					            if (error) logError(error);
 | 
				
			||||||
@@ -61,33 +61,50 @@ const eraseUserMedia = async (roomId) => {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const selectProfilesForUser = async (roomId) => {
 | 
					const selectProfilesForUser = async (roomId) => {
 | 
				
			||||||
    let myInterest = (await db.query("SELECT interest FROM users WHERE room_id = $1", [roomId])).rows[0].interest;
 | 
					    const { myrange, myinterest, mysex, myage, mycity, mycountry } = (await db.query(`SELECT range AS myrange, 
 | 
				
			||||||
    let mySex = (await db.query("SELECT sex FROM users WHERE room_id = $1", [roomId])).rows[0].sex;
 | 
					                                                                                             interest AS myinterest, 
 | 
				
			||||||
    let userAge = (await db.query("SELECT age FROM users WHERE room_id = $1", [roomId])).rows[0].age;
 | 
					                                                                                             sex AS mysex, 
 | 
				
			||||||
    //Selecting profiles other than user's and with difference in age +-2.
 | 
					                                                                                             age AS myage, 
 | 
				
			||||||
    let user;
 | 
					                                                                                             city AS mycity, 
 | 
				
			||||||
    if (myInterest === 'b') // both, no matter what sex
 | 
					                                                                                             country AS mycountry 
 | 
				
			||||||
        user = (await db.query(`SELECT
 | 
					                                                                                      FROM users WHERE room_id = $1`, [roomId])
 | 
				
			||||||
                                    room_id, name, age, sex, description, country, city FROM users
 | 
					    ).rows[0];
 | 
				
			||||||
                                WHERE
 | 
					    const { lat, lng } = (await db.query("SELECT lat, lng FROM cities WHERE name = $1 AND country = $2", [mycity, mycountry])).rows[0];
 | 
				
			||||||
                                    age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
 | 
					    //Selecting profiles other than user's and fitting their needs
 | 
				
			||||||
                                AND room_id != $2
 | 
					    /*
 | 
				
			||||||
                                AND (interest = $3 OR interest = 'b')
 | 
					    2 * ASIN(SQRT(
 | 
				
			||||||
                                ORDER BY RANDOM()
 | 
					                                                                                POWER(SIN( (deg2rad(lat - $6::double precision)) / 2 ), 2) 
 | 
				
			||||||
                                LIMIT 1`, [userAge, roomId, mySex])
 | 
					                                                                                + COS(deg2rad(lat)) 
 | 
				
			||||||
        ).rows[0];
 | 
					                                                                                * COS(deg2rad($6::double precision)) 
 | 
				
			||||||
    else {
 | 
					                                                                                * POWER(SIN(deg2rad(lng - $7::double precision)/2), 2) 
 | 
				
			||||||
        user = (await db.query(`SELECT 
 | 
					                                                                             )) 
 | 
				
			||||||
                                    room_id, name, age, sex, description, country, city FROM users
 | 
					                                                                             * 6371 <= $5::double precision
 | 
				
			||||||
                                WHERE
 | 
					    */
 | 
				
			||||||
                                    age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
 | 
					    let user = (await db.query(`SELECT 
 | 
				
			||||||
                                AND room_id != $2
 | 
					                                room_id, name, age, sex, description, country, city FROM users
 | 
				
			||||||
                                AND sex = $3
 | 
					                            WHERE
 | 
				
			||||||
                                AND (interest = $4 OR interest = 'b')
 | 
					                                age::numeric <@ ANY(ARRAY[numrange($1 - 2, $1 + 2)])
 | 
				
			||||||
                                ORDER BY RANDOM()
 | 
					                            AND room_id != $2
 | 
				
			||||||
                                LIMIT 1`, [userAge, roomId, myInterest, mySex])
 | 
					                            AND ${myinterest !== 'b' ? "sex = $3" : "$3 = $3 AND $4 = $4 AND $5 = $5 AND $6 = $6 AND $7 = $7"}
 | 
				
			||||||
        ).rows[0];
 | 
					                            AND ${myrange !== 0 ?
 | 
				
			||||||
    }
 | 
					                                `city = ANY(ARRAY(SELECT name 
 | 
				
			||||||
 | 
					                                                    FROM cities 
 | 
				
			||||||
 | 
					                                                    WHERE name = ANY(ARRAY(SELECT name 
 | 
				
			||||||
 | 
					                                                                            FROM cities 
 | 
				
			||||||
 | 
					                                                                            WHERE 
 | 
				
			||||||
 | 
					                                                                            check_distance($6::double precision, $7::double precision, lat, lng, $5::double precision)
 | 
				
			||||||
 | 
					                                                                    
 | 
				
			||||||
 | 
					                                                    ))
 | 
				
			||||||
 | 
					                                ))`
 | 
				
			||||||
 | 
					                                :
 | 
				
			||||||
 | 
					                                `check_distance($6::double precision, $7::double precision, (SELECT lat FROM cities WHERE name = city AND cities.country = country), (SELECT lng FROM cities WHERE name = city AND cities.country = country), range)`
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                            AND (range >= $5::double precision AND range != 0)
 | 
				
			||||||
 | 
					                            AND (interest = $4 OR interest = 'b')
 | 
				
			||||||
 | 
					                            ORDER BY RANDOM()
 | 
				
			||||||
 | 
					                            LIMIT 1`, [myage, roomId, myinterest, mysex, myrange, lat, lng])
 | 
				
			||||||
 | 
					    ).rows[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!user) return null;
 | 
					    if (!user) return null;
 | 
				
			||||||
    let media = await getUserProfilePictures(user.room_id);
 | 
					    let media = await getUserProfilePictures(user.room_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -174,6 +191,43 @@ const getUserLanguage = async (roomId) => {
 | 
				
			|||||||
    return (await db.query("SELECT language FROM users WHERE room_id = $1", [roomId])).rows[0].language;
 | 
					    return (await db.query("SELECT language FROM users WHERE room_id = $1", [roomId])).rows[0].language;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkCountry = async (name) => {
 | 
				
			||||||
 | 
					    let res = (await db.query(`SELECT country AS name, levenshtein(country, $1) AS similarity
 | 
				
			||||||
 | 
					                               FROM cities
 | 
				
			||||||
 | 
					                               ORDER BY similarity ASC
 | 
				
			||||||
 | 
					                               LIMIT 3`, [name])
 | 
				
			||||||
 | 
					    ).rows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (res[0].similarity == 0) { // 'similarity' is actually inversed. The less 'similarity', the more it similar. 0 means the same
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            exists: true
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        exists: false,
 | 
				
			||||||
 | 
					        variants: res
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkCity = async (name) => {
 | 
				
			||||||
 | 
					    let res = (await db.query(`SELECT name, country, levenshtein(name, $1) AS similarity
 | 
				
			||||||
 | 
					                               FROM cities
 | 
				
			||||||
 | 
					                               ORDER BY similarity ASC
 | 
				
			||||||
 | 
					                               LIMIT 5`, [name])
 | 
				
			||||||
 | 
					    ).rows;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (res[0].similarity == 0) {
 | 
				
			||||||
 | 
					        return {
 | 
				
			||||||
 | 
					            exists: true,
 | 
				
			||||||
 | 
					            country: res[0].country
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return {
 | 
				
			||||||
 | 
					        exists: false,
 | 
				
			||||||
 | 
					        variants: res
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export {
 | 
					export {
 | 
				
			||||||
    eraseUser,
 | 
					    eraseUser,
 | 
				
			||||||
    appendUserPictures,
 | 
					    appendUserPictures,
 | 
				
			||||||
@@ -196,5 +250,7 @@ export {
 | 
				
			|||||||
    getUnreadMessages,
 | 
					    getUnreadMessages,
 | 
				
			||||||
    markMessageAsRead,
 | 
					    markMessageAsRead,
 | 
				
			||||||
    setUserLanguage,
 | 
					    setUserLanguage,
 | 
				
			||||||
    getUserLanguage
 | 
					    getUserLanguage,
 | 
				
			||||||
 | 
					    checkCountry,
 | 
				
			||||||
 | 
					    checkCity
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								src/index.js
									
									
									
									
									
								
							@@ -32,7 +32,9 @@ import {
 | 
				
			|||||||
    setUserState,
 | 
					    setUserState,
 | 
				
			||||||
    uploadMediaAsMessage,
 | 
					    uploadMediaAsMessage,
 | 
				
			||||||
    setUserLanguage,
 | 
					    setUserLanguage,
 | 
				
			||||||
    getUserLanguage
 | 
					    getUserLanguage,
 | 
				
			||||||
 | 
					    checkCountry,
 | 
				
			||||||
 | 
					    checkCity
 | 
				
			||||||
} from './db.js';
 | 
					} from './db.js';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js";
 | 
					import { processRequest, showRandomProfileToUser, showNewLikes } from "./interactions.js";
 | 
				
			||||||
@@ -84,15 +86,41 @@ client.on("room.message", async (roomId, event) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "country":
 | 
					            case "country":
 | 
				
			||||||
 | 
					                let checkResultCountry = await checkCountry(answer);
 | 
				
			||||||
 | 
					                if (!checkResultCountry.exists) {
 | 
				
			||||||
 | 
					                    console.log(checkResultCountry.variants)
 | 
				
			||||||
 | 
					                    await client.sendText(roomId, i18n.t(
 | 
				
			||||||
 | 
					                        ["errors", "wrongcountry"],
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            first: checkResultCountry.variants[0].name,
 | 
				
			||||||
 | 
					                            second: checkResultCountry.variants[1].name,
 | 
				
			||||||
 | 
					                            third: checkResultCountry.variants[2].name
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ));
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                await processRequest(client, roomId, current_action, answer, 'city');
 | 
					                await processRequest(client, roomId, current_action, answer, 'city');
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "city":
 | 
					            case "city":
 | 
				
			||||||
 | 
					                let checkResultCity = await checkCity(answer); 
 | 
				
			||||||
 | 
					                if (!checkResultCity.exists) {
 | 
				
			||||||
 | 
					                    await client.sendText(roomId, i18n.t(
 | 
				
			||||||
 | 
					                        ["errors", "wrongcity"],
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            first: checkResultCity.variants[0].name,
 | 
				
			||||||
 | 
					                            second: checkResultCity.variants[1].name,
 | 
				
			||||||
 | 
					                            third: checkResultCity.variants[2].name,
 | 
				
			||||||
 | 
					                            fourth: checkResultCity.variants[3].name,
 | 
				
			||||||
 | 
					                            fifth: checkResultCity.variants[4].name,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ));
 | 
				
			||||||
 | 
					                    return;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
                await processRequest(client, roomId, current_action, answer, 'range');
 | 
					                await processRequest(client, roomId, current_action, answer, 'range');
 | 
				
			||||||
                break;
 | 
					                break;
 | 
				
			||||||
            case "range":
 | 
					            case "range":
 | 
				
			||||||
                answer = parseInt(answer.split(" ")[0]);
 | 
					                answer = parseInt(answer.split(" ")[0]);
 | 
				
			||||||
                console.log(answer)
 | 
					                if (!answer && answer != 0) {
 | 
				
			||||||
                if (!answer) {
 | 
					 | 
				
			||||||
                    await client.sendText(roomId, i18n.t(["setup", "range"]));
 | 
					                    await client.sendText(roomId, i18n.t(["setup", "range"]));
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -187,7 +215,7 @@ client.on("room.message", async (roomId, event) => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                } else if (answer == '🏠️' || answer == '4') {
 | 
					                } else if (answer == '🏠️' || answer == '4') {
 | 
				
			||||||
                    await client.sendText(roomId, messages.general.menu);
 | 
					                    await client.sendText(roomId, i18n.t(["general", "menu"]));
 | 
				
			||||||
                    await setUserState(roomId, 'menu');
 | 
					                    await setUserState(roomId, 'menu');
 | 
				
			||||||
                    return;
 | 
					                    return;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -87,8 +87,7 @@ ${chosenProfile.description}`;
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await client.sendText(roomId, i18n.t(["general", "rate"]));
 | 
				
			||||||
    await client.sendText(roomId, messages.general.rate);
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showProfileToUser = async (client, roomId, profileId) => {
 | 
					const showProfileToUser = async (client, roomId, profileId) => {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,6 +37,8 @@
 | 
				
			|||||||
        "didntunderstand": "I cannot understand your answer. Please, read my question again and answer!",
 | 
					        "didntunderstand": "I cannot understand your answer. Please, read my question again and answer!",
 | 
				
			||||||
        "notimplemented": "This feature is not yet implemented! Please, keep track of my updates at https://git.foxarmy.org/leca/heart2heart. Bother your admins to update, if released! ;)",
 | 
					        "notimplemented": "This feature is not yet implemented! Please, keep track of my updates at https://git.foxarmy.org/leca/heart2heart. Bother your admins to update, if released! ;)",
 | 
				
			||||||
        "noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!",
 | 
					        "noprofiles": "There are no profiles left for you! I'm sorry. Please, come back later!",
 | 
				
			||||||
        "alreadymessaged": "You have already messaged that person. Your message was not sent!"
 | 
					        "alreadymessaged": "You have already messaged that person. Your message was not sent!",
 | 
				
			||||||
 | 
					        "wrongcountry": "There is no such country. Perhaps, you meant %{first}, %{second} or %{third}?",
 | 
				
			||||||
 | 
					        "wrongcity": "There is no such city. Perhaps, you meant %{first}, %{second}, %{third}, %{fourth} or %{fifth}?"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -37,7 +37,8 @@
 | 
				
			|||||||
        "didntunderstand": "Я не понимаю ваш ответ. Пожалуйста, перечитайте моё прошлое сообщение и попробуйте снова!",
 | 
					        "didntunderstand": "Я не понимаю ваш ответ. Пожалуйста, перечитайте моё прошлое сообщение и попробуйте снова!",
 | 
				
			||||||
        "notimplemented": "Эта возможность ещё не добавлена! Пожалуйста, следите за обновлениями на моём git'е: https://git.foxarmy.org/leca/heart2heart. Доставайте своих админов, чтобы обновлялись, когда новая версия выходит ;)",
 | 
					        "notimplemented": "Эта возможность ещё не добавлена! Пожалуйста, следите за обновлениями на моём git'е: https://git.foxarmy.org/leca/heart2heart. Доставайте своих админов, чтобы обновлялись, когда новая версия выходит ;)",
 | 
				
			||||||
        "noprofiles": "Не могу найти профили, которые подходят вашим критериям! Извините. Попробуйте позже!",
 | 
					        "noprofiles": "Не могу найти профили, которые подходят вашим критериям! Извините. Попробуйте позже!",
 | 
				
			||||||
        "alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!"
 | 
					        "alreadymessaged": "Вы уже отправляли сообщение этому человеку. Ваше сообщение не было отправлено!",
 | 
				
			||||||
 | 
					        "wrongcountry": "Не знаю такой страны. Возможно, вы имели в виду %{first}, %{second} или %{third}?",
 | 
				
			||||||
 | 
					        "wrongcity": "Не знаю такого города. Возможно, вы имели в виду %{first}, %{second}, %{third}, %{fourth} или %{fifth}?"
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user