Patrick Baumgartner 4 年 前
コミット
b73c400c97

+ 97 - 0
Jessi/ContentFile.py

@@ -0,0 +1,97 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import json
+from Texts import Contents
+import Stats
+import random
+
+class ContentFile(object):
+
+
+    # text inloaded
+    # tags[keyname] = [list of indexes]
+    # text[i] = [list of Contents objects]
+
+    def __init__(self, file:str):
+        self._file = file
+        self.tags = dict()
+        self.entries = list()
+        self.__load()
+        pass
+
+    def __load(self):
+        print("ContentFile::__load()")
+        self.entries.clear()
+        self.entries = list()
+        stats = Stats.Stats.getInstance()
+        print("ContentFile::__load()- Load File: text\\"+self._file)
+        with open('text\\'+self._file) as f:
+            data = json.load(f)
+
+        for i in data:
+            add = True
+            if "mood_required" in i:
+                statKeys = ["angry","annoyed","aroused","bored","compfortable","depressive","energy","excited","exhausted",
+                            "freezy","funny","happyness","hot","humanity","hungry","intelligent","irritated","lonely","naiv",
+                            "playful","sad","shy","sleepy","soziality","temperate","toLoud","toQuiet","usefull","useless"]
+                for sk in statKeys:
+                    if sk+"_max" in i["mood_required"] and i["mood_required"][sk+"_max"] <= getattr(stats.deviceStats,sk):
+                        add = False
+                        break
+                    if sk+"_min" in i["mood_required"] and i["mood_required"][sk+"_min"] >= getattr(stats.deviceStats,sk):
+                        add = False
+                        break
+            if add == True and "text" in i:
+                self.entries.append(Contents.Contents(i))
+        print("Loading done")
+
+
+    def getText(self, tags):
+        tagList = self.__argumentToTagList(tags)
+        res = dict()
+        maxNum = 1
+        for e in self.entries:
+            num = e._hasTags(tagList)
+            if num>=maxNum:
+                maxNum = num
+                if num in res:
+                    res[num].append(e)
+                else:
+                    res[num]=list()
+                    res[num].append(e)
+        return random.choice(res[maxNum])
+
+    def executeText(self, tags):
+        obj = self.getText(tags)
+        if obj is not None:
+            stats = Stats.Stats.getInstance()
+            statKeys = ["angry","annoyed","aroused","bored","compfortable","depressive","energy","excited","exhausted",
+                        "freezy","funny","happyness","hot","humanity","hungry","intelligent","irritated","lonely","naiv",
+                        "playful","sad","shy","sleepy","soziality","temperate","toLoud","toQuiet","usefull","useless"]
+            #for sk in statKeys:
+            #    if sk in obj.addStats:
+            #        print("AddStats "+sk+" "+obj.addStats[sk])
+            #        val = getattr(stats.deviceStats,sk)
+            #        val+=obj.addStats[sk]
+            #        setattr(stats.deviceStats, sk, val)
+                    
+            rndText = random.choice(obj.text)
+            return rndText
+        return ""
+
+    def Update():
+        self.__load()
+
+
+    def __argumentToTagList(self, arg):
+        res = list()
+        if isinstance(arg,list):
+            for i in arg:
+                tmp = self.__argumentToTagList(i)
+                for j in tmp:
+                    res.append(j)
+        else:
+            tmp = arg.split(",")
+            for i in tmp:
+                res.append(i.strip().lower())
+        return res

+ 107 - 0
Jessi/Contents.py

