Got twitter bot working, have player attribute responses
This commit is contained in:
parent
4db745c28a
commit
f2752967d3
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -364,3 +364,6 @@ FodyWeavers.xsd
|
||||||
|
|
||||||
# Venv files
|
# Venv files
|
||||||
Hockey/
|
Hockey/
|
||||||
|
|
||||||
|
# Data directiory
|
||||||
|
Data/
|
22
SimHoc.py
22
SimHoc.py
|
@ -1 +1,23 @@
|
||||||
|
import os, pygame, player, tweepy, twitHandler, time
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
#for name in ["Vivi", "Artemis", "Laika", "Sharks", "Dragons", "Melua", "Sabriina", "Jorts (Buttered)", "Jorts (Unbuttered)"]:
|
||||||
|
# plyr = player.Player(name)
|
||||||
|
# print(f"{name}:")
|
||||||
|
# for atr in plyr.attributes:
|
||||||
|
# print(atr)
|
||||||
|
# print("----------")
|
||||||
|
|
||||||
|
twitter = twitHandler.TwitHandler()
|
||||||
|
if os.path.exists(os.path.join("Data", "lastID.twt")):
|
||||||
|
with open(os.path.join("Data", "lastID.twt")) as idFile:
|
||||||
|
lastID = idFile.readline().strip()
|
||||||
|
else:
|
||||||
|
lastID = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
twitter.scanForMention(lastID)
|
||||||
|
time.sleep(30)
|
||||||
|
with open(os.path.join("Data", "lastID.twt")) as idFile:
|
||||||
|
lastID = idFile.readline().strip()
|
||||||
|
#twitter.sendTextTweet(player.Player("Amogus").twitterString())
|
|
@ -22,6 +22,16 @@
|
||||||
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
<EnableUnmanagedDebugging>false</EnableUnmanagedDebugging>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<Compile Include="attributes.py" />
|
||||||
|
<Compile Include="twitHandler.py">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="db.py">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
|
<Compile Include="player.py">
|
||||||
|
<SubType>Code</SubType>
|
||||||
|
</Compile>
|
||||||
<Compile Include="SimHoc.py" />
|
<Compile Include="SimHoc.py" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -35,6 +45,13 @@
|
||||||
<Architecture>X86</Architecture>
|
<Architecture>X86</Architecture>
|
||||||
</Interpreter>
|
</Interpreter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Data\" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Data\s options.txt" />
|
||||||
|
<Content Include="Data\Twitter.keys" />
|
||||||
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\Python Tools\Microsoft.PythonTools.targets" />
|
||||||
<!-- Uncomment the CoreCompile target to enable the Build command in
|
<!-- Uncomment the CoreCompile target to enable the Build command in
|
||||||
Visual Studio and specify your pre- and post-build commands in
|
Visual Studio and specify your pre- and post-build commands in
|
||||||
|
|
115
attributes.py
Normal file
115
attributes.py
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
import math, random
|
||||||
|
|
||||||
|
versionNumber = 1
|
||||||
|
|
||||||
|
#Class and function definitions
|
||||||
|
class Attribute:
|
||||||
|
"""Contains a name string and a value float."""
|
||||||
|
def __init__(self, name:str, value:float):
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
|
||||||
|
def newValue(self, new):
|
||||||
|
self.value = new
|
||||||
|
|
||||||
|
def twitterFormat(self):
|
||||||
|
frac, whole = math.modf(self.value/20)
|
||||||
|
valueString = ""
|
||||||
|
valueString += "🟩"*int(whole) #green square
|
||||||
|
if frac > 0.1 and frac < 0.5:
|
||||||
|
valueString += "🟧" #orange square
|
||||||
|
elif frac >= 0.5 and frac < 0.9:
|
||||||
|
valueString += "🟨" #yellow square
|
||||||
|
elif frac > 0.9:
|
||||||
|
valueString += "🟩" #green square
|
||||||
|
while len(valueString) < 5:
|
||||||
|
valueString += "🟥" #red square
|
||||||
|
return f"{valueString} - {self.name}"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.name}: {int(self.value)}"
|
||||||
|
|
||||||
|
#Comparison helpers
|
||||||
|
def __eq__(self, other): #can compare attributes by name, or find attribute by value
|
||||||
|
if isinstance(other, Attribute):
|
||||||
|
return self.name == other.name
|
||||||
|
elif isinstance(other, int):
|
||||||
|
return self.value == other
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
#less than/greater than compare attributes by value, or compare value to int
|
||||||
|
def __lt__(self, value):
|
||||||
|
if isinstance(other, Attribute):
|
||||||
|
return self.value < other.value
|
||||||
|
elif isinstance(other, int):
|
||||||
|
return self.value < other
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __le__(self, value):
|
||||||
|
if isinstance(other, Attribute):
|
||||||
|
return self.value <= other.value
|
||||||
|
elif isinstance(other, int):
|
||||||
|
return self.value <= other
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, Attribute):
|
||||||
|
return self.value > other.value
|
||||||
|
elif isinstance(other, int):
|
||||||
|
return self.value > other
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if isinstance(other, Attribute):
|
||||||
|
return self.value >= other.value
|
||||||
|
elif isinstance(other, int):
|
||||||
|
return self.value >= other
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def normalDis(generator:random.Random, mean:float, stdDev:float, min:float=-math.inf, max:float=math.inf) -> float:
|
||||||
|
"""Generates random number from normal distribution with given mean and standard deviation. Optionally takes min and max values."""
|
||||||
|
num = generator.gauss(mean, stdDev)
|
||||||
|
while min > num or num > max:
|
||||||
|
num = generator.gauss(mean, stdDev)
|
||||||
|
return num
|
||||||
|
|
||||||
|
#Attributes to generate
|
||||||
|
#["sample name", mean, stdDev, min(optional), max(optional)]
|
||||||
|
masterList = [
|
||||||
|
["Speed", 60, 25, 0, 100], #sprint speed (Forward)
|
||||||
|
["Agility", 50, 35, 0, 100], #changing directions/goaltending slide (Goalie)
|
||||||
|
["Reflexes", 50, 35, 0, 100], #deflections/goaltending blocks/intercepting passes (Goalie)
|
||||||
|
["Strength", 40, 60, 0, 100], #power of slapshot/body checking (Defense)
|
||||||
|
["Dexterity", 50, 25, 10, 100], #power of wrist shot/dekes (Forward)
|
||||||
|
["Shot Accuracy", 40, 25, 10, 100], #accuracy on shots (Secondary/All Skaters)
|
||||||
|
["Pass Accuracy", 60, 35, 0, 100], #does what you think it does (Defense)
|
||||||
|
["Wisdom", 40, 50, 0, 100], #takes better shots/makes safer passes (Secondary/All)
|
||||||
|
["Stickhandling", 50, 35, 0, 100], #dekes/checking (Secondary/All)
|
||||||
|
["Discipline", 75, 60, 0, 100], #Higher means less penalties
|
||||||
|
["Intelligence", 50, 50, 0, 100], #Skill at positioning (as skater or goalie) (Secondary/All)
|
||||||
|
["Constitution", 60, 20, 0, 100] #Stamina/injury resilience
|
||||||
|
]
|
||||||
|
|
||||||
|
noPrint = [
|
||||||
|
"Discipline",
|
||||||
|
"Constitution"
|
||||||
|
]
|
||||||
|
|
||||||
|
def attributesFromName(name:str):
|
||||||
|
"""Returns a List of Attribute objects, seeded by the name parameter."""
|
||||||
|
generator = random.Random() #individual random instance to avoid conflicts
|
||||||
|
generator.seed(name)
|
||||||
|
atrs = [versionNumber] #in case of stat changes, record which number to use
|
||||||
|
for template in masterList:
|
||||||
|
atrs.append(Attribute(template[0], normalDis(generator, *template[1:])))
|
||||||
|
return atrs
|
19
db.py
Normal file
19
db.py
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import os
|
||||||
|
import sqlite3 as sql
|
||||||
|
|
||||||
|
dataDir = "Data"
|
||||||
|
dbName = "NSHL.db"
|
||||||
|
|
||||||
|
def connect():
|
||||||
|
#create connection, create db if doesn't exist
|
||||||
|
conn = None
|
||||||
|
try:
|
||||||
|
conn = sql.connect(os.path.join(data_dir, dbName))
|
||||||
|
|
||||||
|
# enable write-ahead log for performance and resilience
|
||||||
|
conn.execute('pragma journal_mode=wal')
|
||||||
|
|
||||||
|
return conn
|
||||||
|
except:
|
||||||
|
print("oops, db connection no work")
|
||||||
|
return conn
|
44
player.py
Normal file
44
player.py
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import attributes
|
||||||
|
|
||||||
|
class CreationError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Player(object):
|
||||||
|
"""A hockey player with attributes and various functions."""
|
||||||
|
|
||||||
|
def __init__(self, name:str):
|
||||||
|
if len(name) > 30:
|
||||||
|
raise CreationError("Player name too long.")
|
||||||
|
self.name = name
|
||||||
|
self.attributes = self.loadAttributes()
|
||||||
|
|
||||||
|
def loadAttributes(self):
|
||||||
|
"""Generates attributes based on name, or loads from database if present."""
|
||||||
|
rawAtrs = attributes.attributesFromName(self.name)
|
||||||
|
self.attributesVersion = rawAtrs[0]
|
||||||
|
return rawAtrs[1:]
|
||||||
|
|
||||||
|
def twitterString(self):
|
||||||
|
"""Generates a twitter-formatted string representing the player."""
|
||||||
|
send = f"{self.name}:\n"
|
||||||
|
for attr in self.attributes:
|
||||||
|
if attr.name not in attributes.noPrint:
|
||||||
|
send += attr.twitterFormat()
|
||||||
|
send += "\n"
|
||||||
|
return send
|
||||||
|
|
||||||
|
def __eq__(self, value):
|
||||||
|
if isinstance(value, Player):
|
||||||
|
return self.name == value.name
|
||||||
|
elif isinstance(value, str):
|
||||||
|
return self.name == value
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Skater(Player):
|
||||||
|
"""A hockey player that is not a goalie."""
|
||||||
|
|
||||||
|
|
||||||
|
class Goalie(Player):
|
||||||
|
"""A hockey player that *is* a goalie."""
|
45
twitHandler.py
Normal file
45
twitHandler.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import tweepy, os, json, player
|
||||||
|
|
||||||
|
dataDir = "Data"
|
||||||
|
|
||||||
|
class TwitHandler(object):
|
||||||
|
"""Twitter connection handler class"""
|
||||||
|
api = tweepy.Client()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
path = os.path.join(dataDir, "Twitter.keys")
|
||||||
|
if os.path.exists(path):
|
||||||
|
with open(path) as keysFile:
|
||||||
|
bearerToken, consumerKey, consumerSecret, accessKey, accessSecret = [line.strip() for line in keysFile.readlines()]
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError
|
||||||
|
self.api = tweepy.Client(bearerToken, consumerKey, consumerSecret, accessKey, accessSecret)
|
||||||
|
|
||||||
|
def scanForMention(self, lastRepliedID):
|
||||||
|
mentions = self.api.get_users_mentions(1479541275862908928, since_id=lastRepliedID, max_results=20)
|
||||||
|
if mentions.data is None or len(mentions.data) == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
for mention in reversed(mentions.data): #do oldest first
|
||||||
|
lastID = mention.id
|
||||||
|
|
||||||
|
if "rate " in mention.text.lower():
|
||||||
|
try:
|
||||||
|
name = mention.text.split("rate ",1)[1]
|
||||||
|
self.sendTextReply(player.Player(name).twitterString(), mention)
|
||||||
|
except:
|
||||||
|
print("Tweet already replied to.")
|
||||||
|
|
||||||
|
with open(os.path.join(dataDir, "lastID.twt"), 'w') as file:
|
||||||
|
file.write(str(lastID+1))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def sendTextTweet(self, text:str):
|
||||||
|
self.api.create_tweet(text=text)
|
||||||
|
|
||||||
|
def sendTextReply(self, text:str, prevTweet):
|
||||||
|
self.api.create_tweet(in_reply_to_tweet_id=prevTweet.id, text=text)
|
||||||
|
|
||||||
|
def changeBio(self, newText):
|
||||||
|
pass #awaiting API v1 permission
|
Loading…
Reference in a new issue