From 91184506b9f0a703991fac164f935e8cde6b5da2 Mon Sep 17 00:00:00 2001 From: mpgn Date: Fri, 11 Aug 2017 00:27:29 +0200 Subject: [PATCH 1/4] Add challenges statistics form a user :) --- site/index.html | 54 ++++++++++++++++++++++++++++++++++++----------- site/users.json | 2 +- updater/update.py | 28 ++++++++++++++++++------ 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/site/index.html b/site/index.html index 5c3a4fa..06ee5dc 100644 --- a/site/index.html +++ b/site/index.html @@ -21,7 +21,15 @@ border-bottom: 0rem solid #727e96; } .bar .bar-item { - background: #73d019; + background: #9acc14; + } + .bar { + background: #4e4e4e; + } + tbody tr:first-child td .tile .tile-content:before { + font-family: FontAwesome; + content: "\f005 " !important; + color: #ffb860; } @@ -47,14 +55,35 @@

Hall Of Fame Root-Me

- - - - - + + + + + + + + + + + +
NameProgressionRankOverall Points
NameProgressionProgressionFlagRank
0deer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
7410
21837
@@ -69,12 +98,13 @@

Hall Of Fame Root-Me

diff --git a/site/users.json b/site/users.json index 48c8266..f9215fe 100644 --- a/site/users.json +++ b/site/users.json @@ -1 +1 @@ -{"total_points": "11390", "total_challenge": "275", "users": [{"username": "Capitaine-John", "realn": "mpuygrenier", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg", "rank": "159", "points": "5030", "challenges": "180", "status": "programmer"}, {"username": "v1p3r", "realn": "clabro", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg", "rank": "50", "points": "6975", "challenges": "227", "status": "hacker"}]} \ No newline at end of file +{"total_points": "11390", "total_challenge": "275", "users": [{"username": "Capitaine-John", "username_r": "bonclay", "realn": "mpuygrenier", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg", "rank": "159", "points": "5030", "challenges": "180", "status": "programmer", "details": {"App-Script": 0, "App-System": 0, "Cracking": 0, "Cryptanalysis": 0, "Forensic": 0, "Programming": 0, "Realist": 0, "Network": 0, "Steganography": 0, "Web-Server": 0, "Web-Client": 0}}, {"username": "v1p3r", "username_r": "v1p3r", "realn": "clabro", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg", "rank": "50", "points": "6975", "challenges": "227", "status": "hacker", "details": {"App-Script": 0, "App-System": 0, "Cracking": 0, "Cryptanalysis": 0, "Forensic": 0, "Programming": 0, "Realist": 0, "Network": 0, "Steganography": 0, "Web-Server": 0, "Web-Client": 0}}]} \ No newline at end of file diff --git a/updater/update.py b/updater/update.py index e995b17..ad19468 100644 --- a/updater/update.py +++ b/updater/update.py @@ -3,7 +3,7 @@ import requests import sys -with open('users.json', 'r') as data_file: +with open('../site/users.json', 'r') as data_file: data = json.load(data_file) def wrong_arg(): @@ -14,6 +14,12 @@ def wrong_arg(): def update_users(user): print("[+] Starting update profil") r = requests.get('https://www.root-me.org/'+ user['username'] + '?inc=score') + + regex = r'(.*)' + matches = re.search(regex, r.text) + if matches: + user['username_r'] = "{group}".format(group = matches.group(1)) + regex = r'\n(.*)/(.*)' matches = re.search(regex, r.text) if matches: @@ -42,9 +48,16 @@ def update_users(user): avatar = "{group}".format(group = matches.group(1)) user['avatar'] = "https://www.root-me.org/" + avatar - print("[+]", user['username'], user['realn'], user['avatar'], user['rank'], user['points'] ,user['challenges'], user['status']) + print("[+]", user['username'], user['username_r'], user['realn'], user['avatar'], user['rank'], user['points'] ,user['challenges'], user['status']) + + # Get all data from challenge + regex = r'\n([0-9]*) Points ([0-9]*)/([0-9]*)' + matches = re.finditer(regex, r.text) + for matchNum, match in enumerate(matches): + matchNum = matchNum + 1 + print("{group}".format(group = match.group(1)),"=>", "{group}".format(group = match.group(2)),'/', "{group}".format(group = match.group(3))) - with open('users.json', 'w') as data_file: + with open('../site/users.json', 'w') as data_file: json.dump(data, data_file) print("[+] End update profil") @@ -52,6 +65,8 @@ def update_users(user): def update(): print("[+] Starting Update generic information") + # get generic data + global r r = requests.get('https://www.root-me.org/fr/Communaute/Classement/') regex = r' Date: Fri, 11 Aug 2017 15:53:31 +0200 Subject: [PATCH 2/4] Add challenge progression bar --- site/index.html | 60 +++++++++++++++++++++++++++-------------------- site/users.json | 2 +- updater/update.py | 22 ++++++++++------- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/site/index.html b/site/index.html index 06ee5dc..fa60920 100644 --- a/site/index.html +++ b/site/index.html @@ -55,35 +55,17 @@