@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+
+class Contents(object):
+    
+    text:list
+    addStats:dict
+    actions:list
+    tags:list
+
+    def __init__(self, item:list = None):
+        self.tags = list()
+        self.addStats = dict()
+        self.actions = list()
+        self.text = list()
+        print(item)
+        if item!=None:
+            if "tags" in item:
+                self.tags = self.__argumentToTagList(item["tags"])
+            if "mood_add" in item:
+                for x,y in item["mood_add"].items():
+                    self.addStats[x]=y
+            if "actions" in item:
+                for x in item["actions"]:
+                    self.actions.append(x)
+            if "text" in item:
+                for x in item["text"]:
+                    self.text.append(x)
+
+    def __del__(self):
+        del self.text
+        del self.addStats
+        del self.actions
+        del self.Tags
+
+
+    def hasTags(self, tagList):
+        return self._hasTags(self.__argumentToTagList(tagList))
+
+
+
+    # TagList Info:
+    # inside the .json File, when a Tag starts with "!", this ist a must have Tag
+    # inside the TagList, when a tag has a "+", this tag must be in the .json entry
+    # inside the TagList, when a tag has a "-", this tag must not be in the .json entry
+    def _hasTags(self, tagList):
+        # check High Required Tags
+        for e in self.tags:
+            if e[0:1]=="!":
+                found=False
+                for t in tagList:
+                    if t == e[1:] or t=="+"+e[1:]:
+                        found=True
+                        break
+                if found==False:
+                    print("JSON-Tag not found: "+e)
+                    return 0
+        # check required Tags
+        for t in tagList:
+            tag = t
+            found=False
+            if t[0:1]=="+":
+                for e in self.tags:
+                    if t[1:] == e or ("!"+t[1:])==e:
+                        found = True
+                        break
+                if found==False:
+                    print("Script-Tag not found: "+t+" ... " + t[1:] + " | " + "!"+t[1:])
+                    return 0
+            elif t[0:1]=="-":
+                found=False
+                for e in self.tags:
+                    if t[1:] == e:
+                        found = True
+                        break
+                if found==False:
+                    return 0
+            if found==True:
+                break
+        # get num matching tags
+        numFound = 0
+        for t in tagList:
+            tag1 = t
+            if tag1[0:1]=="+" or tag1[0:1]=="-" or tag1[0:1]=="*" or tag1[0:1]=="!" or tag1[0:1]=="~":
+                tag1=tag1[1:]
+            for e in self.tags:
+                tag2 = e
+                if tag2[0:1]=="+" or tag2[0:1]=="-" or tag2[0:1]=="*" or tag2[0:1]=="!" or tag2[0:1]=="~":
+                    tag2=tag2[1:]
+                if tag1==tag2:
+                    numFound+=1
+                    break
+        return numFound
+
+    def __argumentToTagList(self, arg):
+        res = list()
+        if isinstance(arg,list):
+            for i in arg:
+                tmp = self.__argumentToTagList(i)
+                for j in tmp:
+                    res.append(j)
+        else:
+            tmp = arg.split(",")
+            for i in tmp:
+                res.append(i.strip().lower())
+        return res

+ 8 - 0
Jessi/Replacer.py

@@ -0,0 +1,8 @@
+
+class Enchanter(object):
+
+    def __init__(self):
+        pass
+
+    def addEnchantment(self):
+        pass

+ 3 - 0
Jessi/__init__.py

@@ -0,0 +1,3 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from Texts.main import Text

+ 364 - 0
Jessi/main.py

@@ -0,0 +1,364 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from Stats import Stats
+import configparser
+from Network import Network
+from Texts import ContentFile
+import datetime
+import math
+import json
+import random
+import lib.MoonPhase as mp
+import os
+import re
+
+class NummericPronounce:
+    Normal = 0
+    Year = 1
+    Time = 2
+
+class Text:
+
+    
+    instance = None
+    content = dict()
+
+    @staticmethod
+    def getInstance():
+        if Text.instance==None:
+            Text.instance = Text()
+        return Text.instance
+
+    def __init__(self):
+        self.stats = Stats.getInstance()
+        self.config = configparser.ConfigParser()
+        self.config.read("config.ini")
+        self.replacer = list()
+        # TODO: Replacer.js laden, und alle 60-180 Sekunden eine List-Variable mit möglichen Antworten füllen / aktualisieren
+        #       z.B. aus der Replacer.js "badwords" mit allen möglichen Antworten, abhängig der Requirements, in die Variable "replacer["badwords"][]" füllen
+        #       Weitere mögliche Wortlisten: "niceword" (incl cuteness, wenn verspieltheit hoch ist)
+        self.readReplacer()
+        
+
+    def getText(self, intent, tags):
+        if intent not in self.content:
+            if os.path.isfile("text/"+intent+".json") == False:
+                print("Failed to load intent text: "+intent)
+                return ""
+            self.content[intent] = ContentFile.ContentFile(intent+".json")
+        return self.content[intent].getText(tags)
+
+    def executeText(self, intent, tags):
+        if intent not in self.content:
+            if os.path.isfile("text/"+intent+".json") == False:
+                print("Failed to load intent text: "+intent)
+                return ""
+            self.content[intent] = ContentFile.ContentFile(intent+".json")
+        return self.formatText(self.content[intent].executeText(tags))
+
+    def formatText(self, text:str ) -> str:
+        if text.find("{"):
+            if text.find("{device."):
+                text = text.replace("{device.name}",self.config["global"]["DeviceName"])
+                text = text.replace("{device.owner}",self.config["global"]["Owner"])
+                text = text.replace("{device.ip}", Network.getIp())
+                text = text.replace("{device.host}", Network.getHostName())
+                text = text.replace("{device.hostname}", Network.getHostName())
+            if text.find("{datetime."):
+                #Tomorow: datetime.datetime.now() + datetime.timedelta(days=1)
+                #Yesterday: datetime.datetime.now() - datetime.timedelta(days=1)
+                text = self._formatText_DateTime(text,datetime.datetime.now(),"now")
+                text = self._formatText_DateTime(text,datetime.datetime.now() + datetime.timedelta(days=1),"tomorow")
+                text = self._formatText_DateTime(text,datetime.datetime.now() - datetime.timedelta(days=1),"yesterday")
+                text = self._formatText_DateTime(text,datetime.datetime.now() - datetime.timedelta(days=1),"mordning") #TODO
+                text = self._formatText_DateTime(text,datetime.datetime.now() - datetime.timedelta(days=1),"midday") #TODO
+                text = self._formatText_DateTime(text,datetime.datetime.now() - datetime.timedelta(days=1),"evening") #TODO
+                text = self._formatText_DateTime(text,datetime.datetime.now() - datetime.timedelta(days=1),"night") #TODO
+            if text.find("{weather."):
+                pass
+            if text.find("{house."):
+                pass
+            if text.find("{event."):
+                pass
+            if text.find("{calender."):
+                pass
+            if text.find("{"):
+                print("Enter Replacer Loop")
+                deadcounter=0
+                for k in self.replacer:
+                    print("- Search for '"+k+"'")
+                    notfound=False
+                    while notfound==False:
+                        if text.find("{"+k+"|") >= 0:
+                            print("- found {"+k+"|")
+                            word = self.getRandomWord(self.replacer[k])
+                            if word=="":
+                                text = re.sub("\\{"+k+"(?:\\|([a-z]{0,5}))?\\}","",text,1)
+                            else:
+                                text = re.sub("\\{"+k+"(?:\\|([a-z]{0,5}))?\\}",word+"\\1",text,1)
+
+                            #pos1 = text.find("{"+k+"|")
+                            #pos2 = pos1 + 2 + len(k)
+                            #pos3 = text.find("}",pos1)
+
+                            #res = text[0:pos1] + word + text[pos2:pos3-pos2] + text[pos3:]
+                            #text=res
+
+                        elif text.find("{"+k+"}") >= 0:
+                            print("- found {"+k+"}")
+                            text = re.sub("\\{"+k+"\\}",self.getRandomWord(self.replacer[k]),text,1)
+                        else:
+                            notfound=True
+                            break
+                        deadcounter+=1
+                        if deadcounter > 40:
+                            break
+                print("Eexit Replacer Loop")
+        return text
+
+    def _formatText_DateTime(self,text:str,dt:datetime.datetime,name:str):
+        name="{datetime."+name+"."
+        if text.find(name):
+            dt = datetime.datetime.now() + datetime.timedelta(days=1)
+            text = text.replace(name+"weekday}", self.getWeekDay(dt.weekday()) )
+            text = text.replace(name+"year}", self.getNumericPronounce(dt.year,0, NummericPronounce.Year) )
+            text = text.replace(name+"month}", self.getMonthName(dt.month) )
+            text = text.replace(name+"day}", self.getNumericDottedPronounce(dt.day) )
+            text = text.replace(name+"hour}", self.getNumericPronounce(dt.hour) )
+            text = text.replace(name+"minute}", self.getNumericPronounce(dt.minute) )
+            text = text.replace(name+"seconds}", self.getNumericPronounce(dt.second) )
+            text = text.replace(name+"second}", self.getNumericPronounce(dt.second) )
+            text = text.replace(name+"}", self.getDateString(dt) )
+            text = text.replace(name+"moonphase}", mp.phase(time=dt)["name"] )
+        return text
+
+    def getRandomWord(self, items:list) -> str:
+        if len(items) > 0:
+            return random.choice(items)
+        else:
+            return ""
+
+    def readReplacer(self):
+        tmp = {}
+        with open("text/Replacer.json") as json_file:
+            data = json.load(json_file)
+        for k in data:
+            tmp[k] = list()
+            add = True
+            for i in data[k]:
+                if "mood_required" in i:
+                    statKeys = ["angry","annoyed","aroused","bored","compfortable","depressive","energy","excited","exhausted",
+                                "freezy","funny","happyness","hot","humanity","hungry","intelligent","irritated","lonely","naiv",
+                                "playful","sad","shy","sleepy","soziality","temperate","toLoud","toQuiet","usefull","useless"]
+                    for sk in statKeys:
+                        if sk+"_max" in i["mood_required"] and i["mood_required"][sk+"_max"] <= getattr(self.stats.deviceStats,sk):
+                            add = False
+                            break
+                        if sk+"_min" in i["mood_required"] and i["mood_required"][sk+"_min"] >= getattr(self.stats.deviceStats,sk):
+                            add = False
+                            break
+                if add == True and "text" in i:
+                    for t in i["text"]:
+                        tmp[k].append(t)
+        self.replacer = tmp
+
+    @staticmethod
+    def getWeekDay(weekday:int) -> str:
+        weekdays = [
+            "Sonntag",
+            "Montag",
+            "Dienstag",
+            "Mittwoch",
+            "Donnerstag",
+            "Freitag",
+            "Samstag",
+            "Sonntag"
+        ]
+        return weekdays[weekday]
+
+    @staticmethod
+    def getMonthName(month:int) -> str:
+        monthnames = [
+            "",
+            "Januar",
+            "Februar",
+            "März",
+            "April",
+            "Mai",
+            "Juni",
+            "Juli",
+            "August",
+            "September",
+            "Oktober",
+            "November",
+            "Dezember"
+        ]
+        return monthnames[month]
+
+    @staticmethod
+    def getNumericPronounce(num:float, decimals:int = 2, type:int = NummericPronounce.Normal) -> str:
+        ''' Formats a integer or floating point value into a readable string 
+        num:float - The Number that should be writeable
+        decimals:int - the amount of decimals (max 6)
+
+        Range: -999,999,999.999999 to 999,999,999.999999
+        
+        Returns :str'''
+
+
+        if type == NummericPronounce.Year:
+            s2=""
+            if num<0:
+                num*=-1
+                s2=" vor Christus"
+            if num>=1300 and num<2200:
+                num3 = math.floor(num/100)
+                s = Text.getNumericPronounce(num3)
+                num2 = num % 100
+                if(num2>9):
+                    s += " " + Text.getNumericPronounce(num2)
+                else:
+                    s += " null " + Text.getNumericPronounce(num2 % 10)
+                return s+s2
+        else:
+            # Check for negative numbers
+            if num<0:
+                return "Minus " + Text.getNumericPronounce(num * -1)
+
+            # Check for decimal
+            if math.floor(num)!=math.ceil(num):
+                if decimals<1:
+                    num=math.floor(num)
+                else:
+                    s = Text.getNumericPronounce(math.floor(num))
+                    num -= math.floor(num)
+                    s+=" Komma"
+                    s+= " " + Text.getNumericPronounce(math.floor(num*10)%10)
+                    if decimals>=2: s+= " " + Text.getNumericPronounce(math.floor(num*100)%10)
+                    if decimals>=3: s+= " " + Text.getNumericPronounce(math.floor(num*1000)%10)
+                    if decimals>=4: s+= " " + Text.getNumericPronounce(math.floor(num*10000)%10)
+                    if decimals>=5: s+= " " + Text.getNumericPronounce(math.floor(num*100000)%10)
+                    if decimals>=6: s+= " " + Text.getNumericPronounce(math.floor(num*1000000)%10)
+                    return s
+
+            mod = num%10
+            zehner=(num-mod) % 100
+            hunderter=(num-mod-zehner) % 1000
+            tausender=(num-mod-zehner-hunderter) % 1000000
+            millonener=(num-mod-zehner-hunderter-tausender) % 1000000000
+            if num==0: return "null"
+            elif num==1: return "eins" # handels 1 only
+            elif num==2: return "zwei" # handels 2, 200, 302, 2.000, 2.000.000
+            elif num==3: return "drei"
+            elif num==4: return "vier"
+            elif num==5: return "fünf"
+            elif num==6: return "sechs"
+            elif num==7: return "sieben"
+            elif num==8: return "acht"
+            elif num==9: return "neun"
+            elif num==10: return "zehn"
+            elif num==11: return "elf"
+            elif num==12: return "zwölf"
+            elif num<=19: return Text.getNumericPronounce(mod)+"zehn"
+            elif num==20: return "zwanzig"
+            elif num==30: return "dreissig"
+            elif num==60: return "sechzig"
+            elif num==70: return "siebzig"
+            elif num<=99 and mod == 0: return Text.getNumericPronounce(zehner/10)+"zig"
+            elif num<=99 and mod == 1: return "einund"+Text.getNumericPronounce(zehner) # handels 21, 31, 41, 51, ...
+            elif num<=99 and mod > 1: return Text.getNumericPronounce(mod) + "und"+ Text.getNumericPronounce(zehner) #handels 22, 23, 24, 25, ... 32, 33, 34, ..., 42, 53, ....
+            elif num==100: return "einhundert" # handels 100
+            elif num<=199: return "einhundert" + Text.getNumericPronounce(num%100) # handels 101, 102, 103, ...
+            elif num<=999 and num%100==0: return Text.getNumericPronounce(hunderter/100)+"hundert" # handels 200, 300, 400, 500, 600, 700, 800, 900
+            elif num<=999: return Text.getNumericPronounce(hunderter/100)+"hundert"+Text.getNumericPronounce(num%100) # handels 201, 202, ... 301, 302
+            elif num==1000: return "eintausend"
+            elif num==10000: return "zehntausend"
+            elif num==100000: return "einhunderttausend"
+            elif num==1000000: return "eine millionen"
+            elif num<=1999: return "eintausend"+Text.getNumericPronounce(num%1000)
+            elif num<=999999 and num%1000==0: return Text.getNumericPronounce(tausender/1000)+"tausend" # handels 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000
+            elif num<=999999: return Text.getNumericPronounce(tausender/1000)+"tausend "+Text.getNumericPronounce(num%1000)
+            elif num<=1999999: return "eine millionen "+Text.getNumericPronounce(millonener%1000000)
+            elif num<=999999999 and num%1000000==0: return Text.getNumericPronounce(millonener/1000000)+" millionen" # handels 2mio, 3mio, 4mio, 5mio, 6mio, 7mio, 8mio, 9mio
+            elif num<=999999999: return Text.getNumericPronounce(millonener/1000000)+" millionen "+Text.getNumericPronounce(num%1000000)
+            else: print("ErrNum: "+num)
+
+    @staticmethod
+    def getNumericDottedPronounce(num:int) -> str:
+        if num==1:
+            return "erste"
+        elif num==3:
+            return "dritte"
+        elif num==8:
+            return "achte"
+        elif num>= 20:
+            return Text.getNumericPronounce(num)+"ste"
+        else:
+            return Text.getNumericPronounce(num)+"te"
+
+    @staticmethod
+    def getDateString(date, WeekDay:bool = True, Year:bool = True) -> str:
+        if WeekDay and Year:
+            return Text.getWeekDay(date.weekday())+", der "+Text.getNumericDottedPronounce(date.day)+" "+Text.getMonthName(date.month)+" "+Text.getNumericPronounce(date.year, 0, NummericPronounce.Year)
+            pass
+        elif WeekDay:
+            return Text.getWeekDay(date.weekday())+", der "+Text.getNumericDottedPronounce(date.day)+" "+Text.getMonthName(date.month)
+        elif Year:
+            return Text.getNumericDottedPronounce(date.day)+" "+Text.getMonthName(date.month)+" "+Text.getNumericPronounce(date.year, 0, NummericPronounce.Year)
+        else:
+            return Text.getNumericDottedPronounce(date.day)+" "+Text.getMonthName(date.month)
+        
+    @staticmethod
+    def getTimeString(time, Seconds:bool=False, Use24:bool=False) -> str:
+        if Seconds:
+            return Text.getTimeString(time,False,Use24)+" und " + time.seconds+" Sekunden"
+        else:
+            min = time.minute
+            sur=""
+            surn=""
+            if Use24:
+                hour=Text.getNumericPronounce(time.hour)
+                hourn=Text.getNumericPronounce((time.hour+1)%24)
+            else:
+                hour=Text.getNumericPronounce(time.hour%12)
+                hourn=Text.getNumericPronounce((time.hour+1)%12)
+                if time.hour<5 or time.hour>21: sur=" Nachts"
+                elif time.hour<11: sur=" Morgens"
+                elif time.hour<16: sur=" Mittags"
+                elif time.hour<19: sur=" Nachmittags"
+                else: sur=" Abends"
+
+                if time.hour<4 or time.hour>20: surn=" Nachts"
+                elif time.hour<10: surn=" Morgens"
+                elif time.hour<15: surn=" Mittags"
+                elif time.hour<18: surn=" Nachmittags"
+                else: surn=" Abends"
+
+            if time.hour==23:
+                shour=hour
+                shourn="Mitternacht"
+            elif time==0:
+                shour="Mitternacht"
+                shourn=hourn
+            else:
+                shour=hour
+                shourn=hourn
+
+            if min==0:
+                return "punkt "+shour+sur
+            elif min==30:
+                return "halb "+shourn+surn
+            elif min>=27 and min<30:
+                return "kurz vor halb "+shourn+surn
+            elif min>30 and min<=33:
+                return "kurz nach halb "+shourn+surn
+            elif min==15:
+                return "viertel nach "+shour+sur
+            elif min==45:
+                return "viertel vor "+shourn+surn
+            elif min<=3:
+                return "kurz nach "+shour+sur
+            elif min>=37:
+                return "kurz vor "+shourn+surn
+            else:
+                return hour+" Uhr "+Text.getNumericPronounce(min)+sur