Hall Of Fame Root-Me

- + - - + + + - - - - - - +
NameProgressionProgressionChallengesOverall Flag RankgO
0deer
-
-
-
-
-
-
-
-
-
-
-
-
-
-
7410
21837
@@ -97,13 +79,41 @@

Hall Of Fame Root-Me

diff --git a/site/users.json b/site/users.json index f9215fe..f5d5204 100644 --- a/site/users.json +++ b/site/users.json @@ -1 +1 @@ -{"total_points": "11390", "total_challenge": "275", "users": [{"username": "Capitaine-John", "username_r": "bonclay", "realn": "mpuygrenier", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg", "rank": "159", "points": "5030", "challenges": "180", "status": "programmer", "details": {"App-Script": 0, "App-System": 0, "Cracking": 0, "Cryptanalysis": 0, "Forensic": 0, "Programming": 0, "Realist": 0, "Network": 0, "Steganography": 0, "Web-Server": 0, "Web-Client": 0}}, {"username": "v1p3r", "username_r": "v1p3r", "realn": "clabro", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg", "rank": "50", "points": "6975", "challenges": "227", "status": "hacker", "details": {"App-Script": 0, "App-System": 0, "Cracking": 0, "Cryptanalysis": 0, "Forensic": 0, "Programming": 0, "Realist": 0, "Network": 0, "Steganography": 0, "Web-Server": 0, "Web-Client": 0}}]} \ No newline at end of file +{"users": [{"username": "Capitaine-John", "status": "programmer", "realn": "mpuygrenier", "username_r": "bonclay", "points": "5030", "details": [{"name": "App-Script", "total": "12", "flag": "10", "points": "225", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "16", "points": "495", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "8", "points": "85", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "22", "points": "425", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "8", "points": "235", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "5", "points": "55", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "18", "points": "895", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "16", "points": "350", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "10", "points": "155", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "17", "points": "575", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "180", "rank": "159", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg"}, {"username": "v1p3r", "status": "hacker", "realn": "clabro", "username_r": "v1p3r", "points": "6975", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "18", "points": "560", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "23", "points": "630", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "31", "points": "755", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "18", "points": "665", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "7", "points": "115", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "21", "points": "1125", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "17", "points": "385", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "18", "points": "645", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "227", "rank": "50", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg"}, {"username": "RainbowLyte", "status": "hacker", "realn": "demo", "username_r": "0deer", "points": "7410", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "31", "points": "1950", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "25", "points": "765", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "19", "points": "310", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "12", "points": "385", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "8", "points": "175", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "19", "points": "945", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "15", "points": "305", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "16", "points": "490", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "49", "points": "1525", "color": "#35a2ff"}], "challenges": "218", "rank": "37", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton7416-97761.jpg"}], "total_points": "11390", "total_challenge": "275"} \ No newline at end of file diff --git a/updater/update.py b/updater/update.py index ad19468..61b331b 100644 --- a/updater/update.py +++ b/updater/update.py @@ -15,7 +15,7 @@ def update_users(user): print("[+] Starting update profil") r = requests.get('https://www.root-me.org/'+ user['username'] + '?inc=score') - regex = r'(.*)' + regex = r'

.*(.*)' matches = re.search(regex, r.text) if matches: user['username_r'] = "{group}".format(group = matches.group(1)) @@ -48,14 +48,19 @@ def update_users(user): avatar = "{group}".format(group = matches.group(1)) user['avatar'] = "https://www.root-me.org/" + avatar - print("[+]", user['username'], user['username_r'], user['realn'], user['avatar'], user['rank'], user['points'] ,user['challenges'], user['status']) + print("[+] Infos user:") + print("\t Url name", user['username'], "\n\t Username", user['username_r'], "\n\t Realn", user['realn'], "\n\t Avatar", user['avatar'], "\n\t Rank", user['rank'], "\n\t Points", user['points'], "\n\t Challenges succed",user['challenges'], "\n\t Status", user['status']) # Get all data from challenge regex = r'\n([0-9]*) Points ([0-9]*)/([0-9]*)' matches = re.finditer(regex, r.text) - for matchNum, match in enumerate(matches): - matchNum = matchNum + 1 - print("{group}".format(group = match.group(1)),"=>", "{group}".format(group = match.group(2)),'/', "{group}".format(group = match.group(3))) + for iter_chall, match in enumerate(matches): + user['details'][iter_chall]["points"] = "{group}".format(group = match.group(1)) + user['details'][iter_chall]["flag"] = "{group}".format(group = match.group(2)) + user['details'][iter_chall]["total"] = "{group}".format(group = match.group(3)) + + for detail in user['details']: + print('\t',detail['name'], detail['points'], detail['flag'], detail['total']) with open('../site/users.json', 'w') as data_file: json.dump(data, data_file) @@ -65,8 +70,7 @@ def update_users(user): def update(): print("[+] Starting Update generic information") - # get generic data - global r + # get generic data r = requests.get('https://www.root-me.org/fr/Communaute/Classement/') regex = r' Date: Fri, 11 Aug 2017 16:09:18 +0200 Subject: [PATCH 3/4] Add legend --- README.md | 13 +++++++------ site/index.html | 13 +++++-------- site/users.json | 2 +- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 4c093d8..930a065 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# HallOfFame +# HallOfFame Root-me -Hall Of Fame dashboard for Root-me.org. All users are manually added to the file `users.json`, then with a cron job, the script `update.py` do the rest. +Hall Of Fame dashboard for Root-me.org. Users information are in the `users.json`. -![screenshot](https://i.gyazo.com/1823b9244ef0c4c98d5877f837880dda.png) +![screenshot](https://i.gyazo.com/56b4c6fcc5c5306e3d904735c0716835.png) ## Informations @@ -12,12 +12,13 @@ Since root-me doesn't have any API, this is dirty parsing ! But it's working ;) Add the user in the `users.json` like this : ``` -python update.py 'username' 'realname' +python update.py username realname ``` +You can get the useranme form the url of a Root-me profile : https://www.root-me.org/username -Update the json like this : +Update the json like this : ``` -python3 update.py +python3 update.py update ``` Use a cron job to update the script every night ;) diff --git a/site/index.html b/site/index.html index fa60920..9c6bd31 100644 --- a/site/index.html +++ b/site/index.html @@ -36,23 +36,20 @@
+ : 100% challenges flagged   + : Leader of the Hall of Fame @@ -61,7 +58,7 @@

Hall Of Fame Root-Me

- + @@ -108,9 +105,9 @@

Hall Of Fame Root-Me

+ '' + ''); $.each( val['details'], function( key_c, categorie ) { - $("#" + val.username_r).append('
'); + $("#" + val.username_r).append('
'); if(categorie.flag == categorie.total) { - $("#trophy_" + val.username_r).append(''); + $("#trophy_" + val.username_r).append(''); } }); diff --git a/site/users.json b/site/users.json index f5d5204..6f85016 100644 --- a/site/users.json +++ b/site/users.json @@ -1 +1 @@ -{"users": [{"username": "Capitaine-John", "status": "programmer", "realn": "mpuygrenier", "username_r": "bonclay", "points": "5030", "details": [{"name": "App-Script", "total": "12", "flag": "10", "points": "225", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "16", "points": "495", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "8", "points": "85", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "22", "points": "425", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "8", "points": "235", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "5", "points": "55", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "18", "points": "895", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "16", "points": "350", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "10", "points": "155", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "17", "points": "575", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "180", "rank": "159", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg"}, {"username": "v1p3r", "status": "hacker", "realn": "clabro", "username_r": "v1p3r", "points": "6975", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "18", "points": "560", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "23", "points": "630", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "31", "points": "755", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "18", "points": "665", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "7", "points": "115", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "21", "points": "1125", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "17", "points": "385", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "18", "points": "645", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "227", "rank": "50", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg"}, {"username": "RainbowLyte", "status": "hacker", "realn": "demo", "username_r": "0deer", "points": "7410", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "31", "points": "1950", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "25", "points": "765", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "19", "points": "310", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "12", "points": "385", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "8", "points": "175", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "19", "points": "945", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "15", "points": "305", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "16", "points": "490", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "49", "points": "1525", "color": "#35a2ff"}], "challenges": "218", "rank": "37", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton7416-97761.jpg"}], "total_points": "11390", "total_challenge": "275"} \ No newline at end of file +{"users": [{"username": "Capitaine-John", "status": "programmer", "realn": "mpuygrenier", "username_r": "bonclay", "points": "5030", "details": [{"name": "App-Script", "total": "12", "flag": "10", "points": "225", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "16", "points": "495", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "8", "points": "85", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "22", "points": "425", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "8", "points": "235", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "5", "points": "55", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "18", "points": "895", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "16", "points": "350", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "10", "points": "155", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "17", "points": "575", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "180", "rank": "159", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton29499-458aa.jpg"}, {"username": "v1p3r", "status": "hacker", "realn": "d3mo", "username_r": "v1p3r", "points": "6975", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "18", "points": "560", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "23", "points": "630", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "31", "points": "755", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "18", "points": "665", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "7", "points": "115", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "21", "points": "1125", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "17", "points": "385", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "18", "points": "645", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "50", "points": "1535", "color": "#35a2ff"}], "challenges": "227", "rank": "50", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton9374-f101c.jpg"}, {"username": "RainbowLyte", "status": "hacker", "realn": "demo", "username_r": "0deer", "points": "7410", "details": [{"name": "App-Script", "total": "12", "flag": "12", "points": "350", "color": "#dbff6b"}, {"name": "App-System", "total": "55", "flag": "31", "points": "1950", "color": "#6166ff"}, {"name": "Cracking", "total": "26", "flag": "25", "points": "765", "color": "#ff4141"}, {"name": "Cryptanalysis", "total": "34", "flag": "19", "points": "310", "color": "#b06cfb"}, {"name": "Forensic", "total": "18", "flag": "12", "points": "385", "color": "#35de59"}, {"name": "Progamming", "total": "8", "flag": "8", "points": "175", "color": "#6db8e4"}, {"name": "Realist", "total": "23", "flag": "19", "points": "945", "color": "#ff5887"}, {"name": "Network", "total": "17", "flag": "15", "points": "305", "color": "#e1e0ff"}, {"name": "Steganography", "total": "13", "flag": "12", "points": "210", "color": "#a441ff"}, {"name": "Web-Client", "total": "18", "flag": "16", "points": "490", "color": "#ff84f0"}, {"name": "Web-Server", "total": "51", "flag": "49", "points": "1525", "color": "#35a2ff"}], "challenges": "218", "rank": "37", "avatar": "https://www.root-me.org/local/cache-vignettes/L64xH64/auton7416-97761.jpg"}], "total_points": "11390", "total_challenge": "275"} \ No newline at end of file From 4a383a441c2f41de11aaec860a96f6cee2626a08 Mon Sep 17 00:00:00 2001 From: mpgn Date: Fri, 11 Aug 2017 16:10:47 +0200 Subject: [PATCH 4/4] Update readme --- site/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/index.html b/site/index.html index 9c6bd31..42f3d39 100644 --- a/site/index.html +++ b/site/index.html @@ -48,7 +48,7 @@

Hall Of Fame Root-Me

- : 100% challenges flagged   + : 100% of challenges flagged in a category   : Leader of the Hall of Fame
Overall Flag RankgOTrophy