+ 1 - 1
TextConfig/__init__.py

@@ -40,7 +40,7 @@ class TextConfig:
                             self._items.append({"var":var.strip() ,"val": json.loads(val[1:-1]) })
                             count+=1
         except:
-            print("Failed to load stats from file")
+            print("Failed to load texts from file")
         self._count = count
         return self._items
 

+ 4 - 0
Tin/__init__.py

@@ -0,0 +1,4 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from Tin.main import Mailer
+from Tin.mail import Mail

+ 73 - 0
Tin/konto.py

@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import os
+from lib.config import Config
+
+class konto:
+
+    @property
+    def SMTP(self) -> str:
+        return self.__smtp
+
+    @SMTP.setter
+    def SMTP(self,value:str):
+        self.__smtp = value
+
+    @property
+    def IMap(self) -> str:
+        return self.__imap
+
+    @IMap.setter
+    def IMap(self,value:str):
+        self.__imap = value
+
+    @property
+    def Pop3(self) -> str:
+        return self.__pop3
+
+    @Pop3.setter
+    def Pop3(self,value:str):
+        self.__pop3 = value
+
+    @property
+    def Username(self) -> str:
+        return self.__username
+
+    @Username.setter
+    def Username(self,value:str):
+        self.__username = value
+
+    @property
+    def Password(self) -> str:
+        return self.__password
+
+    @Password.setter
+    def Password(self,value:str):
+        self.__password = value
+
+    @property
+    def Name(self) -> str:
+        return self.__name
+
+    @Name.setter
+    def Name(self,value:str):
+        self.__name = value
+
+    def Send(self, to:str, subject:str, message:str, template:str=None, header:dict={}):
+        html:str = self.__loadTemplate(template)
+        messageId = "Trixy-"
+
+            
+    def __loadTemplate(self, path:str) -> str:
+        contents:str = self.__defaultTemplate()
+        if path==None or path=="":
+            return contents
+        if os.path.exists(path):
+            with open('the-zen-of-python.txt') as f:
+                contents = f.read()
+        else:
+            print(f"E-Mail template not found: {path}")
+        return contents
+
+    def __defaultTemplate(self) -> str:
+        return '%body%'

+ 202 - 0
Tin/mail.py

@@ -0,0 +1,202 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from random import random
+import configparser
+import base64
+
+from email.MIMEMultipart import MIMEMultipart
+from email.MIMEText import MIMEText
+from email.MIMEImage import MIMEImage
+
+
+class MailAdress:
+    def __init__(self,EMail:str="",Name:str=None):
+        self._name=Name
+        self._mail=EMail
+
+    @property
+    def Name(self):
+        if self._name==None or self._name=="":
+            return self._mail
+        return self._name
+
+    @Name.setter
+    def Name(self,value):
+        self._name = value
+
+    @property
+    def Mail(self):
+        return self._mail
+
+    @Mail.setter
+    def Mail(self,value):
+        self._mail = value
+
+class Attachment:
+    def __init__(self):
+        self._size = 0
+        self._fileName=""
+        self._content=""
+
+    @property
+    def Size(self) -> int:
+        return self._size
+
+    @Size.setter
+    def Size(self,value:int):
+        self._size = value
+
+    @property
+    def FileName(self) -> str:
+        return self._fileName
+
+    @FileName.setter
+    def FileName(self,value:str):
+        self._fileName = value
+
+    @property
+    def Content(self) -> str:
+        return self._content
+
+    @Content.setter
+    def Content(self,value:str):
+        self._content = value
+
+
+class Mail:
+
+    def __init__(self):
+        self._from = MailAdress()
+        self._to = MailAdress()
+        self._toName=""
+        self._subject=""
+        self._message=""
+        self._html=""
+        self._attachments=list[Attachment]
+        self._messageID="" #TODO (example Microsoft: Message-Id: <4b5fad31-dcb0-4112-9a75-34bafd9addaa@az.northcentralus.unknown.microsoft.com>)
+        self._headers=dict[str,str]
+        self._boundary = self.CreateBoundary()
+
+
+    @property
+    def From(self) -> MailAdress:
+        return self._from
+
+    @property
+    def To(self) -> MailAdress:
+        return self._to
+
+    @property
+    def Subject(self) -> str:
+        return self._subject
+
+    @Subject.setter
+    def Subject(self,value:str):
+        self._subject = value
+
+    @property
+    def Message(self)->str:
+        return self._message
+
+    @Message.setter
+    def Message(self,value:str):
+        self._message = value
+
+    @property
+    def HTML(self)->str:
+        return self._html
+
+    @HTML.setter
+    def HTML(self,value:str):
+        self._html = value
+
+    @property
+    def Attachments(self)->list[Attachment]:
+        return self._attachments
+
+    @property
+    def MessageID(self) -> str:
+        return self._messageID
+
+    @property
+    def Headers(self) -> dict[str,str]:
+        return self._headers
+
+    def Base64_Encode(self, text:str) -> str:
+        return base64.b64encode(text)
+
+    def Base64_Decode(self, text:str) -> str:
+        return base64.b64decode(text)
+
+    def Send(self, profile:str=""):
+        #TODO load Template - format text
+
+
+        # Read Config and Sender-Profile
+        configMain = configparser.ConfigParser()
+        configMainFile="config/config.ini"
+        configMain.read(configMainFile)
+        deviceName=configMain["global"]["devicename"]
+
+        config = configparser.ConfigParser()
+        configFile="config/email.ini"
+        config.read(configFile)
+        if profile=="":
+            if config and "general" in config and "send" in config["general"]:
+                profile=config["general"]["send"]
+        if profile=="":
+            print("Failed to send E-Mail. No profile found")
+            return
+        if profile not in config:
+            print(f"Profile '{profile}' not found in {configFile}")
+            return
+
+        # Read SMTP Info
+        smtpHost=config[profile]["smtp"]
+        smtpUser=config[profile]["login"]
+        smtpPass=config[profile]["password"]
+        smtpMail=config[profile]["email"]
+        if smtpHost=="" or smtpUser=="" or smtpPass=="":
+            print(f"Failed to load SMTP Host / Login")
+            return
+
+        # Set From / To
+        strFrom = self.From.Name+" <"+smtpMail+">"
+        strTo = self.To.Name+" <"+self.To.Mail+">"
+
+        # Prepare E-Mail
+        msgRoot = MIMEMultipart('related')
+        msgRoot['Subject'] = self._subject
+        msgRoot['From'] = strFrom
+        msgRoot['To'] = strTo
+        msgRoot.preamble = 'This is a multi-part message in MIME format.'
+
+        msgAlternative = MIMEMultipart('alternative')
+        msgRoot.attach(msgAlternative)
+
+        msgText = MIMEText(self._message)
+        msgAlternative.attach(msgText)
+
+        msgText = MIMEText(self._html, 'html')
+        msgAlternative.attach(msgText)
+
+        #TODO add images
+        ## HTML <img src="cid:image1">
+        ## This example assumes the image is in the current directory
+        #fp = open('test.jpg', 'rb')
+        #msgImage = MIMEImage(fp.read())
+        #fp.close()
+        ## Define the image's ID as referenced above
+        #msgImage.add_header('Content-ID', '<image1>')
+        #msgRoot.attach(msgImage)
+
+        #TODO add custom headers
+
+        # Create SMTP Connection and send E-Mail
+        import smtplib
+        smtp = smtplib.SMTP()
+        smtp.connect(smtpHost)
+        smtp.starttls() #TODO check if TLS is working - maybe config.ini option
+        smtp.login(smtpUser, smtpPass)
+        smtp.sendmail(strFrom, strTo, msgRoot.as_string())
+        smtp.quit()

+ 24 - 0
Tin/main.py

@@ -0,0 +1,24 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+import configparser
+
+class Mailer:
+
+    instance = None
+    @staticmethod
+    def getInstance():
+        if Mailer.instance==None:
+            Mailer.instance = Mailer()
+        return Mailer.instance
+
+    def __init__(self):
+        try:
+            config = configparser.ConfigParser()
+            config.read('config/email.ini')
+        except:
+            print("Failed to get Config")
+            config = dict
+        for k,v in config:
+            if k!="general":
+                pass

+ 11 - 0
Trixy.pyproj

@@ -31,6 +31,7 @@
     <Compile Include="MicCapture\__init__.py" />
     <Compile Include="mods\BaseMod.py" />
     <Compile Include="mods\WindowsSay\__init__.py" />
+    <Compile Include="mods\ModPlaySound\__init__.py" />
     <Compile Include="OpenWeatherMap\WeatherData.py" />
     <Compile Include="OpenWeatherMap\WeatherData_Clouds.py" />
     <Compile Include="OpenWeatherMap\WeatherData_Coords.py" />
@@ -51,6 +52,10 @@
     <Compile Include="Stats\TalkingStats.py" />
     <Compile Include="Stats\__init__.py" />
     <Compile Include="TextConfig\__init__.py" />
+    <Compile Include="Tin\konto.py" />
+    <Compile Include="Tin\mail.py" />
+    <Compile Include="Tin\main.py" />
+    <Compile Include="Tin\__init__.py" />
     <Compile Include="VoicePlay\Cache.py" />
     <Compile Include="VoicePlay\main.py" />
     <Compile Include="VoicePlay\__init__.py" />
@@ -67,13 +72,19 @@
     <Content Include="LICENSE" />
     <Content Include="mods\README.md" />
     <Content Include="README.md" />
+    <Content Include="ressources\email\blank.html" />
   </ItemGroup>
   <ItemGroup>
     <Folder Include="lib\" />
     <Folder Include="MicCapture\" />
     <Folder Include="cache\" />
     <Folder Include="config\" />
+    <Folder Include="mods\ModPlaySound\" />
     <Folder Include="mods\WindowsSay\" />
+    <Folder Include="ressources\" />
+    <Folder Include="ressources\email\" />
+    <Folder Include="ressources\sounds\" />
+    <Folder Include="Tin\" />
     <Folder Include="OpenWeatherMap\" />
     <Folder Include="TextConfig\" />
     <Folder Include="Stats\" />

+ 5 - 2
main.py

@@ -15,6 +15,9 @@ if __name__ == "__main__":
     stats:Stats = Stats.getInstance()
 
     print("Init Speech")
-    speach.getInstance().Say("Willkommen")
+    speach.getInstance().Say(".")
+
+    mod.execute("PlaySound", "welcome.wav", False)
+    mod.execute("PlaySound", "welcome.mp3", False)
+    print("Sound playing...")
 
-    mod.execute("PlaySound", "welcome.wav")

+ 21 - 0
mods/BaseMod.py

@@ -0,0 +1,21 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from inspect import stack
+from Stats import Stats as st
+
+class BaseMod:
+    
+    def IsActive(self):
+        return True
+
+    @property
+    def Stats(self) -> st:
+        return st.getInstance()
+
+
+
+    def onSay(self, text:str):
+        pass
+
+    def onPlaySound(self, soundFile:str, ASync:bool=True):
+        pass

+ 75 - 0
mods/ModPlaySound/__init__.py

@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from mods.BaseMod import BaseMod
+import threading
+import os
+import pyaudio
+from pydub import AudioSegment
+from pydub.utils import make_chunks
+import wave
+
+class ModPlaySound(BaseMod):
+
+    __isActive=True
+
+    def __init__(self):
+        self._audio = pyaudio.PyAudio()
+
+
+    def IsActive(self):
+        return True
+
+    def __del__(self):
+        self._audio.terminate()
+
+    def onPlaySound(self, soundFile:str, ASync:bool=True):
+        f = r"ressources/sounds/"+soundFile
+        if os.path.exists(f):
+            if ASync:
+                if soundFile.endswith(".wav"):
+                    threading.Thread(target=self._playSound1, args=(f,), daemon=False).start()
+                else:
+                    threading.Thread(target=self._playSound3, args=(f,), daemon=False).start()
+            else:
+                if soundFile.endswith(".wave"):
+                    self._playSound1(f)
+                else:
+                    self._playSound3(f)
+        else:
+            print(f"File not found: {f}")
+
+    def _playSound1(self, path):
+        f = wave.open(path,"rb")
+        stream = self._audio.open(format = self._audio.get_format_from_width(f.getsampwidth()),channels = f.getnchannels(),rate = f.getframerate(),output = True)
+        data = f.readframes(1024)
+        while data:
+            stream.write(data)
+            data = f.readframes(1024)
+        stream.stop_stream()
+        stream.close()
+
+    def _playSound2(self, path):
+        playsound(path)
+
+    def _playSound3(self, path):
+        sound = AudioSegment.from_file(path)
+
+        stream = self._audio.open(format = self._audio.get_format_from_width(sound.sample_width),
+            channels = sound.channels,
+            rate = sound.frame_rate,
+            output = True)
+
+        start = 0
+        length = sound.duration_seconds
+        volume = 100.0
+        playchunk = sound[start*1000.0:(start+length)*1000.0] - (60 - (60 * (volume/100.0)))
+        millisecondchunk = 50 / 1000.0
+        
+        self.time = start
+        for chunks in make_chunks(playchunk, millisecondchunk*1000):
+            self.time += millisecondchunk
+            stream.write(chunks._data)
+            if self.time >= start+length:
+                break
+
+        stream.close()

+ 26 - 0
mods/WindowsSay/__init__.py

@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+from mods.BaseMod import BaseMod
+import os
+import win32com.client
+import platform
+
+class WindowsSay(BaseMod):
+
+    def __init__(self):
+        self.sys = platform.system()
+        if self.sys=="Windows":
+            self.speaker = win32com.client.Dispatch("SAPI.SpVoice")
+        else:
+            self.speaker = None
+
+
+    def IsActive(self):
+        return self.speaker!=None
+
+    def onSay(self, text):
+        if self.speaker != None:
+            self.speaker.Rate = 0.0
+            self.speaker.Speak(text)
+        else:
+            print("Failed to speak - Speaker is none")

+ 1 - 0
ressources/email/README.md

@@ -0,0 +1 @@
+

+ 13 - 0
ressources/email/blank.html

@@ -0,0 +1,13 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="https://www.w3.org/1999/xhtml" style="min-height: 100%;background:#ffffff">
+<head>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+    <meta name="viewport" content="width-device-width">
+    <meta name="eventId" content="visual-studio-feedback-new-comment">
+    <meta name="messageId" content="%messageid%">
+    <title>%subject%</title>
+</head>
+<body>
+    %body%
+</body>
+</html>

+ 1 - 0
ressources/sounds/README.md

@@ -0,0 +1 @@
+

+ 1 - 0
test/README.md

@@ -0,0 +1 @@
+